blob: dd5c618564f956b0fc635386d689699dbea596f8 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001"""
2BitBake Smart Dictionary Implementation
3
4Functions for interacting with the data structure used by the
5BitBake build tools.
6
7"""
8
9# Copyright (C) 2003, 2004 Chris Larson
10# Copyright (C) 2004, 2005 Seb Frankengul
11# Copyright (C) 2005, 2006 Holger Hans Peter Freyther
12# Copyright (C) 2005 Uli Luckas
13# Copyright (C) 2005 ROAD GmbH
14#
Brad Bishopc342db32019-05-15 21:57:59 -040015# SPDX-License-Identifier: GPL-2.0-only
Patrick Williamsc124f4f2015-09-15 14:41:29 -050016#
Patrick Williamsc124f4f2015-09-15 14:41:29 -050017# Based on functions from the base bb module, Copyright 2003 Holger Schurig
18
19import copy, re, sys, traceback
20from collections import MutableMapping
21import logging
22import hashlib
23import bb, bb.codeparser
24from bb import utils
25from bb.COW import COWDictBase
26
27logger = logging.getLogger("BitBake.Data")
28
29__setvar_keyword__ = ["_append", "_prepend", "_remove"]
Brad Bishop19323692019-04-05 15:28:33 -040030__setvar_regexp__ = re.compile(r'(?P<base>.*?)(?P<keyword>_append|_prepend|_remove)(_(?P<add>[^A-Z]*))?$')
31__expand_var_regexp__ = re.compile(r"\${[a-zA-Z0-9\-_+./~]+?}")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050032__expand_python_regexp__ = re.compile(r"\${@.+?}")
Brad Bishop19323692019-04-05 15:28:33 -040033__whitespace_split__ = re.compile(r'(\s)')
34__override_regexp__ = re.compile(r'[a-z0-9]+')
Patrick Williamsc124f4f2015-09-15 14:41:29 -050035
36def infer_caller_details(loginfo, parent = False, varval = True):
37 """Save the caller the trouble of specifying everything."""
38 # Save effort.
39 if 'ignore' in loginfo and loginfo['ignore']:
40 return
41 # If nothing was provided, mark this as possibly unneeded.
42 if not loginfo:
43 loginfo['ignore'] = True
44 return
45 # Infer caller's likely values for variable (var) and value (value),
46 # to reduce clutter in the rest of the code.
47 above = None
48 def set_above():
49 try:
50 raise Exception
51 except Exception:
52 tb = sys.exc_info()[2]
53 if parent:
54 return tb.tb_frame.f_back.f_back.f_back
55 else:
56 return tb.tb_frame.f_back.f_back
57
58 if varval and ('variable' not in loginfo or 'detail' not in loginfo):
59 if not above:
60 above = set_above()
61 lcls = above.f_locals.items()
62 for k, v in lcls:
63 if k == 'value' and 'detail' not in loginfo:
64 loginfo['detail'] = v
65 if k == 'var' and 'variable' not in loginfo:
66 loginfo['variable'] = v
67 # Infer file/line/function from traceback
68 # Don't use traceback.extract_stack() since it fills the line contents which
69 # we don't need and that hits stat syscalls
70 if 'file' not in loginfo:
71 if not above:
72 above = set_above()
73 f = above.f_back
74 line = f.f_lineno
75 file = f.f_code.co_filename
76 func = f.f_code.co_name
77 loginfo['file'] = file
78 loginfo['line'] = line
79 if func not in loginfo:
80 loginfo['func'] = func
81
82class VariableParse:
83 def __init__(self, varname, d, val = None):
84 self.varname = varname
85 self.d = d
86 self.value = val
87
88 self.references = set()
89 self.execs = set()
90 self.contains = {}
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)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080097 var = self.d.getVarFlag(key, "_content")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050098 self.references.add(key)
99 if var is not None:
100 return var
101 else:
102 return match.group()
103
104 def python_sub(self, match):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500105 if isinstance(match, str):
106 code = match
107 else:
108 code = match.group()[3:-1]
109
110 if "_remote_data" in self.d:
111 connector = self.d["_remote_data"]
112 return connector.expandPythonRef(self.varname, code, self.d)
113
Brad Bishop19323692019-04-05 15:28:33 -0400114 if self.varname:
115 varname = 'Var <%s>' % self.varname
116 else:
117 varname = '<expansion>'
118 codeobj = compile(code.strip(), varname, "eval")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500119
120 parser = bb.codeparser.PythonParser(self.varname, logger)
121 parser.parse_python(code)
122 if self.varname:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500123 vardeps = self.d.getVarFlag(self.varname, "vardeps")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500124 if vardeps is None:
125 parser.log.flush()
126 else:
127 parser.log.flush()
128 self.references |= parser.references
129 self.execs |= parser.execs
130
131 for k in parser.contains:
132 if k not in self.contains:
133 self.contains[k] = parser.contains[k].copy()
134 else:
135 self.contains[k].update(parser.contains[k])
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600136 value = utils.better_eval(codeobj, DataContext(self.d), {'d' : self.d})
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500137 return str(value)
138
139
140class DataContext(dict):
141 def __init__(self, metadata, **kwargs):
142 self.metadata = metadata
143 dict.__init__(self, **kwargs)
144 self['d'] = metadata
145
146 def __missing__(self, key):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500147 value = self.metadata.getVar(key)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500148 if value is None or self.metadata.getVarFlag(key, 'func', False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500149 raise KeyError(key)
150 else:
151 return value
152
153class ExpansionError(Exception):
154 def __init__(self, varname, expression, exception):
155 self.expression = expression
156 self.variablename = varname
157 self.exception = exception
158 if varname:
159 if expression:
160 self.msg = "Failure expanding variable %s, expression was %s which triggered exception %s: %s" % (varname, expression, type(exception).__name__, exception)
161 else:
162 self.msg = "Failure expanding variable %s: %s: %s" % (varname, type(exception).__name__, exception)
163 else:
164 self.msg = "Failure expanding expression %s which triggered exception %s: %s" % (expression, type(exception).__name__, exception)
165 Exception.__init__(self, self.msg)
166 self.args = (varname, expression, exception)
167 def __str__(self):
168 return self.msg
169
170class IncludeHistory(object):
171 def __init__(self, parent = None, filename = '[TOP LEVEL]'):
172 self.parent = parent
173 self.filename = filename
174 self.children = []
175 self.current = self
176
177 def copy(self):
178 new = IncludeHistory(self.parent, self.filename)
179 for c in self.children:
180 new.children.append(c)
181 return new
182
183 def include(self, filename):
184 newfile = IncludeHistory(self.current, filename)
185 self.current.children.append(newfile)
186 self.current = newfile
187 return self
188
189 def __enter__(self):
190 pass
191
192 def __exit__(self, a, b, c):
193 if self.current.parent:
194 self.current = self.current.parent
195 else:
196 bb.warn("Include log: Tried to finish '%s' at top level." % filename)
197 return False
198
199 def emit(self, o, level = 0):
200 """Emit an include history file, and its children."""
201 if level:
202 spaces = " " * (level - 1)
203 o.write("# %s%s" % (spaces, self.filename))
204 if len(self.children) > 0:
205 o.write(" includes:")
206 else:
207 o.write("#\n# INCLUDE HISTORY:\n#")
208 level = level + 1
209 for child in self.children:
210 o.write("\n")
211 child.emit(o, level)
212
213class VariableHistory(object):
214 def __init__(self, dataroot):
215 self.dataroot = dataroot
216 self.variables = COWDictBase.copy()
217
218 def copy(self):
219 new = VariableHistory(self.dataroot)
220 new.variables = self.variables.copy()
221 return new
222
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500223 def __getstate__(self):
224 vardict = {}
225 for k, v in self.variables.iteritems():
226 vardict[k] = v
227 return {'dataroot': self.dataroot,
228 'variables': vardict}
229
230 def __setstate__(self, state):
231 self.dataroot = state['dataroot']
232 self.variables = COWDictBase.copy()
233 for k, v in state['variables'].items():
234 self.variables[k] = v
235
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500236 def record(self, *kwonly, **loginfo):
237 if not self.dataroot._tracking:
238 return
239 if len(kwonly) > 0:
240 raise TypeError
241 infer_caller_details(loginfo, parent = True)
242 if 'ignore' in loginfo and loginfo['ignore']:
243 return
244 if 'op' not in loginfo or not loginfo['op']:
245 loginfo['op'] = 'set'
246 if 'detail' in loginfo:
247 loginfo['detail'] = str(loginfo['detail'])
248 if 'variable' not in loginfo or 'file' not in loginfo:
249 raise ValueError("record() missing variable or file.")
250 var = loginfo['variable']
251
252 if var not in self.variables:
253 self.variables[var] = []
254 if not isinstance(self.variables[var], list):
255 return
256 if 'nodups' in loginfo and loginfo in self.variables[var]:
257 return
258 self.variables[var].append(loginfo.copy())
259
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800260 def rename_variable_hist(self, oldvar, newvar):
261 if not self.dataroot._tracking:
262 return
263 if oldvar not in self.variables:
264 return
265 if newvar not in self.variables:
266 self.variables[newvar] = []
267 for i in self.variables[oldvar]:
268 self.variables[newvar].append(i.copy())
269
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500270 def variable(self, var):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500271 remote_connector = self.dataroot.getVar('_remote_data', False)
272 if remote_connector:
273 varhistory = remote_connector.getVarHistory(var)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500274 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500275 varhistory = []
276
277 if var in self.variables:
278 varhistory.extend(self.variables[var])
279 return varhistory
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500280
281 def emit(self, var, oval, val, o, d):
282 history = self.variable(var)
283
284 # Append override history
285 if var in d.overridedata:
286 for (r, override) in d.overridedata[var]:
287 for event in self.variable(r):
288 loginfo = event.copy()
289 if 'flag' in loginfo and not loginfo['flag'].startswith("_"):
290 continue
291 loginfo['variable'] = var
292 loginfo['op'] = 'override[%s]:%s' % (override, loginfo['op'])
293 history.append(loginfo)
294
295 commentVal = re.sub('\n', '\n#', str(oval))
296 if history:
297 if len(history) == 1:
298 o.write("#\n# $%s\n" % var)
299 else:
300 o.write("#\n# $%s [%d operations]\n" % (var, len(history)))
301 for event in history:
302 # o.write("# %s\n" % str(event))
303 if 'func' in event:
304 # If we have a function listed, this is internal
305 # code, not an operation in a config file, and the
306 # full path is distracting.
307 event['file'] = re.sub('.*/', '', event['file'])
308 display_func = ' [%s]' % event['func']
309 else:
310 display_func = ''
311 if 'flag' in event:
312 flag = '[%s] ' % (event['flag'])
313 else:
314 flag = ''
315 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'])))
316 if len(history) > 1:
317 o.write("# pre-expansion value:\n")
318 o.write('# "%s"\n' % (commentVal))
319 else:
320 o.write("#\n# $%s\n# [no history recorded]\n#\n" % var)
321 o.write('# "%s"\n' % (commentVal))
322
323 def get_variable_files(self, var):
324 """Get the files where operations are made on a variable"""
325 var_history = self.variable(var)
326 files = []
327 for event in var_history:
328 files.append(event['file'])
329 return files
330
331 def get_variable_lines(self, var, f):
332 """Get the line where a operation is made on a variable in file f"""
333 var_history = self.variable(var)
334 lines = []
335 for event in var_history:
336 if f== event['file']:
337 line = event['line']
338 lines.append(line)
339 return lines
340
341 def get_variable_items_files(self, var, d):
342 """
343 Use variable history to map items added to a list variable and
344 the files in which they were added.
345 """
346 history = self.variable(var)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500347 finalitems = (d.getVar(var) or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500348 filemap = {}
349 isset = False
350 for event in history:
351 if 'flag' in event:
352 continue
353 if event['op'] == '_remove':
354 continue
355 if isset and event['op'] == 'set?':
356 continue
357 isset = True
358 items = d.expand(event['detail']).split()
359 for item in items:
360 # This is a little crude but is belt-and-braces to avoid us
361 # having to handle every possible operation type specifically
362 if item in finalitems and not item in filemap:
363 filemap[item] = event['file']
364 return filemap
365
366 def del_var_history(self, var, f=None, line=None):
367 """If file f and line are not given, the entire history of var is deleted"""
368 if var in self.variables:
369 if f and line:
370 self.variables[var] = [ x for x in self.variables[var] if x['file']!=f and x['line']!=line]
371 else:
372 self.variables[var] = []
373
374class DataSmart(MutableMapping):
375 def __init__(self):
376 self.dict = {}
377
378 self.inchistory = IncludeHistory()
379 self.varhistory = VariableHistory(self)
380 self._tracking = False
381
382 self.expand_cache = {}
383
384 # cookie monster tribute
385 # Need to be careful about writes to overridedata as
386 # its only a shallow copy, could influence other data store
387 # copies!
388 self.overridedata = {}
389 self.overrides = None
390 self.overridevars = set(["OVERRIDES", "FILE"])
391 self.inoverride = False
392
393 def enableTracking(self):
394 self._tracking = True
395
396 def disableTracking(self):
397 self._tracking = False
398
399 def expandWithRefs(self, s, varname):
400
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600401 if not isinstance(s, str): # sanity check
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500402 return VariableParse(varname, self, s)
403
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500404 varparse = VariableParse(varname, self)
405
406 while s.find('${') != -1:
407 olds = s
408 try:
409 s = __expand_var_regexp__.sub(varparse.var_sub, s)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500410 try:
411 s = __expand_python_regexp__.sub(varparse.python_sub, s)
412 except SyntaxError as e:
413 # Likely unmatched brackets, just don't expand the expression
414 if e.msg != "EOL while scanning string literal":
415 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500416 if s == olds:
417 break
418 except ExpansionError:
419 raise
420 except bb.parse.SkipRecipe:
421 raise
422 except Exception as exc:
Brad Bishop19323692019-04-05 15:28:33 -0400423 tb = sys.exc_info()[2]
424 raise ExpansionError(varname, s, exc).with_traceback(tb) from exc
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500425
426 varparse.value = s
427
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500428 return varparse
429
430 def expand(self, s, varname = None):
431 return self.expandWithRefs(s, varname).value
432
433 def finalize(self, parent = False):
434 return
435
436 def internal_finalize(self, parent = False):
437 """Performs final steps upon the datastore, including application of overrides"""
438 self.overrides = None
439
440 def need_overrides(self):
Patrick Williamsd7e96312015-09-22 08:09:05 -0500441 if self.overrides is not None:
442 return
443 if self.inoverride:
444 return
445 for count in range(5):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500446 self.inoverride = True
447 # Can end up here recursively so setup dummy values
448 self.overrides = []
449 self.overridesset = set()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500450 self.overrides = (self.getVar("OVERRIDES") or "").split(":") or []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500451 self.overridesset = set(self.overrides)
452 self.inoverride = False
453 self.expand_cache = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500454 newoverrides = (self.getVar("OVERRIDES") or "").split(":") or []
Patrick Williamsd7e96312015-09-22 08:09:05 -0500455 if newoverrides == self.overrides:
456 break
457 self.overrides = newoverrides
458 self.overridesset = set(self.overrides)
459 else:
460 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 -0500461
462 def initVar(self, var):
463 self.expand_cache = {}
464 if not var in self.dict:
465 self.dict[var] = {}
466
467 def _findVar(self, var):
468 dest = self.dict
469 while dest:
470 if var in dest:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500471 return dest[var], self.overridedata.get(var, None)
472
473 if "_remote_data" in dest:
474 connector = dest["_remote_data"]["_content"]
475 return connector.getVar(var)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500476
477 if "_data" not in dest:
478 break
479 dest = dest["_data"]
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500480 return None, self.overridedata.get(var, None)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500481
482 def _makeShadowCopy(self, var):
483 if var in self.dict:
484 return
485
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500486 local_var, _ = self._findVar(var)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500487
488 if local_var:
489 self.dict[var] = copy.copy(local_var)
490 else:
491 self.initVar(var)
492
493
494 def setVar(self, var, value, **loginfo):
495 #print("var=" + str(var) + " val=" + str(value))
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800496 self.expand_cache = {}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500497 parsing=False
498 if 'parsing' in loginfo:
499 parsing=True
500
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500501 if '_remote_data' in self.dict:
502 connector = self.dict["_remote_data"]["_content"]
503 res = connector.setVar(var, value)
504 if not res:
505 return
506
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500507 if 'op' not in loginfo:
508 loginfo['op'] = "set"
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800509
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500510 match = __setvar_regexp__.match(var)
511 if match and match.group("keyword") in __setvar_keyword__:
512 base = match.group('base')
513 keyword = match.group("keyword")
514 override = match.group('add')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500515 l = self.getVarFlag(base, keyword, False) or []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500516 l.append([value, override])
517 self.setVarFlag(base, keyword, l, ignore=True)
518 # And cause that to be recorded:
519 loginfo['detail'] = value
520 loginfo['variable'] = base
521 if override:
522 loginfo['op'] = '%s[%s]' % (keyword, override)
523 else:
524 loginfo['op'] = keyword
525 self.varhistory.record(**loginfo)
526 # todo make sure keyword is not __doc__ or __module__
527 # pay the cookie monster
528
529 # more cookies for the cookie monster
530 if '_' in var:
531 self._setvar_update_overrides(base, **loginfo)
532
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500533 if base in self.overridevars:
Patrick Williamsd7e96312015-09-22 08:09:05 -0500534 self._setvar_update_overridevars(var, value)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500535 return
536
537 if not var in self.dict:
538 self._makeShadowCopy(var)
539
540 if not parsing:
541 if "_append" in self.dict[var]:
542 del self.dict[var]["_append"]
543 if "_prepend" in self.dict[var]:
544 del self.dict[var]["_prepend"]
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500545 if "_remove" in self.dict[var]:
546 del self.dict[var]["_remove"]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500547 if var in self.overridedata:
548 active = []
549 self.need_overrides()
550 for (r, o) in self.overridedata[var]:
551 if o in self.overridesset:
552 active.append(r)
553 elif "_" in o:
554 if set(o.split("_")).issubset(self.overridesset):
555 active.append(r)
556 for a in active:
557 self.delVar(a)
558 del self.overridedata[var]
559
560 # more cookies for the cookie monster
561 if '_' in var:
562 self._setvar_update_overrides(var, **loginfo)
563
564 # setting var
565 self.dict[var]["_content"] = value
566 self.varhistory.record(**loginfo)
567
568 if var in self.overridevars:
Patrick Williamsd7e96312015-09-22 08:09:05 -0500569 self._setvar_update_overridevars(var, value)
570
571 def _setvar_update_overridevars(self, var, value):
572 vardata = self.expandWithRefs(value, var)
573 new = vardata.references
574 new.update(vardata.contains.keys())
575 while not new.issubset(self.overridevars):
576 nextnew = set()
577 self.overridevars.update(new)
578 for i in new:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500579 vardata = self.expandWithRefs(self.getVar(i), i)
Patrick Williamsd7e96312015-09-22 08:09:05 -0500580 nextnew.update(vardata.references)
581 nextnew.update(vardata.contains.keys())
582 new = nextnew
583 self.internal_finalize(True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500584
585 def _setvar_update_overrides(self, var, **loginfo):
586 # aka pay the cookie monster
587 override = var[var.rfind('_')+1:]
588 shortvar = var[:var.rfind('_')]
Brad Bishop19323692019-04-05 15:28:33 -0400589 while override and __override_regexp__.match(override):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500590 if shortvar not in self.overridedata:
591 self.overridedata[shortvar] = []
592 if [var, override] not in self.overridedata[shortvar]:
593 # Force CoW by recreating the list first
594 self.overridedata[shortvar] = list(self.overridedata[shortvar])
595 self.overridedata[shortvar].append([var, override])
596 override = None
597 if "_" in shortvar:
598 override = var[shortvar.rfind('_')+1:]
599 shortvar = var[:shortvar.rfind('_')]
600 if len(shortvar) == 0:
601 override = None
602
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500603 def getVar(self, var, expand=True, noweakdefault=False, parsing=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500604 return self.getVarFlag(var, "_content", expand, noweakdefault, parsing)
605
606 def renameVar(self, key, newkey, **loginfo):
607 """
608 Rename the variable key to newkey
609 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500610 if '_remote_data' in self.dict:
611 connector = self.dict["_remote_data"]["_content"]
612 res = connector.renameVar(key, newkey)
613 if not res:
614 return
615
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500616 val = self.getVar(key, 0, parsing=True)
617 if val is not None:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800618 self.varhistory.rename_variable_hist(key, newkey)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500619 loginfo['variable'] = newkey
620 loginfo['op'] = 'rename from %s' % key
621 loginfo['detail'] = val
622 self.varhistory.record(**loginfo)
623 self.setVar(newkey, val, ignore=True, parsing=True)
624
625 for i in (__setvar_keyword__):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500626 src = self.getVarFlag(key, i, False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500627 if src is None:
628 continue
629
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500630 dest = self.getVarFlag(newkey, i, False) or []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500631 dest.extend(src)
632 self.setVarFlag(newkey, i, dest, ignore=True)
633
634 if key in self.overridedata:
635 self.overridedata[newkey] = []
636 for (v, o) in self.overridedata[key]:
637 self.overridedata[newkey].append([v.replace(key, newkey), o])
638 self.renameVar(v, v.replace(key, newkey))
639
640 if '_' in newkey and val is None:
641 self._setvar_update_overrides(newkey, **loginfo)
642
643 loginfo['variable'] = key
644 loginfo['op'] = 'rename (to)'
645 loginfo['detail'] = newkey
646 self.varhistory.record(**loginfo)
647 self.delVar(key, ignore=True)
648
649 def appendVar(self, var, value, **loginfo):
650 loginfo['op'] = 'append'
651 self.varhistory.record(**loginfo)
652 self.setVar(var + "_append", value, ignore=True, parsing=True)
653
654 def prependVar(self, var, value, **loginfo):
655 loginfo['op'] = 'prepend'
656 self.varhistory.record(**loginfo)
657 self.setVar(var + "_prepend", value, ignore=True, parsing=True)
658
659 def delVar(self, var, **loginfo):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800660 self.expand_cache = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500661 if '_remote_data' in self.dict:
662 connector = self.dict["_remote_data"]["_content"]
663 res = connector.delVar(var)
664 if not res:
665 return
666
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500667 loginfo['detail'] = ""
668 loginfo['op'] = 'del'
669 self.varhistory.record(**loginfo)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500670 self.dict[var] = {}
671 if var in self.overridedata:
672 del self.overridedata[var]
673 if '_' in var:
674 override = var[var.rfind('_')+1:]
675 shortvar = var[:var.rfind('_')]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500676 while override and override.islower():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500677 try:
678 if shortvar in self.overridedata:
679 # Force CoW by recreating the list first
680 self.overridedata[shortvar] = list(self.overridedata[shortvar])
681 self.overridedata[shortvar].remove([var, override])
682 except ValueError as e:
683 pass
684 override = None
685 if "_" in shortvar:
686 override = var[shortvar.rfind('_')+1:]
687 shortvar = var[:shortvar.rfind('_')]
688 if len(shortvar) == 0:
689 override = None
690
691 def setVarFlag(self, var, flag, value, **loginfo):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800692 self.expand_cache = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500693 if '_remote_data' in self.dict:
694 connector = self.dict["_remote_data"]["_content"]
695 res = connector.setVarFlag(var, flag, value)
696 if not res:
697 return
698
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500699 if 'op' not in loginfo:
700 loginfo['op'] = "set"
701 loginfo['flag'] = flag
702 self.varhistory.record(**loginfo)
703 if not var in self.dict:
704 self._makeShadowCopy(var)
705 self.dict[var][flag] = value
706
707 if flag == "_defaultval" and '_' in var:
708 self._setvar_update_overrides(var, **loginfo)
Patrick Williamsd7e96312015-09-22 08:09:05 -0500709 if flag == "_defaultval" and var in self.overridevars:
710 self._setvar_update_overridevars(var, value)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500711
712 if flag == "unexport" or flag == "export":
713 if not "__exportlist" in self.dict:
714 self._makeShadowCopy("__exportlist")
715 if not "_content" in self.dict["__exportlist"]:
716 self.dict["__exportlist"]["_content"] = set()
717 self.dict["__exportlist"]["_content"].add(var)
718
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800719 def getVarFlag(self, var, flag, expand=True, noweakdefault=False, parsing=False, retparser=False):
720 if flag == "_content":
721 cachename = var
722 else:
723 if not flag:
724 bb.warn("Calling getVarFlag with flag unset is invalid")
725 return None
726 cachename = var + "[" + flag + "]"
727
728 if expand and cachename in self.expand_cache:
729 return self.expand_cache[cachename].value
730
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500731 local_var, overridedata = self._findVar(var)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500732 value = None
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800733 removes = set()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500734 if flag == "_content" and overridedata is not None and not parsing:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500735 match = False
736 active = {}
737 self.need_overrides()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500738 for (r, o) in overridedata:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500739 # What about double overrides both with "_" in the name?
740 if o in self.overridesset:
741 active[o] = r
742 elif "_" in o:
743 if set(o.split("_")).issubset(self.overridesset):
744 active[o] = r
745
746 mod = True
747 while mod:
748 mod = False
749 for o in self.overrides:
750 for a in active.copy():
751 if a.endswith("_" + o):
752 t = active[a]
753 del active[a]
754 active[a.replace("_" + o, "")] = t
755 mod = True
756 elif a == o:
757 match = active[a]
758 del active[a]
759 if match:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800760 value, subparser = self.getVarFlag(match, "_content", False, retparser=True)
761 if hasattr(subparser, "removes"):
762 # We have to carry the removes from the overridden variable to apply at the
763 # end of processing
764 removes = subparser.removes
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500765
766 if local_var is not None and value is None:
767 if flag in local_var:
768 value = copy.copy(local_var[flag])
769 elif flag == "_content" and "_defaultval" in local_var and not noweakdefault:
770 value = copy.copy(local_var["_defaultval"])
771
772
773 if flag == "_content" and local_var is not None and "_append" in local_var and not parsing:
774 if not value:
775 value = ""
776 self.need_overrides()
777 for (r, o) in local_var["_append"]:
778 match = True
779 if o:
780 for o2 in o.split("_"):
781 if not o2 in self.overrides:
782 match = False
783 if match:
784 value = value + r
785
786 if flag == "_content" and local_var is not None and "_prepend" in local_var and not parsing:
787 if not value:
788 value = ""
789 self.need_overrides()
790 for (r, o) in local_var["_prepend"]:
791
792 match = True
793 if o:
794 for o2 in o.split("_"):
795 if not o2 in self.overrides:
796 match = False
797 if match:
798 value = r + value
799
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800800 parser = None
801 if expand or retparser:
802 parser = self.expandWithRefs(value, cachename)
803 if expand:
804 value = parser.value
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500805
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800806 if value and flag == "_content" and local_var is not None and "_remove" in local_var and not parsing:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500807 self.need_overrides()
808 for (r, o) in local_var["_remove"]:
809 match = True
810 if o:
811 for o2 in o.split("_"):
812 if not o2 in self.overrides:
813 match = False
814 if match:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800815 removes.add(r)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500816
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800817 if value and flag == "_content" and not parsing:
818 if removes and parser:
819 expanded_removes = {}
820 for r in removes:
821 expanded_removes[r] = self.expand(r).split()
822
823 parser.removes = set()
824 val = ""
825 for v in __whitespace_split__.split(parser.value):
826 skip = False
827 for r in removes:
828 if v in expanded_removes[r]:
829 parser.removes.add(r)
830 skip = True
831 if skip:
832 continue
833 val = val + v
834 parser.value = val
835 if expand:
836 value = parser.value
837
838 if parser:
839 self.expand_cache[cachename] = parser
840
841 if retparser:
842 return value, parser
843
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500844 return value
845
846 def delVarFlag(self, var, flag, **loginfo):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800847 self.expand_cache = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500848 if '_remote_data' in self.dict:
849 connector = self.dict["_remote_data"]["_content"]
850 res = connector.delVarFlag(var, flag)
851 if not res:
852 return
853
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500854 local_var, _ = self._findVar(var)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500855 if not local_var:
856 return
857 if not var in self.dict:
858 self._makeShadowCopy(var)
859
860 if var in self.dict and flag in self.dict[var]:
861 loginfo['detail'] = ""
862 loginfo['op'] = 'delFlag'
863 loginfo['flag'] = flag
864 self.varhistory.record(**loginfo)
865
866 del self.dict[var][flag]
867
868 def appendVarFlag(self, var, flag, value, **loginfo):
869 loginfo['op'] = 'append'
870 loginfo['flag'] = flag
871 self.varhistory.record(**loginfo)
872 newvalue = (self.getVarFlag(var, flag, False) or "") + value
873 self.setVarFlag(var, flag, newvalue, ignore=True)
874
875 def prependVarFlag(self, var, flag, value, **loginfo):
876 loginfo['op'] = 'prepend'
877 loginfo['flag'] = flag
878 self.varhistory.record(**loginfo)
879 newvalue = value + (self.getVarFlag(var, flag, False) or "")
880 self.setVarFlag(var, flag, newvalue, ignore=True)
881
882 def setVarFlags(self, var, flags, **loginfo):
883 self.expand_cache = {}
884 infer_caller_details(loginfo)
885 if not var in self.dict:
886 self._makeShadowCopy(var)
887
888 for i in flags:
889 if i == "_content":
890 continue
891 loginfo['flag'] = i
892 loginfo['detail'] = flags[i]
893 self.varhistory.record(**loginfo)
894 self.dict[var][i] = flags[i]
895
896 def getVarFlags(self, var, expand = False, internalflags=False):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500897 local_var, _ = self._findVar(var)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500898 flags = {}
899
900 if local_var:
901 for i in local_var:
902 if i.startswith("_") and not internalflags:
903 continue
904 flags[i] = local_var[i]
905 if expand and i in expand:
906 flags[i] = self.expand(flags[i], var + "[" + i + "]")
907 if len(flags) == 0:
908 return None
909 return flags
910
911
912 def delVarFlags(self, var, **loginfo):
913 self.expand_cache = {}
914 if not var in self.dict:
915 self._makeShadowCopy(var)
916
917 if var in self.dict:
918 content = None
919
920 loginfo['op'] = 'delete flags'
921 self.varhistory.record(**loginfo)
922
923 # try to save the content
924 if "_content" in self.dict[var]:
925 content = self.dict[var]["_content"]
926 self.dict[var] = {}
927 self.dict[var]["_content"] = content
928 else:
929 del self.dict[var]
930
931 def createCopy(self):
932 """
933 Create a copy of self by setting _data to self
934 """
935 # we really want this to be a DataSmart...
936 data = DataSmart()
937 data.dict["_data"] = self.dict
938 data.varhistory = self.varhistory.copy()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500939 data.varhistory.dataroot = data
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500940 data.inchistory = self.inchistory.copy()
941
942 data._tracking = self._tracking
943
944 data.overrides = None
945 data.overridevars = copy.copy(self.overridevars)
946 # Should really be a deepcopy but has heavy overhead.
947 # Instead, we're careful with writes.
948 data.overridedata = copy.copy(self.overridedata)
949
950 return data
951
952 def expandVarref(self, variable, parents=False):
953 """Find all references to variable in the data and expand it
954 in place, optionally descending to parent datastores."""
955
956 if parents:
957 keys = iter(self)
958 else:
959 keys = self.localkeys()
960
961 ref = '${%s}' % variable
962 value = self.getVar(variable, False)
963 for key in keys:
964 referrervalue = self.getVar(key, False)
965 if referrervalue and ref in referrervalue:
966 self.setVar(key, referrervalue.replace(ref, value))
967
968 def localkeys(self):
969 for key in self.dict:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500970 if key not in ['_data', '_remote_data']:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500971 yield key
972
973 def __iter__(self):
974 deleted = set()
975 overrides = set()
976 def keylist(d):
977 klist = set()
978 for key in d:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500979 if key in ["_data", "_remote_data"]:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500980 continue
981 if key in deleted:
982 continue
983 if key in overrides:
984 continue
985 if not d[key]:
986 deleted.add(key)
987 continue
988 klist.add(key)
989
990 if "_data" in d:
991 klist |= keylist(d["_data"])
992
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500993 if "_remote_data" in d:
994 connector = d["_remote_data"]["_content"]
995 for key in connector.getKeys():
996 if key in deleted:
997 continue
998 klist.add(key)
999
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001000 return klist
1001
1002 self.need_overrides()
1003 for var in self.overridedata:
1004 for (r, o) in self.overridedata[var]:
1005 if o in self.overridesset:
1006 overrides.add(var)
1007 elif "_" in o:
1008 if set(o.split("_")).issubset(self.overridesset):
1009 overrides.add(var)
1010
1011 for k in keylist(self.dict):
1012 yield k
1013
1014 for k in overrides:
1015 yield k
1016
1017 def __len__(self):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001018 return len(frozenset(iter(self)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001019
1020 def __getitem__(self, item):
1021 value = self.getVar(item, False)
1022 if value is None:
1023 raise KeyError(item)
1024 else:
1025 return value
1026
1027 def __setitem__(self, var, value):
1028 self.setVar(var, value)
1029
1030 def __delitem__(self, var):
1031 self.delVar(var)
1032
1033 def get_hash(self):
1034 data = {}
1035 d = self.createCopy()
1036 bb.data.expandKeys(d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001037
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001038 config_whitelist = set((d.getVar("BB_HASHCONFIG_WHITELIST") or "").split())
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001039 keys = set(key for key in iter(d) if not key.startswith("__"))
1040 for key in keys:
1041 if key in config_whitelist:
1042 continue
1043
1044 value = d.getVar(key, False) or ""
1045 data.update({key:value})
1046
1047 varflags = d.getVarFlags(key, internalflags = True)
1048 if not varflags:
1049 continue
1050 for f in varflags:
1051 if f == "_content":
1052 continue
1053 data.update({'%s[%s]' % (key, f):varflags[f]})
1054
1055 for key in ["__BBTASKS", "__BBANONFUNCS", "__BBHANDLERS"]:
1056 bb_list = d.getVar(key, False) or []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001057 data.update({key:str(bb_list)})
1058
1059 if key == "__BBANONFUNCS":
1060 for i in bb_list:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001061 value = d.getVar(i, False) or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001062 data.update({i:value})
1063
1064 data_str = str([(k, data[k]) for k in sorted(data.keys())])
Brad Bishop19323692019-04-05 15:28:33 -04001065 return hashlib.sha256(data_str.encode("utf-8")).hexdigest()