blob: 79b4ed932976ebbddd3ec635bffbf443312ae31f [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>.*))?$')
43__expand_var_regexp__ = re.compile(r"\${[^{}@\n\t ]+}")
44__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)
150 if value is None or self.metadata.getVarFlag(key, 'func'):
151 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)
387 s = __expand_python_regexp__.sub(varparse.python_sub, s)
388 if s == olds:
389 break
390 except ExpansionError:
391 raise
392 except bb.parse.SkipRecipe:
393 raise
394 except Exception as exc:
395 exc_class, exc, tb = sys.exc_info()
396 raise ExpansionError, ExpansionError(varname, s, exc), tb
397
398 varparse.value = s
399
400 if varname:
401 self.expand_cache[varname] = varparse
402
403 return varparse
404
405 def expand(self, s, varname = None):
406 return self.expandWithRefs(s, varname).value
407
408 def finalize(self, parent = False):
409 return
410
411 def internal_finalize(self, parent = False):
412 """Performs final steps upon the datastore, including application of overrides"""
413 self.overrides = None
414
415 def need_overrides(self):
416 if self.overrides is None:
417 if self.inoverride:
418 return
419 self.inoverride = True
420 # Can end up here recursively so setup dummy values
421 self.overrides = []
422 self.overridesset = set()
423 self.overrides = (self.getVar("OVERRIDES", True) or "").split(":") or []
424 self.overridesset = set(self.overrides)
425 self.inoverride = False
426 self.expand_cache = {}
427
428 def initVar(self, var):
429 self.expand_cache = {}
430 if not var in self.dict:
431 self.dict[var] = {}
432
433 def _findVar(self, var):
434 dest = self.dict
435 while dest:
436 if var in dest:
437 return dest[var]
438
439 if "_data" not in dest:
440 break
441 dest = dest["_data"]
442
443 def _makeShadowCopy(self, var):
444 if var in self.dict:
445 return
446
447 local_var = self._findVar(var)
448
449 if local_var:
450 self.dict[var] = copy.copy(local_var)
451 else:
452 self.initVar(var)
453
454
455 def setVar(self, var, value, **loginfo):
456 #print("var=" + str(var) + " val=" + str(value))
457 parsing=False
458 if 'parsing' in loginfo:
459 parsing=True
460
461 if 'op' not in loginfo:
462 loginfo['op'] = "set"
463 self.expand_cache = {}
464 match = __setvar_regexp__.match(var)
465 if match and match.group("keyword") in __setvar_keyword__:
466 base = match.group('base')
467 keyword = match.group("keyword")
468 override = match.group('add')
469 l = self.getVarFlag(base, keyword) or []
470 l.append([value, override])
471 self.setVarFlag(base, keyword, l, ignore=True)
472 # And cause that to be recorded:
473 loginfo['detail'] = value
474 loginfo['variable'] = base
475 if override:
476 loginfo['op'] = '%s[%s]' % (keyword, override)
477 else:
478 loginfo['op'] = keyword
479 self.varhistory.record(**loginfo)
480 # todo make sure keyword is not __doc__ or __module__
481 # pay the cookie monster
482
483 # more cookies for the cookie monster
484 if '_' in var:
485 self._setvar_update_overrides(base, **loginfo)
486
487
488 if base in self.overridevars:
489 self.overridevars.update(self.expandWithRefs(value, var).references)
490 self.internal_finalize(True)
491 return
492
493 if not var in self.dict:
494 self._makeShadowCopy(var)
495
496 if not parsing:
497 if "_append" in self.dict[var]:
498 del self.dict[var]["_append"]
499 if "_prepend" in self.dict[var]:
500 del self.dict[var]["_prepend"]
501 if var in self.overridedata:
502 active = []
503 self.need_overrides()
504 for (r, o) in self.overridedata[var]:
505 if o in self.overridesset:
506 active.append(r)
507 elif "_" in o:
508 if set(o.split("_")).issubset(self.overridesset):
509 active.append(r)
510 for a in active:
511 self.delVar(a)
512 del self.overridedata[var]
513
514 # more cookies for the cookie monster
515 if '_' in var:
516 self._setvar_update_overrides(var, **loginfo)
517
518 # setting var
519 self.dict[var]["_content"] = value
520 self.varhistory.record(**loginfo)
521
522 if var in self.overridevars:
523 self.overridevars.update(self.expandWithRefs(value, var).references)
524 self.internal_finalize(True)
525
526 def _setvar_update_overrides(self, var, **loginfo):
527 # aka pay the cookie monster
528 override = var[var.rfind('_')+1:]
529 shortvar = var[:var.rfind('_')]
530 while override:
531 if shortvar not in self.overridedata:
532 self.overridedata[shortvar] = []
533 if [var, override] not in self.overridedata[shortvar]:
534 # Force CoW by recreating the list first
535 self.overridedata[shortvar] = list(self.overridedata[shortvar])
536 self.overridedata[shortvar].append([var, override])
537 override = None
538 if "_" in shortvar:
539 override = var[shortvar.rfind('_')+1:]
540 shortvar = var[:shortvar.rfind('_')]
541 if len(shortvar) == 0:
542 override = None
543
544 def getVar(self, var, expand=False, noweakdefault=False, parsing=False):
545 return self.getVarFlag(var, "_content", expand, noweakdefault, parsing)
546
547 def renameVar(self, key, newkey, **loginfo):
548 """
549 Rename the variable key to newkey
550 """
551 val = self.getVar(key, 0, parsing=True)
552 if val is not None:
553 loginfo['variable'] = newkey
554 loginfo['op'] = 'rename from %s' % key
555 loginfo['detail'] = val
556 self.varhistory.record(**loginfo)
557 self.setVar(newkey, val, ignore=True, parsing=True)
558
559 for i in (__setvar_keyword__):
560 src = self.getVarFlag(key, i)
561 if src is None:
562 continue
563
564 dest = self.getVarFlag(newkey, i) or []
565 dest.extend(src)
566 self.setVarFlag(newkey, i, dest, ignore=True)
567
568 if key in self.overridedata:
569 self.overridedata[newkey] = []
570 for (v, o) in self.overridedata[key]:
571 self.overridedata[newkey].append([v.replace(key, newkey), o])
572 self.renameVar(v, v.replace(key, newkey))
573
574 if '_' in newkey and val is None:
575 self._setvar_update_overrides(newkey, **loginfo)
576
577 loginfo['variable'] = key
578 loginfo['op'] = 'rename (to)'
579 loginfo['detail'] = newkey
580 self.varhistory.record(**loginfo)
581 self.delVar(key, ignore=True)
582
583 def appendVar(self, var, value, **loginfo):
584 loginfo['op'] = 'append'
585 self.varhistory.record(**loginfo)
586 self.setVar(var + "_append", value, ignore=True, parsing=True)
587
588 def prependVar(self, var, value, **loginfo):
589 loginfo['op'] = 'prepend'
590 self.varhistory.record(**loginfo)
591 self.setVar(var + "_prepend", value, ignore=True, parsing=True)
592
593 def delVar(self, var, **loginfo):
594 loginfo['detail'] = ""
595 loginfo['op'] = 'del'
596 self.varhistory.record(**loginfo)
597 self.expand_cache = {}
598 self.dict[var] = {}
599 if var in self.overridedata:
600 del self.overridedata[var]
601 if '_' in var:
602 override = var[var.rfind('_')+1:]
603 shortvar = var[:var.rfind('_')]
604 while override:
605 try:
606 if shortvar in self.overridedata:
607 # Force CoW by recreating the list first
608 self.overridedata[shortvar] = list(self.overridedata[shortvar])
609 self.overridedata[shortvar].remove([var, override])
610 except ValueError as e:
611 pass
612 override = None
613 if "_" in shortvar:
614 override = var[shortvar.rfind('_')+1:]
615 shortvar = var[:shortvar.rfind('_')]
616 if len(shortvar) == 0:
617 override = None
618
619 def setVarFlag(self, var, flag, value, **loginfo):
620 self.expand_cache = {}
621 if 'op' not in loginfo:
622 loginfo['op'] = "set"
623 loginfo['flag'] = flag
624 self.varhistory.record(**loginfo)
625 if not var in self.dict:
626 self._makeShadowCopy(var)
627 self.dict[var][flag] = value
628
629 if flag == "_defaultval" and '_' in var:
630 self._setvar_update_overrides(var, **loginfo)
631
632 if flag == "unexport" or flag == "export":
633 if not "__exportlist" in self.dict:
634 self._makeShadowCopy("__exportlist")
635 if not "_content" in self.dict["__exportlist"]:
636 self.dict["__exportlist"]["_content"] = set()
637 self.dict["__exportlist"]["_content"].add(var)
638
639 def getVarFlag(self, var, flag, expand=False, noweakdefault=False, parsing=False):
640 local_var = self._findVar(var)
641 value = None
642 if flag == "_content" and var in self.overridedata and not parsing:
643 match = False
644 active = {}
645 self.need_overrides()
646 for (r, o) in self.overridedata[var]:
647 # What about double overrides both with "_" in the name?
648 if o in self.overridesset:
649 active[o] = r
650 elif "_" in o:
651 if set(o.split("_")).issubset(self.overridesset):
652 active[o] = r
653
654 mod = True
655 while mod:
656 mod = False
657 for o in self.overrides:
658 for a in active.copy():
659 if a.endswith("_" + o):
660 t = active[a]
661 del active[a]
662 active[a.replace("_" + o, "")] = t
663 mod = True
664 elif a == o:
665 match = active[a]
666 del active[a]
667 if match:
668 value = self.getVar(match)
669
670 if local_var is not None and value is None:
671 if flag in local_var:
672 value = copy.copy(local_var[flag])
673 elif flag == "_content" and "_defaultval" in local_var and not noweakdefault:
674 value = copy.copy(local_var["_defaultval"])
675
676
677 if flag == "_content" and local_var is not None and "_append" in local_var and not parsing:
678 if not value:
679 value = ""
680 self.need_overrides()
681 for (r, o) in local_var["_append"]:
682 match = True
683 if o:
684 for o2 in o.split("_"):
685 if not o2 in self.overrides:
686 match = False
687 if match:
688 value = value + r
689
690 if flag == "_content" and local_var is not None and "_prepend" in local_var and not parsing:
691 if not value:
692 value = ""
693 self.need_overrides()
694 for (r, o) in local_var["_prepend"]:
695
696 match = True
697 if o:
698 for o2 in o.split("_"):
699 if not o2 in self.overrides:
700 match = False
701 if match:
702 value = r + value
703
704 if expand and value:
705 # Only getvar (flag == _content) hits the expand cache
706 cachename = None
707 if flag == "_content":
708 cachename = var
709 else:
710 cachename = var + "[" + flag + "]"
711 value = self.expand(value, cachename)
712
713 if value and flag == "_content" and local_var is not None and "_remove" in local_var:
714 removes = []
715 self.need_overrides()
716 for (r, o) in local_var["_remove"]:
717 match = True
718 if o:
719 for o2 in o.split("_"):
720 if not o2 in self.overrides:
721 match = False
722 if match:
723 removes.extend(self.expand(r).split())
724
725 filtered = filter(lambda v: v not in removes,
726 value.split())
727 value = " ".join(filtered)
728 if expand and var in self.expand_cache:
729 # We need to ensure the expand cache has the correct value
730 # flag == "_content" here
731 self.expand_cache[var].value = value
732 return value
733
734 def delVarFlag(self, var, flag, **loginfo):
735 self.expand_cache = {}
736 local_var = self._findVar(var)
737 if not local_var:
738 return
739 if not var in self.dict:
740 self._makeShadowCopy(var)
741
742 if var in self.dict and flag in self.dict[var]:
743 loginfo['detail'] = ""
744 loginfo['op'] = 'delFlag'
745 loginfo['flag'] = flag
746 self.varhistory.record(**loginfo)
747
748 del self.dict[var][flag]
749
750 def appendVarFlag(self, var, flag, value, **loginfo):
751 loginfo['op'] = 'append'
752 loginfo['flag'] = flag
753 self.varhistory.record(**loginfo)
754 newvalue = (self.getVarFlag(var, flag, False) or "") + value
755 self.setVarFlag(var, flag, newvalue, ignore=True)
756
757 def prependVarFlag(self, var, flag, value, **loginfo):
758 loginfo['op'] = 'prepend'
759 loginfo['flag'] = flag
760 self.varhistory.record(**loginfo)
761 newvalue = value + (self.getVarFlag(var, flag, False) or "")
762 self.setVarFlag(var, flag, newvalue, ignore=True)
763
764 def setVarFlags(self, var, flags, **loginfo):
765 self.expand_cache = {}
766 infer_caller_details(loginfo)
767 if not var in self.dict:
768 self._makeShadowCopy(var)
769
770 for i in flags:
771 if i == "_content":
772 continue
773 loginfo['flag'] = i
774 loginfo['detail'] = flags[i]
775 self.varhistory.record(**loginfo)
776 self.dict[var][i] = flags[i]
777
778 def getVarFlags(self, var, expand = False, internalflags=False):
779 local_var = self._findVar(var)
780 flags = {}
781
782 if local_var:
783 for i in local_var:
784 if i.startswith("_") and not internalflags:
785 continue
786 flags[i] = local_var[i]
787 if expand and i in expand:
788 flags[i] = self.expand(flags[i], var + "[" + i + "]")
789 if len(flags) == 0:
790 return None
791 return flags
792
793
794 def delVarFlags(self, var, **loginfo):
795 self.expand_cache = {}
796 if not var in self.dict:
797 self._makeShadowCopy(var)
798
799 if var in self.dict:
800 content = None
801
802 loginfo['op'] = 'delete flags'
803 self.varhistory.record(**loginfo)
804
805 # try to save the content
806 if "_content" in self.dict[var]:
807 content = self.dict[var]["_content"]
808 self.dict[var] = {}
809 self.dict[var]["_content"] = content
810 else:
811 del self.dict[var]
812
813 def createCopy(self):
814 """
815 Create a copy of self by setting _data to self
816 """
817 # we really want this to be a DataSmart...
818 data = DataSmart()
819 data.dict["_data"] = self.dict
820 data.varhistory = self.varhistory.copy()
821 data.varhistory.datasmart = data
822 data.inchistory = self.inchistory.copy()
823
824 data._tracking = self._tracking
825
826 data.overrides = None
827 data.overridevars = copy.copy(self.overridevars)
828 # Should really be a deepcopy but has heavy overhead.
829 # Instead, we're careful with writes.
830 data.overridedata = copy.copy(self.overridedata)
831
832 return data
833
834 def expandVarref(self, variable, parents=False):
835 """Find all references to variable in the data and expand it
836 in place, optionally descending to parent datastores."""
837
838 if parents:
839 keys = iter(self)
840 else:
841 keys = self.localkeys()
842
843 ref = '${%s}' % variable
844 value = self.getVar(variable, False)
845 for key in keys:
846 referrervalue = self.getVar(key, False)
847 if referrervalue and ref in referrervalue:
848 self.setVar(key, referrervalue.replace(ref, value))
849
850 def localkeys(self):
851 for key in self.dict:
852 if key != '_data':
853 yield key
854
855 def __iter__(self):
856 deleted = set()
857 overrides = set()
858 def keylist(d):
859 klist = set()
860 for key in d:
861 if key == "_data":
862 continue
863 if key in deleted:
864 continue
865 if key in overrides:
866 continue
867 if not d[key]:
868 deleted.add(key)
869 continue
870 klist.add(key)
871
872 if "_data" in d:
873 klist |= keylist(d["_data"])
874
875 return klist
876
877 self.need_overrides()
878 for var in self.overridedata:
879 for (r, o) in self.overridedata[var]:
880 if o in self.overridesset:
881 overrides.add(var)
882 elif "_" in o:
883 if set(o.split("_")).issubset(self.overridesset):
884 overrides.add(var)
885
886 for k in keylist(self.dict):
887 yield k
888
889 for k in overrides:
890 yield k
891
892 def __len__(self):
893 return len(frozenset(self))
894
895 def __getitem__(self, item):
896 value = self.getVar(item, False)
897 if value is None:
898 raise KeyError(item)
899 else:
900 return value
901
902 def __setitem__(self, var, value):
903 self.setVar(var, value)
904
905 def __delitem__(self, var):
906 self.delVar(var)
907
908 def get_hash(self):
909 data = {}
910 d = self.createCopy()
911 bb.data.expandKeys(d)
912 bb.data.update_data(d)
913
914 config_whitelist = set((d.getVar("BB_HASHCONFIG_WHITELIST", True) or "").split())
915 keys = set(key for key in iter(d) if not key.startswith("__"))
916 for key in keys:
917 if key in config_whitelist:
918 continue
919
920 value = d.getVar(key, False) or ""
921 data.update({key:value})
922
923 varflags = d.getVarFlags(key, internalflags = True)
924 if not varflags:
925 continue
926 for f in varflags:
927 if f == "_content":
928 continue
929 data.update({'%s[%s]' % (key, f):varflags[f]})
930
931 for key in ["__BBTASKS", "__BBANONFUNCS", "__BBHANDLERS"]:
932 bb_list = d.getVar(key, False) or []
933 bb_list.sort()
934 data.update({key:str(bb_list)})
935
936 if key == "__BBANONFUNCS":
937 for i in bb_list:
938 value = d.getVar(i, True) or ""
939 data.update({i:value})
940
941 data_str = str([(k, data[k]) for k in sorted(data.keys())])
942 return hashlib.md5(data_str).hexdigest()