blob: fa1e79427968ac208c0c9a8035304383180638e7 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3"""
4BitBake Smart Dictionary Implementation
5
6Functions for interacting with the data structure used by the
7BitBake 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
31import copy, re, sys, traceback
32from collections import MutableMapping
33import logging
34import hashlib
35import bb, bb.codeparser
36from bb import utils
37from bb.COW import COWDictBase
38
39logger = logging.getLogger("BitBake.Data")
40
41__setvar_keyword__ = ["_append", "_prepend", "_remove"]
42__setvar_regexp__ = re.compile('(?P<base>.*?)(?P<keyword>_append|_prepend|_remove)(_(?P<add>.*))?$')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050043__expand_var_regexp__ = re.compile(r"\${[^{}@\n\t :]+}")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050044__expand_python_regexp__ = re.compile(r"\${@.+?}")
45
46def infer_caller_details(loginfo, parent = False, varval = True):
47 """Save the caller the trouble of specifying everything."""
48 # Save effort.
49 if 'ignore' in loginfo and loginfo['ignore']:
50 return
51 # If nothing was provided, mark this as possibly unneeded.
52 if not loginfo:
53 loginfo['ignore'] = True
54 return
55 # Infer caller's likely values for variable (var) and value (value),
56 # to reduce clutter in the rest of the code.
57 above = None
58 def set_above():
59 try:
60 raise Exception
61 except Exception:
62 tb = sys.exc_info()[2]
63 if parent:
64 return tb.tb_frame.f_back.f_back.f_back
65 else:
66 return tb.tb_frame.f_back.f_back
67
68 if varval and ('variable' not in loginfo or 'detail' not in loginfo):
69 if not above:
70 above = set_above()
71 lcls = above.f_locals.items()
72 for k, v in lcls:
73 if k == 'value' and 'detail' not in loginfo:
74 loginfo['detail'] = v
75 if k == 'var' and 'variable' not in loginfo:
76 loginfo['variable'] = v
77 # Infer file/line/function from traceback
78 # Don't use traceback.extract_stack() since it fills the line contents which
79 # we don't need and that hits stat syscalls
80 if 'file' not in loginfo:
81 if not above:
82 above = set_above()
83 f = above.f_back
84 line = f.f_lineno
85 file = f.f_code.co_filename
86 func = f.f_code.co_name
87 loginfo['file'] = file
88 loginfo['line'] = line
89 if func not in loginfo:
90 loginfo['func'] = func
91
92class VariableParse:
93 def __init__(self, varname, d, val = None):
94 self.varname = varname
95 self.d = d
96 self.value = val
97
98 self.references = set()
99 self.execs = set()
100 self.contains = {}
101
102 def var_sub(self, match):
103 key = match.group()[2:-1]
104 if self.varname and key:
105 if self.varname == key:
106 raise Exception("variable %s references itself!" % self.varname)
107 if key in self.d.expand_cache:
108 varparse = self.d.expand_cache[key]
109 var = varparse.value
110 else:
111 var = self.d.getVarFlag(key, "_content", True)
112 self.references.add(key)
113 if var is not None:
114 return var
115 else:
116 return match.group()
117
118 def python_sub(self, match):
119 code = match.group()[3:-1]
120 codeobj = compile(code.strip(), self.varname or "<expansion>", "eval")
121
122 parser = bb.codeparser.PythonParser(self.varname, logger)
123 parser.parse_python(code)
124 if self.varname:
125 vardeps = self.d.getVarFlag(self.varname, "vardeps", True)
126 if vardeps is None:
127 parser.log.flush()
128 else:
129 parser.log.flush()
130 self.references |= parser.references
131 self.execs |= parser.execs
132
133 for k in parser.contains:
134 if k not in self.contains:
135 self.contains[k] = parser.contains[k].copy()
136 else:
137 self.contains[k].update(parser.contains[k])
138 value = utils.better_eval(codeobj, DataContext(self.d))
139 return str(value)
140
141
142class DataContext(dict):
143 def __init__(self, metadata, **kwargs):
144 self.metadata = metadata
145 dict.__init__(self, **kwargs)
146 self['d'] = metadata
147
148 def __missing__(self, key):
149 value = self.metadata.getVar(key, True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500150 if value is None or self.metadata.getVarFlag(key, 'func', False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500151 raise KeyError(key)
152 else:
153 return value
154
155class ExpansionError(Exception):
156 def __init__(self, varname, expression, exception):
157 self.expression = expression
158 self.variablename = varname
159 self.exception = exception
160 if varname:
161 if expression:
162 self.msg = "Failure expanding variable %s, expression was %s which triggered exception %s: %s" % (varname, expression, type(exception).__name__, exception)
163 else:
164 self.msg = "Failure expanding variable %s: %s: %s" % (varname, type(exception).__name__, exception)
165 else:
166 self.msg = "Failure expanding expression %s which triggered exception %s: %s" % (expression, type(exception).__name__, exception)
167 Exception.__init__(self, self.msg)
168 self.args = (varname, expression, exception)
169 def __str__(self):
170 return self.msg
171
172class IncludeHistory(object):
173 def __init__(self, parent = None, filename = '[TOP LEVEL]'):
174 self.parent = parent
175 self.filename = filename
176 self.children = []
177 self.current = self
178
179 def copy(self):
180 new = IncludeHistory(self.parent, self.filename)
181 for c in self.children:
182 new.children.append(c)
183 return new
184
185 def include(self, filename):
186 newfile = IncludeHistory(self.current, filename)
187 self.current.children.append(newfile)
188 self.current = newfile
189 return self
190
191 def __enter__(self):
192 pass
193
194 def __exit__(self, a, b, c):
195 if self.current.parent:
196 self.current = self.current.parent
197 else:
198 bb.warn("Include log: Tried to finish '%s' at top level." % filename)
199 return False
200
201 def emit(self, o, level = 0):
202 """Emit an include history file, and its children."""
203 if level:
204 spaces = " " * (level - 1)
205 o.write("# %s%s" % (spaces, self.filename))
206 if len(self.children) > 0:
207 o.write(" includes:")
208 else:
209 o.write("#\n# INCLUDE HISTORY:\n#")
210 level = level + 1
211 for child in self.children:
212 o.write("\n")
213 child.emit(o, level)
214
215class VariableHistory(object):
216 def __init__(self, dataroot):
217 self.dataroot = dataroot
218 self.variables = COWDictBase.copy()
219
220 def copy(self):
221 new = VariableHistory(self.dataroot)
222 new.variables = self.variables.copy()
223 return new
224
225 def record(self, *kwonly, **loginfo):
226 if not self.dataroot._tracking:
227 return
228 if len(kwonly) > 0:
229 raise TypeError
230 infer_caller_details(loginfo, parent = True)
231 if 'ignore' in loginfo and loginfo['ignore']:
232 return
233 if 'op' not in loginfo or not loginfo['op']:
234 loginfo['op'] = 'set'
235 if 'detail' in loginfo:
236 loginfo['detail'] = str(loginfo['detail'])
237 if 'variable' not in loginfo or 'file' not in loginfo:
238 raise ValueError("record() missing variable or file.")
239 var = loginfo['variable']
240
241 if var not in self.variables:
242 self.variables[var] = []
243 if not isinstance(self.variables[var], list):
244 return
245 if 'nodups' in loginfo and loginfo in self.variables[var]:
246 return
247 self.variables[var].append(loginfo.copy())
248
249 def variable(self, var):
250 if var in self.variables:
251 return self.variables[var]
252 else:
253 return []
254
255 def emit(self, var, oval, val, o, d):
256 history = self.variable(var)
257
258 # Append override history
259 if var in d.overridedata:
260 for (r, override) in d.overridedata[var]:
261 for event in self.variable(r):
262 loginfo = event.copy()
263 if 'flag' in loginfo and not loginfo['flag'].startswith("_"):
264 continue
265 loginfo['variable'] = var
266 loginfo['op'] = 'override[%s]:%s' % (override, loginfo['op'])
267 history.append(loginfo)
268
269 commentVal = re.sub('\n', '\n#', str(oval))
270 if history:
271 if len(history) == 1:
272 o.write("#\n# $%s\n" % var)
273 else:
274 o.write("#\n# $%s [%d operations]\n" % (var, len(history)))
275 for event in history:
276 # o.write("# %s\n" % str(event))
277 if 'func' in event:
278 # If we have a function listed, this is internal
279 # code, not an operation in a config file, and the
280 # full path is distracting.
281 event['file'] = re.sub('.*/', '', event['file'])
282 display_func = ' [%s]' % event['func']
283 else:
284 display_func = ''
285 if 'flag' in event:
286 flag = '[%s] ' % (event['flag'])
287 else:
288 flag = ''
289 o.write("# %s %s:%s%s\n# %s\"%s\"\n" % (event['op'], event['file'], event['line'], display_func, flag, re.sub('\n', '\n# ', event['detail'])))
290 if len(history) > 1:
291 o.write("# pre-expansion value:\n")
292 o.write('# "%s"\n' % (commentVal))
293 else:
294 o.write("#\n# $%s\n# [no history recorded]\n#\n" % var)
295 o.write('# "%s"\n' % (commentVal))
296
297 def get_variable_files(self, var):
298 """Get the files where operations are made on a variable"""
299 var_history = self.variable(var)
300 files = []
301 for event in var_history:
302 files.append(event['file'])
303 return files
304
305 def get_variable_lines(self, var, f):
306 """Get the line where a operation is made on a variable in file f"""
307 var_history = self.variable(var)
308 lines = []
309 for event in var_history:
310 if f== event['file']:
311 line = event['line']
312 lines.append(line)
313 return lines
314
315 def get_variable_items_files(self, var, d):
316 """
317 Use variable history to map items added to a list variable and
318 the files in which they were added.
319 """
320 history = self.variable(var)
321 finalitems = (d.getVar(var, True) or '').split()
322 filemap = {}
323 isset = False
324 for event in history:
325 if 'flag' in event:
326 continue
327 if event['op'] == '_remove':
328 continue
329 if isset and event['op'] == 'set?':
330 continue
331 isset = True
332 items = d.expand(event['detail']).split()
333 for item in items:
334 # This is a little crude but is belt-and-braces to avoid us
335 # having to handle every possible operation type specifically
336 if item in finalitems and not item in filemap:
337 filemap[item] = event['file']
338 return filemap
339
340 def del_var_history(self, var, f=None, line=None):
341 """If file f and line are not given, the entire history of var is deleted"""
342 if var in self.variables:
343 if f and line:
344 self.variables[var] = [ x for x in self.variables[var] if x['file']!=f and x['line']!=line]
345 else:
346 self.variables[var] = []
347
348class DataSmart(MutableMapping):
349 def __init__(self):
350 self.dict = {}
351
352 self.inchistory = IncludeHistory()
353 self.varhistory = VariableHistory(self)
354 self._tracking = False
355
356 self.expand_cache = {}
357
358 # cookie monster tribute
359 # Need to be careful about writes to overridedata as
360 # its only a shallow copy, could influence other data store
361 # copies!
362 self.overridedata = {}
363 self.overrides = None
364 self.overridevars = set(["OVERRIDES", "FILE"])
365 self.inoverride = False
366
367 def enableTracking(self):
368 self._tracking = True
369
370 def disableTracking(self):
371 self._tracking = False
372
373 def expandWithRefs(self, s, varname):
374
375 if not isinstance(s, basestring): # sanity check
376 return VariableParse(varname, self, s)
377
378 if varname and varname in self.expand_cache:
379 return self.expand_cache[varname]
380
381 varparse = VariableParse(varname, self)
382
383 while s.find('${') != -1:
384 olds = s
385 try:
386 s = __expand_var_regexp__.sub(varparse.var_sub, s)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500387 try:
388 s = __expand_python_regexp__.sub(varparse.python_sub, s)
389 except SyntaxError as e:
390 # Likely unmatched brackets, just don't expand the expression
391 if e.msg != "EOL while scanning string literal":
392 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500393 if s == olds:
394 break
395 except ExpansionError:
396 raise
397 except bb.parse.SkipRecipe:
398 raise
399 except Exception as exc:
400 exc_class, exc, tb = sys.exc_info()
401 raise ExpansionError, ExpansionError(varname, s, exc), tb
402
403 varparse.value = s
404
405 if varname:
406 self.expand_cache[varname] = varparse
407
408 return varparse
409
410 def expand(self, s, varname = None):
411 return self.expandWithRefs(s, varname).value
412
413 def finalize(self, parent = False):
414 return
415
416 def internal_finalize(self, parent = False):
417 """Performs final steps upon the datastore, including application of overrides"""
418 self.overrides = None
419
420 def need_overrides(self):
Patrick Williamsd7e96312015-09-22 08:09:05 -0500421 if self.overrides is not None:
422 return
423 if self.inoverride:
424 return
425 for count in range(5):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500426 self.inoverride = True
427 # Can end up here recursively so setup dummy values
428 self.overrides = []
429 self.overridesset = set()
430 self.overrides = (self.getVar("OVERRIDES", True) or "").split(":") or []
431 self.overridesset = set(self.overrides)
432 self.inoverride = False
433 self.expand_cache = {}
Patrick Williamsd7e96312015-09-22 08:09:05 -0500434 newoverrides = (self.getVar("OVERRIDES", True) or "").split(":") or []
435 if newoverrides == self.overrides:
436 break
437 self.overrides = newoverrides
438 self.overridesset = set(self.overrides)
439 else:
440 bb.fatal("Overrides could not be expanded into a stable state after 5 iterations, overrides must be being referenced by other overridden variables in some recursive fashion. Please provide your configuration to bitbake-devel so we can laugh, er, I mean try and understand how to make it work.")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500441
442 def initVar(self, var):
443 self.expand_cache = {}
444 if not var in self.dict:
445 self.dict[var] = {}
446
447 def _findVar(self, var):
448 dest = self.dict
449 while dest:
450 if var in dest:
451 return dest[var]
452
453 if "_data" not in dest:
454 break
455 dest = dest["_data"]
456
457 def _makeShadowCopy(self, var):
458 if var in self.dict:
459 return
460
461 local_var = self._findVar(var)
462
463 if local_var:
464 self.dict[var] = copy.copy(local_var)
465 else:
466 self.initVar(var)
467
468
469 def setVar(self, var, value, **loginfo):
470 #print("var=" + str(var) + " val=" + str(value))
471 parsing=False
472 if 'parsing' in loginfo:
473 parsing=True
474
475 if 'op' not in loginfo:
476 loginfo['op'] = "set"
477 self.expand_cache = {}
478 match = __setvar_regexp__.match(var)
479 if match and match.group("keyword") in __setvar_keyword__:
480 base = match.group('base')
481 keyword = match.group("keyword")
482 override = match.group('add')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500483 l = self.getVarFlag(base, keyword, False) or []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500484 l.append([value, override])
485 self.setVarFlag(base, keyword, l, ignore=True)
486 # And cause that to be recorded:
487 loginfo['detail'] = value
488 loginfo['variable'] = base
489 if override:
490 loginfo['op'] = '%s[%s]' % (keyword, override)
491 else:
492 loginfo['op'] = keyword
493 self.varhistory.record(**loginfo)
494 # todo make sure keyword is not __doc__ or __module__
495 # pay the cookie monster
496
497 # more cookies for the cookie monster
498 if '_' in var:
499 self._setvar_update_overrides(base, **loginfo)
500
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500501 if base in self.overridevars:
Patrick Williamsd7e96312015-09-22 08:09:05 -0500502 self._setvar_update_overridevars(var, value)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500503 return
504
505 if not var in self.dict:
506 self._makeShadowCopy(var)
507
508 if not parsing:
509 if "_append" in self.dict[var]:
510 del self.dict[var]["_append"]
511 if "_prepend" in self.dict[var]:
512 del self.dict[var]["_prepend"]
513 if var in self.overridedata:
514 active = []
515 self.need_overrides()
516 for (r, o) in self.overridedata[var]:
517 if o in self.overridesset:
518 active.append(r)
519 elif "_" in o:
520 if set(o.split("_")).issubset(self.overridesset):
521 active.append(r)
522 for a in active:
523 self.delVar(a)
524 del self.overridedata[var]
525
526 # more cookies for the cookie monster
527 if '_' in var:
528 self._setvar_update_overrides(var, **loginfo)
529
530 # setting var
531 self.dict[var]["_content"] = value
532 self.varhistory.record(**loginfo)
533
534 if var in self.overridevars:
Patrick Williamsd7e96312015-09-22 08:09:05 -0500535 self._setvar_update_overridevars(var, value)
536
537 def _setvar_update_overridevars(self, var, value):
538 vardata = self.expandWithRefs(value, var)
539 new = vardata.references
540 new.update(vardata.contains.keys())
541 while not new.issubset(self.overridevars):
542 nextnew = set()
543 self.overridevars.update(new)
544 for i in new:
545 vardata = self.expandWithRefs(self.getVar(i, True), i)
546 nextnew.update(vardata.references)
547 nextnew.update(vardata.contains.keys())
548 new = nextnew
549 self.internal_finalize(True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500550
551 def _setvar_update_overrides(self, var, **loginfo):
552 # aka pay the cookie monster
553 override = var[var.rfind('_')+1:]
554 shortvar = var[:var.rfind('_')]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500555 while override and override.islower():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500556 if shortvar not in self.overridedata:
557 self.overridedata[shortvar] = []
558 if [var, override] not in self.overridedata[shortvar]:
559 # Force CoW by recreating the list first
560 self.overridedata[shortvar] = list(self.overridedata[shortvar])
561 self.overridedata[shortvar].append([var, override])
562 override = None
563 if "_" in shortvar:
564 override = var[shortvar.rfind('_')+1:]
565 shortvar = var[:shortvar.rfind('_')]
566 if len(shortvar) == 0:
567 override = None
568
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500569 def getVar(self, var, expand, noweakdefault=False, parsing=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500570 return self.getVarFlag(var, "_content", expand, noweakdefault, parsing)
571
572 def renameVar(self, key, newkey, **loginfo):
573 """
574 Rename the variable key to newkey
575 """
576 val = self.getVar(key, 0, parsing=True)
577 if val is not None:
578 loginfo['variable'] = newkey
579 loginfo['op'] = 'rename from %s' % key
580 loginfo['detail'] = val
581 self.varhistory.record(**loginfo)
582 self.setVar(newkey, val, ignore=True, parsing=True)
583
584 for i in (__setvar_keyword__):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500585 src = self.getVarFlag(key, i, False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500586 if src is None:
587 continue
588
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500589 dest = self.getVarFlag(newkey, i, False) or []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500590 dest.extend(src)
591 self.setVarFlag(newkey, i, dest, ignore=True)
592
593 if key in self.overridedata:
594 self.overridedata[newkey] = []
595 for (v, o) in self.overridedata[key]:
596 self.overridedata[newkey].append([v.replace(key, newkey), o])
597 self.renameVar(v, v.replace(key, newkey))
598
599 if '_' in newkey and val is None:
600 self._setvar_update_overrides(newkey, **loginfo)
601
602 loginfo['variable'] = key
603 loginfo['op'] = 'rename (to)'
604 loginfo['detail'] = newkey
605 self.varhistory.record(**loginfo)
606 self.delVar(key, ignore=True)
607
608 def appendVar(self, var, value, **loginfo):
609 loginfo['op'] = 'append'
610 self.varhistory.record(**loginfo)
611 self.setVar(var + "_append", value, ignore=True, parsing=True)
612
613 def prependVar(self, var, value, **loginfo):
614 loginfo['op'] = 'prepend'
615 self.varhistory.record(**loginfo)
616 self.setVar(var + "_prepend", value, ignore=True, parsing=True)
617
618 def delVar(self, var, **loginfo):
619 loginfo['detail'] = ""
620 loginfo['op'] = 'del'
621 self.varhistory.record(**loginfo)
622 self.expand_cache = {}
623 self.dict[var] = {}
624 if var in self.overridedata:
625 del self.overridedata[var]
626 if '_' in var:
627 override = var[var.rfind('_')+1:]
628 shortvar = var[:var.rfind('_')]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500629 while override and override.islower():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500630 try:
631 if shortvar in self.overridedata:
632 # Force CoW by recreating the list first
633 self.overridedata[shortvar] = list(self.overridedata[shortvar])
634 self.overridedata[shortvar].remove([var, override])
635 except ValueError as e:
636 pass
637 override = None
638 if "_" in shortvar:
639 override = var[shortvar.rfind('_')+1:]
640 shortvar = var[:shortvar.rfind('_')]
641 if len(shortvar) == 0:
642 override = None
643
644 def setVarFlag(self, var, flag, value, **loginfo):
645 self.expand_cache = {}
646 if 'op' not in loginfo:
647 loginfo['op'] = "set"
648 loginfo['flag'] = flag
649 self.varhistory.record(**loginfo)
650 if not var in self.dict:
651 self._makeShadowCopy(var)
652 self.dict[var][flag] = value
653
654 if flag == "_defaultval" and '_' in var:
655 self._setvar_update_overrides(var, **loginfo)
Patrick Williamsd7e96312015-09-22 08:09:05 -0500656 if flag == "_defaultval" and var in self.overridevars:
657 self._setvar_update_overridevars(var, value)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500658
659 if flag == "unexport" or flag == "export":
660 if not "__exportlist" in self.dict:
661 self._makeShadowCopy("__exportlist")
662 if not "_content" in self.dict["__exportlist"]:
663 self.dict["__exportlist"]["_content"] = set()
664 self.dict["__exportlist"]["_content"].add(var)
665
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500666 def getVarFlag(self, var, flag, expand, noweakdefault=False, parsing=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500667 local_var = self._findVar(var)
668 value = None
669 if flag == "_content" and var in self.overridedata and not parsing:
670 match = False
671 active = {}
672 self.need_overrides()
673 for (r, o) in self.overridedata[var]:
674 # What about double overrides both with "_" in the name?
675 if o in self.overridesset:
676 active[o] = r
677 elif "_" in o:
678 if set(o.split("_")).issubset(self.overridesset):
679 active[o] = r
680
681 mod = True
682 while mod:
683 mod = False
684 for o in self.overrides:
685 for a in active.copy():
686 if a.endswith("_" + o):
687 t = active[a]
688 del active[a]
689 active[a.replace("_" + o, "")] = t
690 mod = True
691 elif a == o:
692 match = active[a]
693 del active[a]
694 if match:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500695 value = self.getVar(match, False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500696
697 if local_var is not None and value is None:
698 if flag in local_var:
699 value = copy.copy(local_var[flag])
700 elif flag == "_content" and "_defaultval" in local_var and not noweakdefault:
701 value = copy.copy(local_var["_defaultval"])
702
703
704 if flag == "_content" and local_var is not None and "_append" in local_var and not parsing:
705 if not value:
706 value = ""
707 self.need_overrides()
708 for (r, o) in local_var["_append"]:
709 match = True
710 if o:
711 for o2 in o.split("_"):
712 if not o2 in self.overrides:
713 match = False
714 if match:
715 value = value + r
716
717 if flag == "_content" and local_var is not None and "_prepend" in local_var and not parsing:
718 if not value:
719 value = ""
720 self.need_overrides()
721 for (r, o) in local_var["_prepend"]:
722
723 match = True
724 if o:
725 for o2 in o.split("_"):
726 if not o2 in self.overrides:
727 match = False
728 if match:
729 value = r + value
730
731 if expand and value:
732 # Only getvar (flag == _content) hits the expand cache
733 cachename = None
734 if flag == "_content":
735 cachename = var
736 else:
737 cachename = var + "[" + flag + "]"
738 value = self.expand(value, cachename)
739
740 if value and flag == "_content" and local_var is not None and "_remove" in local_var:
741 removes = []
742 self.need_overrides()
743 for (r, o) in local_var["_remove"]:
744 match = True
745 if o:
746 for o2 in o.split("_"):
747 if not o2 in self.overrides:
748 match = False
749 if match:
750 removes.extend(self.expand(r).split())
751
752 filtered = filter(lambda v: v not in removes,
753 value.split())
754 value = " ".join(filtered)
755 if expand and var in self.expand_cache:
756 # We need to ensure the expand cache has the correct value
757 # flag == "_content" here
758 self.expand_cache[var].value = value
759 return value
760
761 def delVarFlag(self, var, flag, **loginfo):
762 self.expand_cache = {}
763 local_var = self._findVar(var)
764 if not local_var:
765 return
766 if not var in self.dict:
767 self._makeShadowCopy(var)
768
769 if var in self.dict and flag in self.dict[var]:
770 loginfo['detail'] = ""
771 loginfo['op'] = 'delFlag'
772 loginfo['flag'] = flag
773 self.varhistory.record(**loginfo)
774
775 del self.dict[var][flag]
776
777 def appendVarFlag(self, var, flag, value, **loginfo):
778 loginfo['op'] = 'append'
779 loginfo['flag'] = flag
780 self.varhistory.record(**loginfo)
781 newvalue = (self.getVarFlag(var, flag, False) or "") + value
782 self.setVarFlag(var, flag, newvalue, ignore=True)
783
784 def prependVarFlag(self, var, flag, value, **loginfo):
785 loginfo['op'] = 'prepend'
786 loginfo['flag'] = flag
787 self.varhistory.record(**loginfo)
788 newvalue = value + (self.getVarFlag(var, flag, False) or "")
789 self.setVarFlag(var, flag, newvalue, ignore=True)
790
791 def setVarFlags(self, var, flags, **loginfo):
792 self.expand_cache = {}
793 infer_caller_details(loginfo)
794 if not var in self.dict:
795 self._makeShadowCopy(var)
796
797 for i in flags:
798 if i == "_content":
799 continue
800 loginfo['flag'] = i
801 loginfo['detail'] = flags[i]
802 self.varhistory.record(**loginfo)
803 self.dict[var][i] = flags[i]
804
805 def getVarFlags(self, var, expand = False, internalflags=False):
806 local_var = self._findVar(var)
807 flags = {}
808
809 if local_var:
810 for i in local_var:
811 if i.startswith("_") and not internalflags:
812 continue
813 flags[i] = local_var[i]
814 if expand and i in expand:
815 flags[i] = self.expand(flags[i], var + "[" + i + "]")
816 if len(flags) == 0:
817 return None
818 return flags
819
820
821 def delVarFlags(self, var, **loginfo):
822 self.expand_cache = {}
823 if not var in self.dict:
824 self._makeShadowCopy(var)
825
826 if var in self.dict:
827 content = None
828
829 loginfo['op'] = 'delete flags'
830 self.varhistory.record(**loginfo)
831
832 # try to save the content
833 if "_content" in self.dict[var]:
834 content = self.dict[var]["_content"]
835 self.dict[var] = {}
836 self.dict[var]["_content"] = content
837 else:
838 del self.dict[var]
839
840 def createCopy(self):
841 """
842 Create a copy of self by setting _data to self
843 """
844 # we really want this to be a DataSmart...
845 data = DataSmart()
846 data.dict["_data"] = self.dict
847 data.varhistory = self.varhistory.copy()
848 data.varhistory.datasmart = data
849 data.inchistory = self.inchistory.copy()
850
851 data._tracking = self._tracking
852
853 data.overrides = None
854 data.overridevars = copy.copy(self.overridevars)
855 # Should really be a deepcopy but has heavy overhead.
856 # Instead, we're careful with writes.
857 data.overridedata = copy.copy(self.overridedata)
858
859 return data
860
861 def expandVarref(self, variable, parents=False):
862 """Find all references to variable in the data and expand it
863 in place, optionally descending to parent datastores."""
864
865 if parents:
866 keys = iter(self)
867 else:
868 keys = self.localkeys()
869
870 ref = '${%s}' % variable
871 value = self.getVar(variable, False)
872 for key in keys:
873 referrervalue = self.getVar(key, False)
874 if referrervalue and ref in referrervalue:
875 self.setVar(key, referrervalue.replace(ref, value))
876
877 def localkeys(self):
878 for key in self.dict:
879 if key != '_data':
880 yield key
881
882 def __iter__(self):
883 deleted = set()
884 overrides = set()
885 def keylist(d):
886 klist = set()
887 for key in d:
888 if key == "_data":
889 continue
890 if key in deleted:
891 continue
892 if key in overrides:
893 continue
894 if not d[key]:
895 deleted.add(key)
896 continue
897 klist.add(key)
898
899 if "_data" in d:
900 klist |= keylist(d["_data"])
901
902 return klist
903
904 self.need_overrides()
905 for var in self.overridedata:
906 for (r, o) in self.overridedata[var]:
907 if o in self.overridesset:
908 overrides.add(var)
909 elif "_" in o:
910 if set(o.split("_")).issubset(self.overridesset):
911 overrides.add(var)
912
913 for k in keylist(self.dict):
914 yield k
915
916 for k in overrides:
917 yield k
918
919 def __len__(self):
920 return len(frozenset(self))
921
922 def __getitem__(self, item):
923 value = self.getVar(item, False)
924 if value is None:
925 raise KeyError(item)
926 else:
927 return value
928
929 def __setitem__(self, var, value):
930 self.setVar(var, value)
931
932 def __delitem__(self, var):
933 self.delVar(var)
934
935 def get_hash(self):
936 data = {}
937 d = self.createCopy()
938 bb.data.expandKeys(d)
939 bb.data.update_data(d)
940
941 config_whitelist = set((d.getVar("BB_HASHCONFIG_WHITELIST", True) or "").split())
942 keys = set(key for key in iter(d) if not key.startswith("__"))
943 for key in keys:
944 if key in config_whitelist:
945 continue
946
947 value = d.getVar(key, False) or ""
948 data.update({key:value})
949
950 varflags = d.getVarFlags(key, internalflags = True)
951 if not varflags:
952 continue
953 for f in varflags:
954 if f == "_content":
955 continue
956 data.update({'%s[%s]' % (key, f):varflags[f]})
957
958 for key in ["__BBTASKS", "__BBANONFUNCS", "__BBHANDLERS"]:
959 bb_list = d.getVar(key, False) or []
960 bb_list.sort()
961 data.update({key:str(bb_list)})
962
963 if key == "__BBANONFUNCS":
964 for i in bb_list:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500965 value = d.getVar(i, False) or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500966 data.update({i:value})
967
968 data_str = str([(k, data[k]) for k in sorted(data.keys())])
969 return hashlib.md5(data_str).hexdigest()