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