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