blob: 875250de4031d899b5da06b4639630a03956544e [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001#!/usr/bin/env python
2# ex:ts=4:sw=4:sts=4:et
3# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4"""
5 class for handling configuration data files
6
7 Reads a .conf file and obtains its metadata
8
9"""
10
11# Copyright (C) 2003, 2004 Chris Larson
12# Copyright (C) 2003, 2004 Phil Blundell
13#
14# This program is free software; you can redistribute it and/or modify
15# it under the terms of the GNU General Public License version 2 as
16# published by the Free Software Foundation.
17#
18# This program is distributed in the hope that it will be useful,
19# but WITHOUT ANY WARRANTY; without even the implied warranty of
20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21# GNU General Public License for more details.
22#
23# You should have received a copy of the GNU General Public License along
24# with this program; if not, write to the Free Software Foundation, Inc.,
25# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26
27import errno
28import re
29import os
30import bb.utils
31from bb.parse import ParseError, resolve_file, ast, logger, handle
32
33__config_regexp__ = re.compile( r"""
34 ^
35 (?P<exp>export\s*)?
36 (?P<var>[a-zA-Z0-9\-~_+.${}/]+?)
37 (\[(?P<flag>[a-zA-Z0-9\-_+.]+)\])?
38
39 \s* (
40 (?P<colon>:=) |
41 (?P<lazyques>\?\?=) |
42 (?P<ques>\?=) |
43 (?P<append>\+=) |
44 (?P<prepend>=\+) |
45 (?P<predot>=\.) |
46 (?P<postdot>\.=) |
47 =
48 ) \s*
49
50 (?!'[^']*'[^']*'$)
51 (?!\"[^\"]*\"[^\"]*\"$)
52 (?P<apo>['\"])
53 (?P<value>.*)
54 (?P=apo)
55 $
56 """, re.X)
57__include_regexp__ = re.compile( r"include\s+(.+)" )
58__require_regexp__ = re.compile( r"require\s+(.+)" )
59__export_regexp__ = re.compile( r"export\s+([a-zA-Z0-9\-_+.${}/]+)$" )
Patrick Williamsc0f7c042017-02-23 20:41:17 -060060__unset_regexp__ = re.compile( r"unset\s+([a-zA-Z0-9\-_+.${}/]+)$" )
61__unset_flag_regexp__ = re.compile( r"unset\s+([a-zA-Z0-9\-_+.${}/]+)\[([a-zA-Z0-9\-_+.${}/]+)\]$" )
Patrick Williamsc124f4f2015-09-15 14:41:29 -050062
63def init(data):
64 topdir = data.getVar('TOPDIR', False)
65 if not topdir:
66 data.setVar('TOPDIR', os.getcwd())
67
68
69def supports(fn, d):
70 return fn[-5:] == ".conf"
71
72def include(parentfn, fn, lineno, data, error_out):
73 """
74 error_out: A string indicating the verb (e.g. "include", "inherit") to be
75 used in a ParseError that will be raised if the file to be included could
76 not be included. Specify False to avoid raising an error in this case.
77 """
78 if parentfn == fn: # prevent infinite recursion
79 return None
80
81 fn = data.expand(fn)
82 parentfn = data.expand(parentfn)
83
84 if not os.path.isabs(fn):
85 dname = os.path.dirname(parentfn)
86 bbpath = "%s:%s" % (dname, data.getVar("BBPATH", True))
87 abs_fn, attempts = bb.utils.which(bbpath, fn, history=True)
88 if abs_fn and bb.parse.check_dependency(data, abs_fn):
Patrick Williamsc0f7c042017-02-23 20:41:17 -060089 logger.warning("Duplicate inclusion for %s in %s" % (abs_fn, data.getVar('FILE', True)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -050090 for af in attempts:
91 bb.parse.mark_dependency(data, af)
92 if abs_fn:
93 fn = abs_fn
94 elif bb.parse.check_dependency(data, fn):
Patrick Williamsc0f7c042017-02-23 20:41:17 -060095 logger.warning("Duplicate inclusion for %s in %s" % (fn, data.getVar('FILE', True)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -050096
97 try:
98 bb.parse.handle(fn, data, True)
99 except (IOError, OSError) as exc:
100 if exc.errno == errno.ENOENT:
101 if error_out:
102 raise ParseError("Could not %s file %s" % (error_out, fn), parentfn, lineno)
103 logger.debug(2, "CONF file '%s' not found", fn)
104 else:
105 if error_out:
106 raise ParseError("Could not %s file %s: %s" % (error_out, fn, exc.strerror), parentfn, lineno)
107 else:
108 raise ParseError("Error parsing %s: %s" % (fn, exc.strerror), parentfn, lineno)
109
110# We have an issue where a UI might want to enforce particular settings such as
111# an empty DISTRO variable. If configuration files do something like assigning
112# a weak default, it turns out to be very difficult to filter out these changes,
113# particularly when the weak default might appear half way though parsing a chain
114# of configuration files. We therefore let the UIs hook into configuration file
115# parsing. This turns out to be a hard problem to solve any other way.
116confFilters = []
117
118def handle(fn, data, include):
119 init(data)
120
121 if include == 0:
122 oldfile = None
123 else:
124 oldfile = data.getVar('FILE', False)
125
126 abs_fn = resolve_file(fn, data)
127 f = open(abs_fn, 'r')
128
129 if include:
130 bb.parse.mark_dependency(data, abs_fn)
131
132 statements = ast.StatementGroup()
133 lineno = 0
134 while True:
135 lineno = lineno + 1
136 s = f.readline()
137 if not s:
138 break
139 w = s.strip()
140 # skip empty lines
141 if not w:
142 continue
143 s = s.rstrip()
144 while s[-1] == '\\':
145 s2 = f.readline().strip()
146 lineno = lineno + 1
147 if (not s2 or s2 and s2[0] != "#") and s[0] == "#" :
148 bb.fatal("There is a confusing multiline, partially commented expression on line %s of file %s (%s).\nPlease clarify whether this is all a comment or should be parsed." % (lineno, fn, s))
149 s = s[:-1] + s2
150 # skip comments
151 if s[0] == '#':
152 continue
153 feeder(lineno, s, abs_fn, statements)
154
155 # DONE WITH PARSING... time to evaluate
156 data.setVar('FILE', abs_fn)
157 statements.eval(data)
158 if oldfile:
159 data.setVar('FILE', oldfile)
160
161 f.close()
162
163 for f in confFilters:
164 f(fn, data)
165
166 return data
167
168def feeder(lineno, s, fn, statements):
169 m = __config_regexp__.match(s)
170 if m:
171 groupd = m.groupdict()
172 ast.handleData(statements, fn, lineno, groupd)
173 return
174
175 m = __include_regexp__.match(s)
176 if m:
177 ast.handleInclude(statements, fn, lineno, m, False)
178 return
179
180 m = __require_regexp__.match(s)
181 if m:
182 ast.handleInclude(statements, fn, lineno, m, True)
183 return
184
185 m = __export_regexp__.match(s)
186 if m:
187 ast.handleExport(statements, fn, lineno, m)
188 return
189
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600190 m = __unset_regexp__.match(s)
191 if m:
192 ast.handleUnset(statements, fn, lineno, m)
193 return
194
195 m = __unset_flag_regexp__.match(s)
196 if m:
197 ast.handleUnsetFlag(statements, fn, lineno, m)
198 return
199
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500200 raise ParseError("unparsed line: '%s'" % s, fn, lineno);
201
202# Add us to the handlers list
203from bb.parse import handlers
204handlers.append({'supports': supports, 'handle': handle, 'init': init})
205del handlers