blob: fbd75b14ad0653a1a96bdceff3a7e3c1e1b5be08 [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\-_+.${}/]+)$" )
60
61def init(data):
62 topdir = data.getVar('TOPDIR', False)
63 if not topdir:
64 data.setVar('TOPDIR', os.getcwd())
65
66
67def supports(fn, d):
68 return fn[-5:] == ".conf"
69
70def include(parentfn, fn, lineno, data, error_out):
71 """
72 error_out: A string indicating the verb (e.g. "include", "inherit") to be
73 used in a ParseError that will be raised if the file to be included could
74 not be included. Specify False to avoid raising an error in this case.
75 """
76 if parentfn == fn: # prevent infinite recursion
77 return None
78
79 fn = data.expand(fn)
80 parentfn = data.expand(parentfn)
81
82 if not os.path.isabs(fn):
83 dname = os.path.dirname(parentfn)
84 bbpath = "%s:%s" % (dname, data.getVar("BBPATH", True))
85 abs_fn, attempts = bb.utils.which(bbpath, fn, history=True)
86 if abs_fn and bb.parse.check_dependency(data, abs_fn):
87 logger.warn("Duplicate inclusion for %s in %s" % (abs_fn, data.getVar('FILE', True)))
88 for af in attempts:
89 bb.parse.mark_dependency(data, af)
90 if abs_fn:
91 fn = abs_fn
92 elif bb.parse.check_dependency(data, fn):
93 logger.warn("Duplicate inclusion for %s in %s" % (fn, data.getVar('FILE', True)))
94
95 try:
96 bb.parse.handle(fn, data, True)
97 except (IOError, OSError) as exc:
98 if exc.errno == errno.ENOENT:
99 if error_out:
100 raise ParseError("Could not %s file %s" % (error_out, fn), parentfn, lineno)
101 logger.debug(2, "CONF file '%s' not found", fn)
102 else:
103 if error_out:
104 raise ParseError("Could not %s file %s: %s" % (error_out, fn, exc.strerror), parentfn, lineno)
105 else:
106 raise ParseError("Error parsing %s: %s" % (fn, exc.strerror), parentfn, lineno)
107
108# We have an issue where a UI might want to enforce particular settings such as
109# an empty DISTRO variable. If configuration files do something like assigning
110# a weak default, it turns out to be very difficult to filter out these changes,
111# particularly when the weak default might appear half way though parsing a chain
112# of configuration files. We therefore let the UIs hook into configuration file
113# parsing. This turns out to be a hard problem to solve any other way.
114confFilters = []
115
116def handle(fn, data, include):
117 init(data)
118
119 if include == 0:
120 oldfile = None
121 else:
122 oldfile = data.getVar('FILE', False)
123
124 abs_fn = resolve_file(fn, data)
125 f = open(abs_fn, 'r')
126
127 if include:
128 bb.parse.mark_dependency(data, abs_fn)
129
130 statements = ast.StatementGroup()
131 lineno = 0
132 while True:
133 lineno = lineno + 1
134 s = f.readline()
135 if not s:
136 break
137 w = s.strip()
138 # skip empty lines
139 if not w:
140 continue
141 s = s.rstrip()
142 while s[-1] == '\\':
143 s2 = f.readline().strip()
144 lineno = lineno + 1
145 if (not s2 or s2 and s2[0] != "#") and s[0] == "#" :
146 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))
147 s = s[:-1] + s2
148 # skip comments
149 if s[0] == '#':
150 continue
151 feeder(lineno, s, abs_fn, statements)
152
153 # DONE WITH PARSING... time to evaluate
154 data.setVar('FILE', abs_fn)
155 statements.eval(data)
156 if oldfile:
157 data.setVar('FILE', oldfile)
158
159 f.close()
160
161 for f in confFilters:
162 f(fn, data)
163
164 return data
165
166def feeder(lineno, s, fn, statements):
167 m = __config_regexp__.match(s)
168 if m:
169 groupd = m.groupdict()
170 ast.handleData(statements, fn, lineno, groupd)
171 return
172
173 m = __include_regexp__.match(s)
174 if m:
175 ast.handleInclude(statements, fn, lineno, m, False)
176 return
177
178 m = __require_regexp__.match(s)
179 if m:
180 ast.handleInclude(statements, fn, lineno, m, True)
181 return
182
183 m = __export_regexp__.match(s)
184 if m:
185 ast.handleExport(statements, fn, lineno, m)
186 return
187
188 raise ParseError("unparsed line: '%s'" % s, fn, lineno);
189
190# Add us to the handlers list
191from bb.parse import handlers
192handlers.append({'supports': supports, 'handle': handle, 'init': init})
193del handlers