bitbake/data_smart: Improve Variable expansion error handling
[bitbake.git] / lib / bb / data_smart.py
1 # ex:ts=4:sw=4:sts=4:et
2 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3 """
4 BitBake Smart Dictionary Implementation
5
6 Functions for interacting with the data structure used by the
7 BitBake build tools.
8
9 """
10
11 # Copyright (C) 2003, 2004  Chris Larson
12 # Copyright (C) 2004, 2005  Seb Frankengul
13 # Copyright (C) 2005, 2006  Holger Hans Peter Freyther
14 # Copyright (C) 2005        Uli Luckas
15 # Copyright (C) 2005        ROAD GmbH
16 #
17 # This program is free software; you can redistribute it and/or modify
18 # it under the terms of the GNU General Public License version 2 as
19 # published by the Free Software Foundation.
20 #
21 # This program is distributed in the hope that it will be useful,
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24 # GNU General Public License for more details.
25 #
26 # You should have received a copy of the GNU General Public License along
27 # with this program; if not, write to the Free Software Foundation, Inc.,
28 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
29 # Based on functions from the base bb module, Copyright 2003 Holger Schurig
30
31 import copy, re
32 from collections import MutableMapping
33 import logging
34 import bb, bb.codeparser
35 from bb   import utils
36 from bb.COW  import COWDictBase
37
38 logger = logging.getLogger("BitBake.Data")
39
40 __setvar_keyword__ = ["_append", "_prepend"]
41 __setvar_regexp__ = re.compile('(?P<base>.*?)(?P<keyword>_append|_prepend)(_(?P<add>.*))?')
42 __expand_var_regexp__ = re.compile(r"\${[^{}]+}")
43 __expand_python_regexp__ = re.compile(r"\${@.+?}")
44
45
46 class VariableParse:
47     def __init__(self, varname, d, val = None):
48         self.varname = varname
49         self.d = d
50         self.value = val
51
52         self.references = set()
53         self.execs = set()
54
55     def var_sub(self, match):
56             key = match.group()[2:-1]
57             if self.varname and key:
58                 if self.varname == key:
59                     raise Exception("variable %s references itself!" % self.varname)
60             var = self.d.getVar(key, 1)
61             if var is not None:
62                 self.references.add(key)
63                 return var
64             else:
65                 return match.group()
66
67     def python_sub(self, match):
68             code = match.group()[3:-1]
69             codeobj = compile(code.strip(), self.varname or "<expansion>", "eval")
70
71             parser = bb.codeparser.PythonParser()
72             parser.parse_python(code)
73             self.references |= parser.references
74             self.execs |= parser.execs
75
76             value = utils.better_eval(codeobj, DataContext(self.d))
77             return str(value)
78
79
80 class DataContext(dict):
81     def __init__(self, metadata, **kwargs):
82         self.metadata = metadata
83         dict.__init__(self, **kwargs)
84         self['d'] = metadata
85
86     def __missing__(self, key):
87         value = self.metadata.getVar(key, True)
88         if value is None or self.metadata.getVarFlag(key, 'func'):
89             raise KeyError(key)
90         else:
91             return value
92
93 class ExpansionError(Exception):
94     def __init__(self, varname, expression, exception):
95         self.expression = expression
96         self.variablename = varname
97         self.exception = exception
98         self.msg = "Failure expanding variable %s, expression was %s which triggered exception %s: %s" % (varname, expression, type(exception).__name__, exception)
99         Exception.__init__(self, self.msg)
100         self.args = (varname, expression, exception)
101     def __str__(self):
102         return self.msg
103
104 class DataSmart(MutableMapping):
105     def __init__(self, special = COWDictBase.copy(), seen = COWDictBase.copy() ):
106         self.dict = {}
107
108         # cookie monster tribute
109         self._special_values = special
110         self._seen_overrides = seen
111
112         self.expand_cache = {}
113
114     def expandWithRefs(self, s, varname):
115
116         if not isinstance(s, basestring): # sanity check
117             return VariableParse(varname, self, s)
118
119         if varname and varname in self.expand_cache:
120             return self.expand_cache[varname]
121
122         varparse = VariableParse(varname, self)
123
124         while s.find('${') != -1:
125             olds = s
126             try:
127                 s = __expand_var_regexp__.sub(varparse.var_sub, s)
128                 s = __expand_python_regexp__.sub(varparse.python_sub, s)
129                 if s == olds:
130                     break
131             except ExpansionError:
132                 raise
133             except Exception as exc:
134                 raise ExpansionError(varname, s, exc)
135
136         varparse.value = s
137
138         if varname:
139             self.expand_cache[varname] = varparse
140
141         return varparse
142
143     def expand(self, s, varname):
144         return self.expandWithRefs(s, varname).value
145
146
147     def finalize(self):
148         """Performs final steps upon the datastore, including application of overrides"""
149
150         overrides = (self.getVar("OVERRIDES", True) or "").split(":") or []
151
152         #
153         # Well let us see what breaks here. We used to iterate
154         # over each variable and apply the override and then
155         # do the line expanding.
156         # If we have bad luck - which we will have - the keys
157         # where in some order that is so important for this
158         # method which we don't have anymore.
159         # Anyway we will fix that and write test cases this
160         # time.
161
162         #
163         # First we apply all overrides
164         # Then  we will handle _append and _prepend
165         #
166
167         for o in overrides:
168             # calculate '_'+override
169             l = len(o) + 1
170
171             # see if one should even try
172             if o not in self._seen_overrides:
173                 continue
174
175             vars = self._seen_overrides[o]
176             for var in vars:
177                 name = var[:-l]
178                 try:
179                     self.setVar(name, self.getVar(var, False))
180                 except Exception:
181                     logger.info("Untracked delVar")
182
183         # now on to the appends and prepends
184         for op in __setvar_keyword__:
185             if op in self._special_values:
186                 appends = self._special_values[op] or []
187                 for append in appends:
188                     keep = []
189                     for (a, o) in self.getVarFlag(append, op) or []:
190                         if o and not o in overrides:
191                             keep.append((a ,o))
192                             continue
193
194                         if op is "_append":
195                             sval = self.getVar(append, False) or ""
196                             sval += a
197                             self.setVar(append, sval)
198                         elif op is "_prepend":
199                             sval = a + (self.getVar(append, False) or "")
200                             self.setVar(append, sval)
201
202                     # We save overrides that may be applied at some later stage
203                     if keep:
204                         self.setVarFlag(append, op, keep)
205                     else:
206                         self.delVarFlag(append, op)
207
208     def initVar(self, var):
209         self.expand_cache = {}
210         if not var in self.dict:
211             self.dict[var] = {}
212
213     def _findVar(self, var):
214         dest = self.dict
215         while dest:
216             if var in dest:
217                 return dest[var]
218
219             if "_data" not in dest:
220                 break
221             dest = dest["_data"]
222
223     def _makeShadowCopy(self, var):
224         if var in self.dict:
225             return
226
227         local_var = self._findVar(var)
228
229         if local_var:
230             self.dict[var] = copy.copy(local_var)
231         else:
232             self.initVar(var)
233
234     def setVar(self, var, value):
235         self.expand_cache = {}
236         match  = __setvar_regexp__.match(var)
237         if match and match.group("keyword") in __setvar_keyword__:
238             base = match.group('base')
239             keyword = match.group("keyword")
240             override = match.group('add')
241             l = self.getVarFlag(base, keyword) or []
242             l.append([value, override])
243             self.setVarFlag(base, keyword, l)
244
245             # todo make sure keyword is not __doc__ or __module__
246             # pay the cookie monster
247             try:
248                 self._special_values[keyword].add( base )
249             except KeyError:
250                 self._special_values[keyword] = set()
251                 self._special_values[keyword].add( base )
252
253             return
254
255         if not var in self.dict:
256             self._makeShadowCopy(var)
257
258         # more cookies for the cookie monster
259         if '_' in var:
260             override = var[var.rfind('_')+1:]
261             if override not in self._seen_overrides:
262                 self._seen_overrides[override] = set()
263             self._seen_overrides[override].add( var )
264
265         # setting var
266         self.dict[var]["content"] = value
267
268     def getVar(self, var, exp):
269         value = self.getVarFlag(var, "content")
270
271         if exp and value:
272             return self.expand(value, var)
273         return value
274
275     def renameVar(self, key, newkey):
276         """
277         Rename the variable key to newkey
278         """
279         val = self.getVar(key, 0)
280         if val is not None:
281             self.setVar(newkey, val)
282
283         for i in ('_append', '_prepend'):
284             src = self.getVarFlag(key, i)
285             if src is None:
286                 continue
287
288             dest = self.getVarFlag(newkey, i) or []
289             dest.extend(src)
290             self.setVarFlag(newkey, i, dest)
291
292             if i in self._special_values and key in self._special_values[i]:
293                 self._special_values[i].remove(key)
294                 self._special_values[i].add(newkey)
295
296         self.delVar(key)
297
298     def delVar(self, var):
299         self.expand_cache = {}
300         self.dict[var] = {}
301
302     def setVarFlag(self, var, flag, flagvalue):
303         if not var in self.dict:
304             self._makeShadowCopy(var)
305         self.dict[var][flag] = flagvalue
306
307     def getVarFlag(self, var, flag, expand=False):
308         local_var = self._findVar(var)
309         value = None
310         if local_var:
311             if flag in local_var:
312                 value = copy.copy(local_var[flag])
313             elif flag == "content" and "defaultval" in local_var:
314                 value = copy.copy(local_var["defaultval"])
315         if expand and value:
316             value = self.expand(value, None)
317         return value
318
319     def delVarFlag(self, var, flag):
320         local_var = self._findVar(var)
321         if not local_var:
322             return
323         if not var in self.dict:
324             self._makeShadowCopy(var)
325
326         if var in self.dict and flag in self.dict[var]:
327             del self.dict[var][flag]
328
329     def setVarFlags(self, var, flags):
330         if not var in self.dict:
331             self._makeShadowCopy(var)
332
333         for i in flags:
334             if i == "content":
335                 continue
336             self.dict[var][i] = flags[i]
337
338     def getVarFlags(self, var):
339         local_var = self._findVar(var)
340         flags = {}
341
342         if local_var:
343             for i in local_var:
344                 if i == "content":
345                     continue
346                 flags[i] = local_var[i]
347
348         if len(flags) == 0:
349             return None
350         return flags
351
352
353     def delVarFlags(self, var):
354         if not var in self.dict:
355             self._makeShadowCopy(var)
356
357         if var in self.dict:
358             content = None
359
360             # try to save the content
361             if "content" in self.dict[var]:
362                 content  = self.dict[var]["content"]
363                 self.dict[var]            = {}
364                 self.dict[var]["content"] = content
365             else:
366                 del self.dict[var]
367
368
369     def createCopy(self):
370         """
371         Create a copy of self by setting _data to self
372         """
373         # we really want this to be a DataSmart...
374         data = DataSmart(seen=self._seen_overrides.copy(), special=self._special_values.copy())
375         data.dict["_data"] = self.dict
376
377         return data
378
379     def expandVarref(self, variable, parents=False):
380         """Find all references to variable in the data and expand it
381            in place, optionally descending to parent datastores."""
382
383         if parents:
384             keys = iter(self)
385         else:
386             keys = self.localkeys()
387
388         ref = '${%s}' % variable
389         value = self.getVar(variable, False)
390         for key in keys:
391             referrervalue = self.getVar(key, False)
392             if referrervalue and ref in referrervalue:
393                 self.setVar(key, referrervalue.replace(ref, value))
394
395     def localkeys(self):
396         for key in self.dict:
397             if key != '_data':
398                 yield key
399
400     def __iter__(self):
401         seen = set()
402         def _keys(d):
403             if "_data" in d:
404                 for key in _keys(d["_data"]):
405                     yield key
406
407             for key in d:
408                 if key != "_data":
409                     if not key in seen:
410                         seen.add(key)
411                         yield key
412         return _keys(self.dict)
413
414     def __len__(self):
415         return len(frozenset(self))
416
417     def __getitem__(self, item):
418         value = self.getVar(item, False)
419         if value is None:
420             raise KeyError(item)
421         else:
422             return value
423
424     def __setitem__(self, var, value):
425         self.setVar(var, value)
426
427     def __delitem__(self, var):
428         self.delVar(var)