blob: 07db7be97aa5eb65b5ccb424a39330c7ce901ea7 [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"]
Brad Bishop19323692019-04-05 15:28:33 -040042__setvar_regexp__ = re.compile(r'(?P<base>.*?)(?P<keyword>_append|_prepend|_remove)(_(?P<add>[^A-Z]*))?$')
43__expand_var_regexp__ = re.compile(r"\${[a-zA-Z0-9\-_+./~]+?}")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050044__expand_python_regexp__ = re.compile(r"\${@.+?}")
Brad Bishop19323692019-04-05 15:28:33 -040045__whitespace_split__ = re.compile(r'(\s)')
46__override_regexp__ = re.compile(r'[a-z0-9]+')
Patrick Williamsc124f4f2015-09-15 14:41:29 -050047
48def infer_caller_details(loginfo, parent = False, varval = True):
49 """Save the caller the trouble of specifying everything."""
50 # Save effort.
51 if 'ignore' in loginfo and loginfo['ignore']:
52 return
53 # If nothing was provided, mark this as possibly unneeded.
54 if not loginfo:
55 loginfo['ignore'] = True
56 return
57 # Infer caller's likely values for variable (var) and value (value),
58 # to reduce clutter in the rest of the code.
59 above = None
60 def set_above():
61 try:
62 raise Exception
63 except Exception:
64 tb = sys.exc_info()[2]
65 if parent:
66 return tb.tb_frame.f_back.f_back.f_back
67 else:
68 return tb.tb_frame.f_back.f_back
69
70 if varval and ('variable' not in loginfo or 'detail' not in loginfo):
71 if not above:
72 above = set_above()
73 lcls = above.f_locals.items()
74 for k, v in lcls:
75 if k == 'value' and 'detail' not in loginfo:
76 loginfo['detail'] = v
77 if k == 'var' and 'variable' not in loginfo:
78 loginfo['variable'] = v
79 # Infer file/line/function from traceback
80 # Don't use traceback.extract_stack() since it fills the line contents which
81 # we don't need and that hits stat syscalls
82 if 'file' not in loginfo:
83 if not above:
84 above = set_above()
85 f = above.f_back
86 line = f.f_lineno
87 file = f.f_code.co_filename
88 func = f.f_code.co_name
89 loginfo['file'] = file
90 loginfo['line'] = line
91 if func not in loginfo:
92 loginfo['func'] = func
93
94class VariableParse:
95 def __init__(self, varname, d, val = None):
96 self.varname = varname
97 self.d = d
98 self.value = val
99
100 self.references = set()
101 self.execs = set()
102 self.contains = {}
103
104 def var_sub(self, match):
105 key = match.group()[2:-1]
106 if self.varname and key:
107 if self.varname == key:
108 raise Exception("variable %s references itself!" % self.varname)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800109 var = self.d.getVarFlag(key, "_content")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500110 self.references.add(key)
111 if var is not None:
112 return var
113 else:
114 return match.group()
115
116 def python_sub(self, match):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500117 if isinstance(match, str):
118 code = match
119 else:
120 code = match.group()[3:-1]
121
122 if "_remote_data" in self.d:
123 connector = self.d["_remote_data"]
124 return connector.expandPythonRef(self.varname, code, self.d)
125
Brad Bishop19323692019-04-05 15:28:33 -0400126 if self.varname:
127 varname = 'Var <%s>' % self.varname
128 else:
129 varname = '<expansion>'
130 codeobj = compile(code.strip(), varname, "eval")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500131
132 parser = bb.codeparser.PythonParser(self.varname, logger)
133 parser.parse_python(code)
134 if self.varname:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500135 vardeps = self.d.getVarFlag(self.varname, "vardeps")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500136 if vardeps is None:
137 parser.log.flush()
138 else:
139 parser.log.flush()
140 self.references |= parser.references
141 self.execs |= parser.execs
142
143 for k in parser.contains:
144 if k not in self.contains:
145 self.contains[k] = parser.contains[k].copy()
146 else:
147 self.contains[k].update(parser.contains[k])
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600148 value = utils.better_eval(codeobj, DataContext(self.d), {'d' : self.d})
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500149 return str(value)
150
151
152class DataContext(dict):
153 def __init__(self, metadata, **kwargs):
154 self.metadata = metadata
155 dict.__init__(self, **kwargs)
156 self['d'] = metadata
157
158 def __missing__(self, key):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500159 value = self.metadata.getVar(key)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500160 if value is None or self.metadata.getVarFlag(key, 'func', False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500161 raise KeyError(key)
162 else:
163 return value
164
165class ExpansionError(Exception):
166 def __init__(self, varname, expression, exception):
167 self.expression = expression
168 self.variablename = varname
169 self.exception = exception
170 if varname:
171 if expression:
172 self.msg = "Failure expanding variable %s, expression was %s which triggered exception %s: %s" % (varname, expression, type(exception).__name__, exception)
173 else:
174 self.msg = "Failure expanding variable %s: %s: %s" % (varname, type(exception).__name__, exception)
175 else:
176 self.msg = "Failure expanding expression %s which triggered exception %s: %s" % (expression, type(exception).__name__, exception)
177 Exception.__init__(self, self.msg)
178 self.args = (varname, expression, exception)
179 def __str__(self):
180 return self.msg
181
182class IncludeHistory(object):
183 def __init__(self, parent = None, filename = '[TOP LEVEL]'):
184 self.parent = parent
185 self.filename = filename
186 self.children = []
187 self.current = self
188
189 def copy(self):
190 new = IncludeHistory(self.parent, self.filename)
191 for c in self.children:
192 new.children.append(c)
193 return new
194
195 def include(self, filename):
196 newfile = IncludeHistory(self.current, filename)
197 self.current.children.append(newfile)
198 self.current = newfile
199 return self
200
201 def __enter__(self):
202 pass
203
204 def __exit__(self, a, b, c):
205 if self.current.parent:
206 self.current = self.current.parent
207 else:
208 bb.warn("Include log: Tried to finish '%s' at top level." % filename)
209 return False
210
211 def emit(self, o, level = 0):
212 """Emit an include history file, and its children."""
213 if level:
214 spaces = " " * (level - 1)
215 o.write("# %s%s" % (spaces, self.filename))
216 if len(self.children) > 0:
217 o.write(" includes:")
218 else:
219 o.write("#\n# INCLUDE HISTORY:\n#")
220 level = level + 1
221 for child in self.children:
222 o.write("\n")
223 child.emit(o, level)
224
225class VariableHistory(object):
226 def __init__(self, dataroot):
227 self.dataroot = dataroot
228 self.variables = COWDictBase.copy()
229
230 def copy(self):
231 new = VariableHistory(self.dataroot)
232 new.variables = self.variables.copy()
233 return new
234
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500235 def __getstate__(self):
236 vardict = {}
237 for k, v in self.variables.iteritems():
238 vardict[k] = v
239 return {'dataroot': self.dataroot,
240 'variables': vardict}
241
242 def __setstate__(self, state):
243 self.dataroot = state['dataroot']
244 self.variables = COWDictBase.copy()
245 for k, v in state['variables'].items():
246 self.variables[k] = v
247
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500248 def record(self, *kwonly, **loginfo):
249 if not self.dataroot._tracking:
250 return
251 if len(kwonly) > 0:
252 raise TypeError
253 infer_caller_details(loginfo, parent = True)
254 if 'ignore' in loginfo and loginfo['ignore']:
255 return
256 if 'op' not in loginfo or not loginfo['op']:
257 loginfo['op'] = 'set'
258 if 'detail' in loginfo:
259 loginfo['detail'] = str(loginfo['detail'])
260 if 'variable' not in loginfo or 'file' not in loginfo:
261 raise ValueError("record() missing variable or file.")
262 var = loginfo['variable']
263
264 if var not in self.variables:
265 self.variables[var] = []
266 if not isinstance(self.variables[var], list):
267 return
268 if 'nodups' in loginfo and loginfo in self.variables[var]:
269 return
270 self.variables[var].append(loginfo.copy())
271
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800272 def rename_variable_hist(self, oldvar, newvar):
273 if not self.dataroot._tracking:
274 return
275 if oldvar not in self.variables:
276 return
277 if newvar not in self.variables:
278 self.variables[newvar] = []
279 for i in self.variables[oldvar]:
280 self.variables[newvar].append(i.copy())
281
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500282 def variable(self, var):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500283 remote_connector = self.dataroot.getVar('_remote_data', False)
284 if remote_connector:
285 varhistory = remote_connector.getVarHistory(var)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500286 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500287 varhistory = []
288
289 if var in self.variables:
290 varhistory.extend(self.variables[var])
291 return varhistory
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500292
293 def emit(self, var, oval, val, o, d):
294 history = self.variable(var)
295
296 # Append override history
297 if var in d.overridedata:
298 for (r, override) in d.overridedata[var]:
299 for event in self.variable(r):
300 loginfo = event.copy()
301 if 'flag' in loginfo and not loginfo['flag'].startswith("_"):
302 continue
303 loginfo['variable'] = var
304 loginfo['op'] = 'override[%s]:%s' % (override, loginfo['op'])
305 history.append(loginfo)
306
307 commentVal = re.sub('\n', '\n#', str(oval))
308 if history:
309 if len(history) == 1:
310 o.write("#\n# $%s\n" % var)
311 else:
312 o.write("#\n# $%s [%d operations]\n" % (var, len(history)))
313 for event in history:
314 # o.write("# %s\n" % str(event))
315 if 'func' in event:
316 # If we have a function listed, this is internal
317 # code, not an operation in a config file, and the
318 # full path is distracting.
319 event['file'] = re.sub('.*/', '', event['file'])
320 display_func = ' [%s]' % event['func']
321 else:
322 display_func = ''
323 if 'flag' in event:
324 flag = '[%s] ' % (event['flag'])
325 else:
326 flag = ''
327 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'])))
328 if len(history) > 1:
329 o.write("# pre-expansion value:\n")
330 o.write('# "%s"\n' % (commentVal))
331 else:
332 o.write("#\n# $%s\n# [no history recorded]\n#\n" % var)
333 o.write('# "%s"\n' % (commentVal))
334
335 def get_variable_files(self, var):
336 """Get the files where operations are made on a variable"""
337 var_history = self.variable(var)
338 files = []
339 for event in var_history:
340 files.append(event['file'])
341 return files
342
343 def get_variable_lines(self, var, f):
344 """Get the line where a operation is made on a variable in file f"""
345 var_history = self.variable(var)
346 lines = []
347 for event in var_history:
348 if f== event['file']:
349 line = event['line']
350 lines.append(line)
351 return lines
352
353 def get_variable_items_files(self, var, d):
354 """
355 Use variable history to map items added to a list variable and
356 the files in which they were added.
357 """
358 history = self.variable(var)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500359 finalitems = (d.getVar(var) or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500360 filemap = {}
361 isset = False
362 for event in history:
363 if 'flag' in event:
364 continue
365 if event['op'] == '_remove':
366 continue
367 if isset and event['op'] == 'set?':
368 continue
369 isset = True
370 items = d.expand(event['detail']).split()
371 for item in items:
372 # This is a little crude but is belt-and-braces to avoid us
373 # having to handle every possible operation type specifically
374 if item in finalitems and not item in filemap:
375 filemap[item] = event['file']
376 return filemap
377
378 def del_var_history(self, var, f=None, line=None):
379 """If file f and line are not given, the entire history of var is deleted"""
380 if var in self.variables:
381 if f and line:
382 self.variables[var] = [ x for x in self.variables[var] if x['file']!=f and x['line']!=line]
383 else:
384 self.variables[var] = []
385
386class DataSmart(MutableMapping):
387 def __init__(self):
388 self.dict = {}
389
390 self.inchistory = IncludeHistory()
391 self.varhistory = VariableHistory(self)
392 self._tracking = False
393
394 self.expand_cache = {}
395
396 # cookie monster tribute
397 # Need to be careful about writes to overridedata as
398 # its only a shallow copy, could influence other data store
399 # copies!
400 self.overridedata = {}
401 self.overrides = None
402 self.overridevars = set(["OVERRIDES", "FILE"])
403 self.inoverride = False
404
405 def enableTracking(self):
406 self._tracking = True
407
408 def disableTracking(self):
409 self._tracking = False
410
411 def expandWithRefs(self, s, varname):
412
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600413 if not isinstance(s, str): # sanity check
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500414 return VariableParse(varname, self, s)
415
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500416 varparse = VariableParse(varname, self)
417
418 while s.find('${') != -1:
419 olds = s
420 try:
421 s = __expand_var_regexp__.sub(varparse.var_sub, s)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500422 try:
423 s = __expand_python_regexp__.sub(varparse.python_sub, s)
424 except SyntaxError as e:
425 # Likely unmatched brackets, just don't expand the expression
426 if e.msg != "EOL while scanning string literal":
427 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500428 if s == olds:
429 break
430 except ExpansionError:
431 raise
432 except bb.parse.SkipRecipe:
433 raise
434 except Exception as exc:
Brad Bishop19323692019-04-05 15:28:33 -0400435 tb = sys.exc_info()[2]
436 raise ExpansionError(varname, s, exc).with_traceback(tb) from exc
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500437
438 varparse.value = s
439
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500440 return varparse
441
442 def expand(self, s, varname = None):
443 return self.expandWithRefs(s, varname).value
444
445 def finalize(self, parent = False):
446 return
447
448 def internal_finalize(self, parent = False):
449 """Performs final steps upon the datastore, including application of overrides"""
450 self.overrides = None
451
452 def need_overrides(self):
Patrick Williamsd7e96312015-09-22 08:09:05 -0500453 if self.overrides is not None:
454 return
455 if self.inoverride:
456 return
457 for count in range(5):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500458 self.inoverride = True
459 # Can end up here recursively so setup dummy values
460 self.overrides = []
461 self.overridesset = set()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500462 self.overrides = (self.getVar("OVERRIDES") or "").split(":") or []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500463 self.overridesset = set(self.overrides)
464 self.inoverride = False
465 self.expand_cache = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500466 newoverrides = (self.getVar("OVERRIDES") or "").split(":") or []
Patrick Williamsd7e96312015-09-22 08:09:05 -0500467 if newoverrides == self.overrides:
468 break
469 self.overrides = newoverrides
470 self.overridesset = set(self.overrides)
471 else:
472 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 -0500473
474 def initVar(self, var):
475 self.expand_cache = {}
476 if not var in self.dict:
477 self.dict[var] = {}
478
479 def _findVar(self, var):
480 dest = self.dict
481 while dest:
482 if var in dest:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500483 return dest[var], self.overridedata.get(var, None)
484
485 if "_remote_data" in dest:
486 connector = dest["_remote_data"]["_content"]
487 return connector.getVar(var)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500488
489 if "_data" not in dest:
490 break
491 dest = dest["_data"]
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500492 return None, self.overridedata.get(var, None)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500493
494 def _makeShadowCopy(self, var):
495 if var in self.dict:
496 return
497
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500498 local_var, _ = self._findVar(var)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500499
500 if local_var:
501 self.dict[var] = copy.copy(local_var)
502 else:
503 self.initVar(var)
504
505
506 def setVar(self, var, value, **loginfo):
507 #print("var=" + str(var) + " val=" + str(value))
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800508 self.expand_cache = {}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500509 parsing=False
510 if 'parsing' in loginfo:
511 parsing=True
512
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500513 if '_remote_data' in self.dict:
514 connector = self.dict["_remote_data"]["_content"]
515 res = connector.setVar(var, value)
516 if not res:
517 return
518
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500519 if 'op' not in loginfo:
520 loginfo['op'] = "set"
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800521
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500522 match = __setvar_regexp__.match(var)
523 if match and match.group("keyword") in __setvar_keyword__:
524 base = match.group('base')
525 keyword = match.group("keyword")
526 override = match.group('add')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500527 l = self.getVarFlag(base, keyword, False) or []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500528 l.append([value, override])
529 self.setVarFlag(base, keyword, l, ignore=True)
530 # And cause that to be recorded:
531 loginfo['detail'] = value
532 loginfo['variable'] = base
533 if override:
534 loginfo['op'] = '%s[%s]' % (keyword, override)
535 else:
536 loginfo['op'] = keyword
537 self.varhistory.record(**loginfo)
538 # todo make sure keyword is not __doc__ or __module__
539 # pay the cookie monster
540
541 # more cookies for the cookie monster
542 if '_' in var:
543 self._setvar_update_overrides(base, **loginfo)
544
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500545 if base in self.overridevars:
Patrick Williamsd7e96312015-09-22 08:09:05 -0500546 self._setvar_update_overridevars(var, value)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500547 return
548
549 if not var in self.dict:
550 self._makeShadowCopy(var)
551
552 if not parsing:
553 if "_append" in self.dict[var]:
554 del self.dict[var]["_append"]
555 if "_prepend" in self.dict[var]:
556 del self.dict[var]["_prepend"]
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500557 if "_remove" in self.dict[var]:
558 del self.dict[var]["_remove"]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500559 if var in self.overridedata:
560 active = []
561 self.need_overrides()
562 for (r, o) in self.overridedata[var]:
563 if o in self.overridesset:
564 active.append(r)
565 elif "_" in o:
566 if set(o.split("_")).issubset(self.overridesset):
567 active.append(r)
568 for a in active:
569 self.delVar(a)
570 del self.overridedata[var]
571
572 # more cookies for the cookie monster
573 if '_' in var:
574 self._setvar_update_overrides(var, **loginfo)
575
576 # setting var
577 self.dict[var]["_content"] = value
578 self.varhistory.record(**loginfo)
579
580 if var in self.overridevars:
Patrick Williamsd7e96312015-09-22 08:09:05 -0500581 self._setvar_update_overridevars(var, value)
582
583 def _setvar_update_overridevars(self, var, value):
584 vardata = self.expandWithRefs(value, var)
585 new = vardata.references
586 new.update(vardata.contains.keys())
587 while not new.issubset(self.overridevars):
588 nextnew = set()
589 self.overridevars.update(new)
590 for i in new:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500591 vardata = self.expandWithRefs(self.getVar(i), i)
Patrick Williamsd7e96312015-09-22 08:09:05 -0500592 nextnew.update(vardata.references)
593 nextnew.update(vardata.contains.keys())
594 new = nextnew
595 self.internal_finalize(True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500596
597 def _setvar_update_overrides(self, var, **loginfo):
598 # aka pay the cookie monster
599 override = var[var.rfind('_')+1:]
600 shortvar = var[:var.rfind('_')]
Brad Bishop19323692019-04-05 15:28:33 -0400601 while override and __override_regexp__.match(override):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500602 if shortvar not in self.overridedata:
603 self.overridedata[shortvar] = []
604 if [var, override] not in self.overridedata[shortvar]:
605 # Force CoW by recreating the list first
606 self.overridedata[shortvar] = list(self.overridedata[shortvar])
607 self.overridedata[shortvar].append([var, override])
608 override = None
609 if "_" in shortvar:
610 override = var[shortvar.rfind('_')+1:]
611 shortvar = var[:shortvar.rfind('_')]
612 if len(shortvar) == 0:
613 override = None
614
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500615 def getVar(self, var, expand=True, noweakdefault=False, parsing=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500616 return self.getVarFlag(var, "_content", expand, noweakdefault, parsing)
617
618 def renameVar(self, key, newkey, **loginfo):
619 """
620 Rename the variable key to newkey
621 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500622 if '_remote_data' in self.dict:
623 connector = self.dict["_remote_data"]["_content"]
624 res = connector.renameVar(key, newkey)
625 if not res:
626 return
627
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500628 val = self.getVar(key, 0, parsing=True)
629 if val is not None:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800630 self.varhistory.rename_variable_hist(key, newkey)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500631 loginfo['variable'] = newkey
632 loginfo['op'] = 'rename from %s' % key
633 loginfo['detail'] = val
634 self.varhistory.record(**loginfo)
635 self.setVar(newkey, val, ignore=True, parsing=True)
636
637 for i in (__setvar_keyword__):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500638 src = self.getVarFlag(key, i, False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500639 if src is None:
640 continue
641
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500642 dest = self.getVarFlag(newkey, i, False) or []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500643 dest.extend(src)
644 self.setVarFlag(newkey, i, dest, ignore=True)
645
646 if key in self.overridedata:
647 self.overridedata[newkey] = []
648 for (v, o) in self.overridedata[key]:
649 self.overridedata[newkey].append([v.replace(key, newkey), o])
650 self.renameVar(v, v.replace(key, newkey))
651
652 if '_' in newkey and val is None:
653 self._setvar_update_overrides(newkey, **loginfo)
654
655 loginfo['variable'] = key
656 loginfo['op'] = 'rename (to)'
657 loginfo['detail'] = newkey
658 self.varhistory.record(**loginfo)
659 self.delVar(key, ignore=True)
660
661 def appendVar(self, var, value, **loginfo):
662 loginfo['op'] = 'append'
663 self.varhistory.record(**loginfo)
664 self.setVar(var + "_append", value, ignore=True, parsing=True)
665
666 def prependVar(self, var, value, **loginfo):
667 loginfo['op'] = 'prepend'
668 self.varhistory.record(**loginfo)
669 self.setVar(var + "_prepend", value, ignore=True, parsing=True)
670
671 def delVar(self, var, **loginfo):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800672 self.expand_cache = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500673 if '_remote_data' in self.dict:
674 connector = self.dict["_remote_data"]["_content"]
675 res = connector.delVar(var)
676 if not res:
677 return
678
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500679 loginfo['detail'] = ""
680 loginfo['op'] = 'del'
681 self.varhistory.record(**loginfo)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500682 self.dict[var] = {}
683 if var in self.overridedata:
684 del self.overridedata[var]
685 if '_' in var:
686 override = var[var.rfind('_')+1:]
687 shortvar = var[:var.rfind('_')]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500688 while override and override.islower():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500689 try:
690 if shortvar in self.overridedata:
691 # Force CoW by recreating the list first
692 self.overridedata[shortvar] = list(self.overridedata[shortvar])
693 self.overridedata[shortvar].remove([var, override])
694 except ValueError as e:
695 pass
696 override = None
697 if "_" in shortvar:
698 override = var[shortvar.rfind('_')+1:]
699 shortvar = var[:shortvar.rfind('_')]
700 if len(shortvar) == 0:
701 override = None
702
703 def setVarFlag(self, var, flag, value, **loginfo):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800704 self.expand_cache = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500705 if '_remote_data' in self.dict:
706 connector = self.dict["_remote_data"]["_content"]
707 res = connector.setVarFlag(var, flag, value)
708 if not res:
709 return
710
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500711 if 'op' not in loginfo:
712 loginfo['op'] = "set"
713 loginfo['flag'] = flag
714 self.varhistory.record(**loginfo)
715 if not var in self.dict:
716 self._makeShadowCopy(var)
717 self.dict[var][flag] = value
718
719 if flag == "_defaultval" and '_' in var:
720 self._setvar_update_overrides(var, **loginfo)
Patrick Williamsd7e96312015-09-22 08:09:05 -0500721 if flag == "_defaultval" and var in self.overridevars:
722 self._setvar_update_overridevars(var, value)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500723
724 if flag == "unexport" or flag == "export":
725 if not "__exportlist" in self.dict:
726 self._makeShadowCopy("__exportlist")
727 if not "_content" in self.dict["__exportlist"]:
728 self.dict["__exportlist"]["_content"] = set()
729 self.dict["__exportlist"]["_content"].add(var)
730
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800731 def getVarFlag(self, var, flag, expand=True, noweakdefault=False, parsing=False, retparser=False):
732 if flag == "_content":
733 cachename = var
734 else:
735 if not flag:
736 bb.warn("Calling getVarFlag with flag unset is invalid")
737 return None
738 cachename = var + "[" + flag + "]"
739
740 if expand and cachename in self.expand_cache:
741 return self.expand_cache[cachename].value
742
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500743 local_var, overridedata = self._findVar(var)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500744 value = None
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800745 removes = set()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500746 if flag == "_content" and overridedata is not None and not parsing:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500747 match = False
748 active = {}
749 self.need_overrides()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500750 for (r, o) in overridedata:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500751 # What about double overrides both with "_" in the name?
752 if o in self.overridesset:
753 active[o] = r
754 elif "_" in o:
755 if set(o.split("_")).issubset(self.overridesset):
756 active[o] = r
757
758 mod = True
759 while mod:
760 mod = False
761 for o in self.overrides:
762 for a in active.copy():
763 if a.endswith("_" + o):
764 t = active[a]
765 del active[a]
766 active[a.replace("_" + o, "")] = t
767 mod = True
768 elif a == o:
769 match = active[a]
770 del active[a]
771 if match:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800772 value, subparser = self.getVarFlag(match, "_content", False, retparser=True)
773 if hasattr(subparser, "removes"):
774 # We have to carry the removes from the overridden variable to apply at the
775 # end of processing
776 removes = subparser.removes
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500777
778 if local_var is not None and value is None:
779 if flag in local_var:
780 value = copy.copy(local_var[flag])
781 elif flag == "_content" and "_defaultval" in local_var and not noweakdefault:
782 value = copy.copy(local_var["_defaultval"])
783
784
785 if flag == "_content" and local_var is not None and "_append" in local_var and not parsing:
786 if not value:
787 value = ""
788 self.need_overrides()
789 for (r, o) in local_var["_append"]:
790 match = True
791 if o:
792 for o2 in o.split("_"):
793 if not o2 in self.overrides:
794 match = False
795 if match:
796 value = value + r
797
798 if flag == "_content" and local_var is not None and "_prepend" in local_var and not parsing:
799 if not value:
800 value = ""
801 self.need_overrides()
802 for (r, o) in local_var["_prepend"]:
803
804 match = True
805 if o:
806 for o2 in o.split("_"):
807 if not o2 in self.overrides:
808 match = False
809 if match:
810 value = r + value
811
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800812 parser = None
813 if expand or retparser:
814 parser = self.expandWithRefs(value, cachename)
815 if expand:
816 value = parser.value
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500817
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800818 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 -0500819 self.need_overrides()
820 for (r, o) in local_var["_remove"]:
821 match = True
822 if o:
823 for o2 in o.split("_"):
824 if not o2 in self.overrides:
825 match = False
826 if match:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800827 removes.add(r)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500828
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800829 if value and flag == "_content" and not parsing:
830 if removes and parser:
831 expanded_removes = {}
832 for r in removes:
833 expanded_removes[r] = self.expand(r).split()
834
835 parser.removes = set()
836 val = ""
837 for v in __whitespace_split__.split(parser.value):
838 skip = False
839 for r in removes:
840 if v in expanded_removes[r]:
841 parser.removes.add(r)
842 skip = True
843 if skip:
844 continue
845 val = val + v
846 parser.value = val
847 if expand:
848 value = parser.value
849
850 if parser:
851 self.expand_cache[cachename] = parser
852
853 if retparser:
854 return value, parser
855
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500856 return value
857
858 def delVarFlag(self, var, flag, **loginfo):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800859 self.expand_cache = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500860 if '_remote_data' in self.dict:
861 connector = self.dict["_remote_data"]["_content"]
862 res = connector.delVarFlag(var, flag)
863 if not res:
864 return
865
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500866 local_var, _ = self._findVar(var)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500867 if not local_var:
868 return
869 if not var in self.dict:
870 self._makeShadowCopy(var)
871
872 if var in self.dict and flag in self.dict[var]:
873 loginfo['detail'] = ""
874 loginfo['op'] = 'delFlag'
875 loginfo['flag'] = flag
876 self.varhistory.record(**loginfo)
877
878 del self.dict[var][flag]
879
880 def appendVarFlag(self, var, flag, value, **loginfo):
881 loginfo['op'] = 'append'
882 loginfo['flag'] = flag
883 self.varhistory.record(**loginfo)
884 newvalue = (self.getVarFlag(var, flag, False) or "") + value
885 self.setVarFlag(var, flag, newvalue, ignore=True)
886
887 def prependVarFlag(self, var, flag, value, **loginfo):
888 loginfo['op'] = 'prepend'
889 loginfo['flag'] = flag
890 self.varhistory.record(**loginfo)
891 newvalue = value + (self.getVarFlag(var, flag, False) or "")
892 self.setVarFlag(var, flag, newvalue, ignore=True)
893
894 def setVarFlags(self, var, flags, **loginfo):
895 self.expand_cache = {}
896 infer_caller_details(loginfo)
897 if not var in self.dict:
898 self._makeShadowCopy(var)
899
900 for i in flags:
901 if i == "_content":
902 continue
903 loginfo['flag'] = i
904 loginfo['detail'] = flags[i]
905 self.varhistory.record(**loginfo)
906 self.dict[var][i] = flags[i]
907
908 def getVarFlags(self, var, expand = False, internalflags=False):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500909 local_var, _ = self._findVar(var)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500910 flags = {}
911
912 if local_var:
913 for i in local_var:
914 if i.startswith("_") and not internalflags:
915 continue
916 flags[i] = local_var[i]
917 if expand and i in expand:
918 flags[i] = self.expand(flags[i], var + "[" + i + "]")
919 if len(flags) == 0:
920 return None
921 return flags
922
923
924 def delVarFlags(self, var, **loginfo):
925 self.expand_cache = {}
926 if not var in self.dict:
927 self._makeShadowCopy(var)
928
929 if var in self.dict:
930 content = None
931
932 loginfo['op'] = 'delete flags'
933 self.varhistory.record(**loginfo)
934
935 # try to save the content
936 if "_content" in self.dict[var]:
937 content = self.dict[var]["_content"]
938 self.dict[var] = {}
939 self.dict[var]["_content"] = content
940 else:
941 del self.dict[var]
942
943 def createCopy(self):
944 """
945 Create a copy of self by setting _data to self
946 """
947 # we really want this to be a DataSmart...
948 data = DataSmart()
949 data.dict["_data"] = self.dict
950 data.varhistory = self.varhistory.copy()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500951 data.varhistory.dataroot = data
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500952 data.inchistory = self.inchistory.copy()
953
954 data._tracking = self._tracking
955
956 data.overrides = None
957 data.overridevars = copy.copy(self.overridevars)
958 # Should really be a deepcopy but has heavy overhead.
959 # Instead, we're careful with writes.
960 data.overridedata = copy.copy(self.overridedata)
961
962 return data
963
964 def expandVarref(self, variable, parents=False):
965 """Find all references to variable in the data and expand it
966 in place, optionally descending to parent datastores."""
967
968 if parents:
969 keys = iter(self)
970 else:
971 keys = self.localkeys()
972
973 ref = '${%s}' % variable
974 value = self.getVar(variable, False)
975 for key in keys:
976 referrervalue = self.getVar(key, False)
977 if referrervalue and ref in referrervalue:
978 self.setVar(key, referrervalue.replace(ref, value))
979
980 def localkeys(self):
981 for key in self.dict:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500982 if key not in ['_data', '_remote_data']:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500983 yield key
984
985 def __iter__(self):
986 deleted = set()
987 overrides = set()
988 def keylist(d):
989 klist = set()
990 for key in d:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500991 if key in ["_data", "_remote_data"]:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500992 continue
993 if key in deleted:
994 continue
995 if key in overrides:
996 continue
997 if not d[key]:
998 deleted.add(key)
999 continue
1000 klist.add(key)
1001
1002 if "_data" in d:
1003 klist |= keylist(d["_data"])
1004
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001005 if "_remote_data" in d:
1006 connector = d["_remote_data"]["_content"]
1007 for key in connector.getKeys():
1008 if key in deleted:
1009 continue
1010 klist.add(key)
1011
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001012 return klist
1013
1014 self.need_overrides()
1015 for var in self.overridedata:
1016 for (r, o) in self.overridedata[var]:
1017 if o in self.overridesset:
1018 overrides.add(var)
1019 elif "_" in o:
1020 if set(o.split("_")).issubset(self.overridesset):
1021 overrides.add(var)
1022
1023 for k in keylist(self.dict):
1024 yield k
1025
1026 for k in overrides:
1027 yield k
1028
1029 def __len__(self):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001030 return len(frozenset(iter(self)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001031
1032 def __getitem__(self, item):
1033 value = self.getVar(item, False)
1034 if value is None:
1035 raise KeyError(item)
1036 else:
1037 return value
1038
1039 def __setitem__(self, var, value):
1040 self.setVar(var, value)
1041
1042 def __delitem__(self, var):
1043 self.delVar(var)
1044
1045 def get_hash(self):
1046 data = {}
1047 d = self.createCopy()
1048 bb.data.expandKeys(d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001049
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001050 config_whitelist = set((d.getVar("BB_HASHCONFIG_WHITELIST") or "").split())
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001051 keys = set(key for key in iter(d) if not key.startswith("__"))
1052 for key in keys:
1053 if key in config_whitelist:
1054 continue
1055
1056 value = d.getVar(key, False) or ""
1057 data.update({key:value})
1058
1059 varflags = d.getVarFlags(key, internalflags = True)
1060 if not varflags:
1061 continue
1062 for f in varflags:
1063 if f == "_content":
1064 continue
1065 data.update({'%s[%s]' % (key, f):varflags[f]})
1066
1067 for key in ["__BBTASKS", "__BBANONFUNCS", "__BBHANDLERS"]:
1068 bb_list = d.getVar(key, False) or []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001069 data.update({key:str(bb_list)})
1070
1071 if key == "__BBANONFUNCS":
1072 for i in bb_list:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001073 value = d.getVar(i, False) or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001074 data.update({i:value})
1075
1076 data_str = str([(k, data[k]) for k in sorted(data.keys())])
Brad Bishop19323692019-04-05 15:28:33 -04001077 return hashlib.sha256(data_str.encode("utf-8")).hexdigest()