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