summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/data_smart.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/bb/data_smart.py')
-rw-r--r--bitbake/lib/bb/data_smart.py804
1 files changed, 804 insertions, 0 deletions
diff --git a/bitbake/lib/bb/data_smart.py b/bitbake/lib/bb/data_smart.py
new file mode 100644
index 0000000..e4bdb2f
--- /dev/null
+++ b/bitbake/lib/bb/data_smart.py
@@ -0,0 +1,804 @@
1# 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 if varval and ('variable' not in loginfo or 'detail' not in loginfo):
58 try:
59 raise Exception
60 except Exception:
61 tb = sys.exc_info()[2]
62 if parent:
63 above = tb.tb_frame.f_back.f_back
64 else:
65 above = tb.tb_frame.f_back
66 lcls = above.f_locals.items()
67 for k, v in lcls:
68 if k == 'value' and 'detail' not in loginfo:
69 loginfo['detail'] = v
70 if k == 'var' and 'variable' not in loginfo:
71 loginfo['variable'] = v
72 # Infer file/line/function from traceback
73 if 'file' not in loginfo:
74 depth = 3
75 if parent:
76 depth = 4
77 file, line, func, text = traceback.extract_stack(limit = depth)[0]
78 loginfo['file'] = file
79 loginfo['line'] = line
80 if func not in loginfo:
81 loginfo['func'] = func
82
83class VariableParse:
84 def __init__(self, varname, d, val = None):
85 self.varname = varname
86 self.d = d
87 self.value = val
88
89 self.references = set()
90 self.execs = set()
91 self.contains = {}
92
93 def var_sub(self, match):
94 key = match.group()[2:-1]
95 if self.varname and key:
96 if self.varname == key:
97 raise Exception("variable %s references itself!" % self.varname)
98 if key in self.d.expand_cache:
99 varparse = self.d.expand_cache[key]
100 var = varparse.value
101 else:
102 var = self.d.getVarFlag(key, "_content", True)
103 self.references.add(key)
104 if var is not None:
105 return var
106 else:
107 return match.group()
108
109 def python_sub(self, match):
110 code = match.group()[3:-1]
111 codeobj = compile(code.strip(), self.varname or "<expansion>", "eval")
112
113 parser = bb.codeparser.PythonParser(self.varname, logger)
114 parser.parse_python(code)
115 if self.varname:
116 vardeps = self.d.getVarFlag(self.varname, "vardeps", True)
117 if vardeps is None:
118 parser.log.flush()
119 else:
120 parser.log.flush()
121 self.references |= parser.references
122 self.execs |= parser.execs
123
124 for k in parser.contains:
125 if k not in self.contains:
126 self.contains[k] = parser.contains[k].copy()
127 else:
128 self.contains[k].update(parser.contains[k])
129 value = utils.better_eval(codeobj, DataContext(self.d))
130 return str(value)
131
132
133class DataContext(dict):
134 def __init__(self, metadata, **kwargs):
135 self.metadata = metadata
136 dict.__init__(self, **kwargs)
137 self['d'] = metadata
138
139 def __missing__(self, key):
140 value = self.metadata.getVar(key, True)
141 if value is None or self.metadata.getVarFlag(key, 'func'):
142 raise KeyError(key)
143 else:
144 return value
145
146class ExpansionError(Exception):
147 def __init__(self, varname, expression, exception):
148 self.expression = expression
149 self.variablename = varname
150 self.exception = exception
151 if varname:
152 if expression:
153 self.msg = "Failure expanding variable %s, expression was %s which triggered exception %s: %s" % (varname, expression, type(exception).__name__, exception)
154 else:
155 self.msg = "Failure expanding variable %s: %s: %s" % (varname, type(exception).__name__, exception)
156 else:
157 self.msg = "Failure expanding expression %s which triggered exception %s: %s" % (expression, type(exception).__name__, exception)
158 Exception.__init__(self, self.msg)
159 self.args = (varname, expression, exception)
160 def __str__(self):
161 return self.msg
162
163class IncludeHistory(object):
164 def __init__(self, parent = None, filename = '[TOP LEVEL]'):
165 self.parent = parent
166 self.filename = filename
167 self.children = []
168 self.current = self
169
170 def copy(self):
171 new = IncludeHistory(self.parent, self.filename)
172 for c in self.children:
173 new.children.append(c)
174 return new
175
176 def include(self, filename):
177 newfile = IncludeHistory(self.current, filename)
178 self.current.children.append(newfile)
179 self.current = newfile
180 return self
181
182 def __enter__(self):
183 pass
184
185 def __exit__(self, a, b, c):
186 if self.current.parent:
187 self.current = self.current.parent
188 else:
189 bb.warn("Include log: Tried to finish '%s' at top level." % filename)
190 return False
191
192 def emit(self, o, level = 0):
193 """Emit an include history file, and its children."""
194 if level:
195 spaces = " " * (level - 1)
196 o.write("# %s%s" % (spaces, self.filename))
197 if len(self.children) > 0:
198 o.write(" includes:")
199 else:
200 o.write("#\n# INCLUDE HISTORY:\n#")
201 level = level + 1
202 for child in self.children:
203 o.write("\n")
204 child.emit(o, level)
205
206class VariableHistory(object):
207 def __init__(self, dataroot):
208 self.dataroot = dataroot
209 self.variables = COWDictBase.copy()
210
211 def copy(self):
212 new = VariableHistory(self.dataroot)
213 new.variables = self.variables.copy()
214 return new
215
216 def record(self, *kwonly, **loginfo):
217 if not self.dataroot._tracking:
218 return
219 if len(kwonly) > 0:
220 raise TypeError
221 infer_caller_details(loginfo, parent = True)
222 if 'ignore' in loginfo and loginfo['ignore']:
223 return
224 if 'op' not in loginfo or not loginfo['op']:
225 loginfo['op'] = 'set'
226 if 'detail' in loginfo:
227 loginfo['detail'] = str(loginfo['detail'])
228 if 'variable' not in loginfo or 'file' not in loginfo:
229 raise ValueError("record() missing variable or file.")
230 var = loginfo['variable']
231
232 if var not in self.variables:
233 self.variables[var] = []
234 self.variables[var].append(loginfo.copy())
235
236 def variable(self, var):
237 if var in self.variables:
238 return self.variables[var]
239 else:
240 return []
241
242 def emit(self, var, oval, val, o):
243 history = self.variable(var)
244 commentVal = re.sub('\n', '\n#', str(oval))
245 if history:
246 if len(history) == 1:
247 o.write("#\n# $%s\n" % var)
248 else:
249 o.write("#\n# $%s [%d operations]\n" % (var, len(history)))
250 for event in history:
251 # o.write("# %s\n" % str(event))
252 if 'func' in event:
253 # If we have a function listed, this is internal
254 # code, not an operation in a config file, and the
255 # full path is distracting.
256 event['file'] = re.sub('.*/', '', event['file'])
257 display_func = ' [%s]' % event['func']
258 else:
259 display_func = ''
260 if 'flag' in event:
261 flag = '[%s] ' % (event['flag'])
262 else:
263 flag = ''
264 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'])))
265 if len(history) > 1:
266 o.write("# computed:\n")
267 o.write('# "%s"\n' % (commentVal))
268 else:
269 o.write("#\n# $%s\n# [no history recorded]\n#\n" % var)
270 o.write('# "%s"\n' % (commentVal))
271
272 def get_variable_files(self, var):
273 """Get the files where operations are made on a variable"""
274 var_history = self.variable(var)
275 files = []
276 for event in var_history:
277 files.append(event['file'])
278 return files
279
280 def get_variable_lines(self, var, f):
281 """Get the line where a operation is made on a variable in file f"""
282 var_history = self.variable(var)
283 lines = []
284 for event in var_history:
285 if f== event['file']:
286 line = event['line']
287 lines.append(line)
288 return lines
289
290 def del_var_history(self, var, f=None, line=None):
291 """If file f and line are not given, the entire history of var is deleted"""
292 if var in self.variables:
293 if f and line:
294 self.variables[var] = [ x for x in self.variables[var] if x['file']!=f and x['line']!=line]
295 else:
296 self.variables[var] = []
297
298class DataSmart(MutableMapping):
299 def __init__(self, special = COWDictBase.copy(), seen = COWDictBase.copy() ):
300 self.dict = {}
301
302 self.inchistory = IncludeHistory()
303 self.varhistory = VariableHistory(self)
304 self._tracking = False
305
306 # cookie monster tribute
307 self._special_values = special
308 self._seen_overrides = seen
309
310 self.expand_cache = {}
311
312 def enableTracking(self):
313 self._tracking = True
314
315 def disableTracking(self):
316 self._tracking = False
317
318 def expandWithRefs(self, s, varname):
319
320 if not isinstance(s, basestring): # sanity check
321 return VariableParse(varname, self, s)
322
323 if varname and varname in self.expand_cache:
324 return self.expand_cache[varname]
325
326 varparse = VariableParse(varname, self)
327
328 while s.find('${') != -1:
329 olds = s
330 try:
331 s = __expand_var_regexp__.sub(varparse.var_sub, s)
332 s = __expand_python_regexp__.sub(varparse.python_sub, s)
333 if s == olds:
334 break
335 except ExpansionError:
336 raise
337 except bb.parse.SkipPackage:
338 raise
339 except Exception as exc:
340 raise ExpansionError(varname, s, exc)
341
342 varparse.value = s
343
344 if varname:
345 self.expand_cache[varname] = varparse
346
347 return varparse
348
349 def expand(self, s, varname = None):
350 return self.expandWithRefs(s, varname).value
351
352
353 def finalize(self, parent = False):
354 """Performs final steps upon the datastore, including application of overrides"""
355
356 overrides = (self.getVar("OVERRIDES", True) or "").split(":") or []
357 finalize_caller = {
358 'op': 'finalize',
359 }
360 infer_caller_details(finalize_caller, parent = parent, varval = False)
361
362 #
363 # Well let us see what breaks here. We used to iterate
364 # over each variable and apply the override and then
365 # do the line expanding.
366 # If we have bad luck - which we will have - the keys
367 # where in some order that is so important for this
368 # method which we don't have anymore.
369 # Anyway we will fix that and write test cases this
370 # time.
371
372 #
373 # First we apply all overrides
374 # Then we will handle _append and _prepend and store the _remove
375 # information for later.
376 #
377
378 # We only want to report finalization once per variable overridden.
379 finalizes_reported = {}
380
381 for o in overrides:
382 # calculate '_'+override
383 l = len(o) + 1
384
385 # see if one should even try
386 if o not in self._seen_overrides:
387 continue
388
389 vars = self._seen_overrides[o].copy()
390 for var in vars:
391 name = var[:-l]
392 try:
393 # Report only once, even if multiple changes.
394 if name not in finalizes_reported:
395 finalizes_reported[name] = True
396 finalize_caller['variable'] = name
397 finalize_caller['detail'] = 'was: ' + str(self.getVar(name, False))
398 self.varhistory.record(**finalize_caller)
399 # Copy history of the override over.
400 for event in self.varhistory.variable(var):
401 loginfo = event.copy()
402 loginfo['variable'] = name
403 loginfo['op'] = 'override[%s]:%s' % (o, loginfo['op'])
404 self.varhistory.record(**loginfo)
405 self.setVar(name, self.getVar(var, False), op = 'finalize', file = 'override[%s]' % o, line = '')
406 self.delVar(var)
407 except Exception:
408 logger.info("Untracked delVar")
409
410 # now on to the appends and prepends, and stashing the removes
411 for op in __setvar_keyword__:
412 if op in self._special_values:
413 appends = self._special_values[op] or []
414 for append in appends:
415 keep = []
416 for (a, o) in self.getVarFlag(append, op) or []:
417 match = True
418 if o:
419 for o2 in o.split("_"):
420 if not o2 in overrides:
421 match = False
422 if not match:
423 keep.append((a ,o))
424 continue
425
426 if op == "_append":
427 sval = self.getVar(append, False) or ""
428 sval += a
429 self.setVar(append, sval)
430 elif op == "_prepend":
431 sval = a + (self.getVar(append, False) or "")
432 self.setVar(append, sval)
433 elif op == "_remove":
434 removes = self.getVarFlag(append, "_removeactive", False) or []
435 removes.extend(a.split())
436 self.setVarFlag(append, "_removeactive", removes, ignore=True)
437
438 # We save overrides that may be applied at some later stage
439 if keep:
440 self.setVarFlag(append, op, keep, ignore=True)
441 else:
442 self.delVarFlag(append, op, ignore=True)
443
444 def initVar(self, var):
445 self.expand_cache = {}
446 if not var in self.dict:
447 self.dict[var] = {}
448
449 def _findVar(self, var):
450 dest = self.dict
451 while dest:
452 if var in dest:
453 return dest[var]
454
455 if "_data" not in dest:
456 break
457 dest = dest["_data"]
458
459 def _makeShadowCopy(self, var):
460 if var in self.dict:
461 return
462
463 local_var = self._findVar(var)
464
465 if local_var:
466 self.dict[var] = copy.copy(local_var)
467 else:
468 self.initVar(var)
469
470
471 def setVar(self, var, value, **loginfo):
472 #print("var=" + str(var) + " val=" + str(value))
473 if 'op' not in loginfo:
474 loginfo['op'] = "set"
475 self.expand_cache = {}
476 match = __setvar_regexp__.match(var)
477 if match and match.group("keyword") in __setvar_keyword__:
478 base = match.group('base')
479 keyword = match.group("keyword")
480 override = match.group('add')
481 l = self.getVarFlag(base, keyword) or []
482 l.append([value, override])
483 self.setVarFlag(base, keyword, l, ignore=True)
484 # And cause that to be recorded:
485 loginfo['detail'] = value
486 loginfo['variable'] = base
487 if override:
488 loginfo['op'] = '%s[%s]' % (keyword, override)
489 else:
490 loginfo['op'] = keyword
491 self.varhistory.record(**loginfo)
492 # todo make sure keyword is not __doc__ or __module__
493 # pay the cookie monster
494 try:
495 self._special_values[keyword].add(base)
496 except KeyError:
497 self._special_values[keyword] = set()
498 self._special_values[keyword].add(base)
499
500 return
501
502 if not var in self.dict:
503 self._makeShadowCopy(var)
504
505 # more cookies for the cookie monster
506 if '_' in var:
507 self._setvar_update_overrides(var)
508
509 # setting var
510 self.dict[var]["_content"] = value
511 self.varhistory.record(**loginfo)
512
513 def _setvar_update_overrides(self, var):
514 # aka pay the cookie monster
515 override = var[var.rfind('_')+1:]
516 if len(override) > 0:
517 if override not in self._seen_overrides:
518 self._seen_overrides[override] = set()
519 self._seen_overrides[override].add( var )
520
521 def getVar(self, var, expand=False, noweakdefault=False):
522 return self.getVarFlag(var, "_content", expand, noweakdefault)
523
524 def renameVar(self, key, newkey, **loginfo):
525 """
526 Rename the variable key to newkey
527 """
528 val = self.getVar(key, 0)
529 if val is not None:
530 loginfo['variable'] = newkey
531 loginfo['op'] = 'rename from %s' % key
532 loginfo['detail'] = val
533 self.varhistory.record(**loginfo)
534 self.setVar(newkey, val, ignore=True)
535
536 for i in (__setvar_keyword__):
537 src = self.getVarFlag(key, i)
538 if src is None:
539 continue
540
541 dest = self.getVarFlag(newkey, i) or []
542 dest.extend(src)
543 self.setVarFlag(newkey, i, dest, ignore=True)
544
545 if i in self._special_values and key in self._special_values[i]:
546 self._special_values[i].remove(key)
547 self._special_values[i].add(newkey)
548
549 loginfo['variable'] = key
550 loginfo['op'] = 'rename (to)'
551 loginfo['detail'] = newkey
552 self.varhistory.record(**loginfo)
553 self.delVar(key, ignore=True)
554
555 def appendVar(self, var, value, **loginfo):
556 loginfo['op'] = 'append'
557 self.varhistory.record(**loginfo)
558 newvalue = (self.getVar(var, False) or "") + value
559 self.setVar(var, newvalue, ignore=True)
560
561 def prependVar(self, var, value, **loginfo):
562 loginfo['op'] = 'prepend'
563 self.varhistory.record(**loginfo)
564 newvalue = value + (self.getVar(var, False) or "")
565 self.setVar(var, newvalue, ignore=True)
566
567 def delVar(self, var, **loginfo):
568 loginfo['detail'] = ""
569 loginfo['op'] = 'del'
570 self.varhistory.record(**loginfo)
571 self.expand_cache = {}
572 self.dict[var] = {}
573 if '_' in var:
574 override = var[var.rfind('_')+1:]
575 if override and override in self._seen_overrides and var in self._seen_overrides[override]:
576 self._seen_overrides[override].remove(var)
577
578 def setVarFlag(self, var, flag, value, **loginfo):
579 if 'op' not in loginfo:
580 loginfo['op'] = "set"
581 loginfo['flag'] = flag
582 self.varhistory.record(**loginfo)
583 if not var in self.dict:
584 self._makeShadowCopy(var)
585 self.dict[var][flag] = value
586
587 if flag == "defaultval" and '_' in var:
588 self._setvar_update_overrides(var)
589
590 if flag == "unexport" or flag == "export":
591 if not "__exportlist" in self.dict:
592 self._makeShadowCopy("__exportlist")
593 if not "_content" in self.dict["__exportlist"]:
594 self.dict["__exportlist"]["_content"] = set()
595 self.dict["__exportlist"]["_content"].add(var)
596
597 def getVarFlag(self, var, flag, expand=False, noweakdefault=False):
598 local_var = self._findVar(var)
599 value = None
600 if local_var is not None:
601 if flag in local_var:
602 value = copy.copy(local_var[flag])
603 elif flag == "_content" and "defaultval" in local_var and not noweakdefault:
604 value = copy.copy(local_var["defaultval"])
605 if expand and value:
606 # Only getvar (flag == _content) hits the expand cache
607 cachename = None
608 if flag == "_content":
609 cachename = var
610 else:
611 cachename = var + "[" + flag + "]"
612 value = self.expand(value, cachename)
613 if value is not None and flag == "_content" and local_var is not None and "_removeactive" in local_var:
614 filtered = filter(lambda v: v not in local_var["_removeactive"],
615 value.split(" "))
616 value = " ".join(filtered)
617 if expand:
618 # We need to ensure the expand cache has the correct value
619 # flag == "_content" here
620 self.expand_cache[var].value = value
621 return value
622
623 def delVarFlag(self, var, flag, **loginfo):
624 local_var = self._findVar(var)
625 if not local_var:
626 return
627 if not var in self.dict:
628 self._makeShadowCopy(var)
629
630 if var in self.dict and flag in self.dict[var]:
631 loginfo['detail'] = ""
632 loginfo['op'] = 'delFlag'
633 loginfo['flag'] = flag
634 self.varhistory.record(**loginfo)
635
636 del self.dict[var][flag]
637
638 def appendVarFlag(self, var, flag, value, **loginfo):
639 loginfo['op'] = 'append'
640 loginfo['flag'] = flag
641 self.varhistory.record(**loginfo)
642 newvalue = (self.getVarFlag(var, flag, False) or "") + value
643 self.setVarFlag(var, flag, newvalue, ignore=True)
644
645 def prependVarFlag(self, var, flag, value, **loginfo):
646 loginfo['op'] = 'prepend'
647 loginfo['flag'] = flag
648 self.varhistory.record(**loginfo)
649 newvalue = value + (self.getVarFlag(var, flag, False) or "")
650 self.setVarFlag(var, flag, newvalue, ignore=True)
651
652 def setVarFlags(self, var, flags, **loginfo):
653 infer_caller_details(loginfo)
654 if not var in self.dict:
655 self._makeShadowCopy(var)
656
657 for i in flags:
658 if i == "_content":
659 continue
660 loginfo['flag'] = i
661 loginfo['detail'] = flags[i]
662 self.varhistory.record(**loginfo)
663 self.dict[var][i] = flags[i]
664
665 def getVarFlags(self, var, expand = False, internalflags=False):
666 local_var = self._findVar(var)
667 flags = {}
668
669 if local_var:
670 for i in local_var:
671 if i.startswith("_") and not internalflags:
672 continue
673 flags[i] = local_var[i]
674 if expand and i in expand:
675 flags[i] = self.expand(flags[i], var + "[" + i + "]")
676 if len(flags) == 0:
677 return None
678 return flags
679
680
681 def delVarFlags(self, var, **loginfo):
682 if not var in self.dict:
683 self._makeShadowCopy(var)
684
685 if var in self.dict:
686 content = None
687
688 loginfo['op'] = 'delete flags'
689 self.varhistory.record(**loginfo)
690
691 # try to save the content
692 if "_content" in self.dict[var]:
693 content = self.dict[var]["_content"]
694 self.dict[var] = {}
695 self.dict[var]["_content"] = content
696 else:
697 del self.dict[var]
698
699
700 def createCopy(self):
701 """
702 Create a copy of self by setting _data to self
703 """
704 # we really want this to be a DataSmart...
705 data = DataSmart(seen=self._seen_overrides.copy(), special=self._special_values.copy())
706 data.dict["_data"] = self.dict
707 data.varhistory = self.varhistory.copy()
708 data.varhistory.datasmart = data
709 data.inchistory = self.inchistory.copy()
710
711 data._tracking = self._tracking
712
713 return data
714
715 def expandVarref(self, variable, parents=False):
716 """Find all references to variable in the data and expand it
717 in place, optionally descending to parent datastores."""
718
719 if parents:
720 keys = iter(self)
721 else:
722 keys = self.localkeys()
723
724 ref = '${%s}' % variable
725 value = self.getVar(variable, False)
726 for key in keys:
727 referrervalue = self.getVar(key, False)
728 if referrervalue and ref in referrervalue:
729 self.setVar(key, referrervalue.replace(ref, value))
730
731 def localkeys(self):
732 for key in self.dict:
733 if key != '_data':
734 yield key
735
736 def __iter__(self):
737 def keylist(d):
738 klist = set()
739 for key in d:
740 if key == "_data":
741 continue
742 if not d[key]:
743 continue
744 klist.add(key)
745
746 if "_data" in d:
747 klist |= keylist(d["_data"])
748
749 return klist
750
751 for k in keylist(self.dict):
752 yield k
753
754 def __len__(self):
755 return len(frozenset(self))
756
757 def __getitem__(self, item):
758 value = self.getVar(item, False)
759 if value is None:
760 raise KeyError(item)
761 else:
762 return value
763
764 def __setitem__(self, var, value):
765 self.setVar(var, value)
766
767 def __delitem__(self, var):
768 self.delVar(var)
769
770 def get_hash(self):
771 data = {}
772 d = self.createCopy()
773 bb.data.expandKeys(d)
774 bb.data.update_data(d)
775
776 config_whitelist = set((d.getVar("BB_HASHCONFIG_WHITELIST", True) or "").split())
777 keys = set(key for key in iter(d) if not key.startswith("__"))
778 for key in keys:
779 if key in config_whitelist:
780 continue
781
782 value = d.getVar(key, False) or ""
783 data.update({key:value})
784
785 varflags = d.getVarFlags(key, internalflags = True)
786 if not varflags:
787 continue
788 for f in varflags:
789 if f == "_content":
790 continue
791 data.update({'%s[%s]' % (key, f):varflags[f]})
792
793 for key in ["__BBTASKS", "__BBANONFUNCS", "__BBHANDLERS"]:
794 bb_list = d.getVar(key, False) or []
795 bb_list.sort()
796 data.update({key:str(bb_list)})
797
798 if key == "__BBANONFUNCS":
799 for i in bb_list:
800 value = d.getVar(i, True) or ""
801 data.update({i:value})
802
803 data_str = str([(k, data[k]) for k in sorted(data.keys())])
804 return hashlib.md5(data_str).hexdigest()