From patchwork Mon Oct 8 18:50:51 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [bitbake-devel, 2/2] data_smart.py and friends: Track variable history Date: Mon, 08 Oct 2012 18:50:51 -0000 From: Peter Seebach X-Patchwork-Id: 37939 Message-Id: To: bitbake-devel This patch adds tracking of the history of variable assignments. The changes are predominantly localized to data_smart.py and parse/ast.py. BBHandler.py is changed to use d.setVar(...) instead of data.setVar(..., d), and cooker.py and data.py are altered to display the recorded data, and turn tracking on for the bitbake -e case. The data.py update_data() function warns DataSmart.finalize() to report the caller one further back up the tree. In general, d.setVar() does what it used to do. Optionally, arguments describing an operation may be appended; if none are present, the operation is implicitly ignored. If it's not ignored, it will attempt to infer missing information (name of variable, value assigned, file and line) by examining the traceback. This slightly elaborate process eliminates a category of problems in which the 'var' member of the keyword arguments dict is set, and a positional argument corresponding to 'var' is also set. It also makes calling much simpler for the common cases. The resulting output gives you a pretty good picture of what values got set, and how they got set. Signed-off-by: Peter Seebach --- lib/bb/cooker.py | 10 ++- lib/bb/data.py | 34 ++++++- lib/bb/data.pyc | Bin 0 -> 14383 bytes lib/bb/data_smart.py | 201 +++++++++++++++++++++++++++++++----- lib/bb/data_smart.pyc | Bin 0 -> 21279 bytes lib/bb/parse/ast.py | 25 ++++- lib/bb/parse/parse_py/BBHandler.py | 6 +- 7 files changed, 235 insertions(+), 41 deletions(-) create mode 100644 lib/bb/data.pyc create mode 100644 lib/bb/data_smart.pyc diff --git a/lib/bb/cooker.py b/lib/bb/cooker.py index 0c7c4e0..1e959c6 100644 --- a/lib/bb/cooker.py +++ b/lib/bb/cooker.py @@ -101,8 +101,10 @@ class BBCooker: # to use environment variables which have been cleaned from the # BitBake processes env self.savedenv = bb.data.init() + if self.configuration.show_environment: + self.savedenv.enableTracking() for k in savedenv: - self.savedenv.setVar(k, savedenv[k]) + self.savedenv.setVar(k, savedenv[k], op = 'inherit', file = '[saved environment]') self.caches_array = [] # Currently, only Image Creator hob ui needs extra cache. @@ -177,6 +179,8 @@ class BBCooker: def initConfigurationData(self): self.configuration.data = bb.data.init() + if self.configuration.show_environment: + self.configuration.data.enableTracking() if not self.server_registration_cb: self.configuration.data.setVar("BB_WORKERCONTEXT", "1") @@ -186,6 +190,8 @@ class BBCooker: def loadConfigurationData(self): self.configuration.data = bb.data.init() + if self.configuration.show_environment: + self.configuration.data.enableTracking() if not self.server_registration_cb: self.configuration.data.setVar("BB_WORKERCONTEXT", "1") @@ -840,6 +846,8 @@ class BBCooker: def parseConfigurationFiles(self, prefiles, postfiles): data = self.configuration.data + if self.configuration.show_environment: + data.enableTracking() bb.parse.init_parser(data) # Parse files for loading *before* bitbake.conf and any includes diff --git a/lib/bb/data.py b/lib/bb/data.py index 9a32353..3c99762 100644 --- a/lib/bb/data.py +++ b/lib/bb/data.py @@ -166,9 +166,9 @@ def inheritFromOS(d, savedenv, permitted): for s in savedenv.keys(): if s in permitted: try: - setVar(s, getVar(s, savedenv, True), d) + d.setVar(s, getVar(s, savedenv, True), op = 'inherit') if s in exportlist: - setVarFlag(s, "export", True, d) + d.setVarFlag(s, "export", True, op = 'automatic') except TypeError: pass @@ -194,8 +194,34 @@ def emit_var(var, o=sys.__stdout__, d = init(), all=False): return 0 if all: + history = d.history.variable(var) commentVal = re.sub('\n', '\n#', str(oval)) - o.write('# %s=%s\n' % (var, commentVal)) + if history: + if len(history) == 1: + o.write("#\n# $%s\n" % var) + else: + o.write("#\n# $%s [%d operations]\n" % (var, len(history))) + for event in history: + # o.write("# %s\n" % str(event)) + if 'details' in event and event['details']: + value = event['details'] + else: + value = event['value'] + if 'func' in event: + func = ' [%s]' % event['func'] + else: + func = '' + if 'flag' in event: + flag = '[%s] ' % (event['flag']) + else: + flag = '' + o.write("# %s %s:%s%s\n# %s\"%s\"\n" % (event['op'], event['file'], event['line'], func, flag, re.sub('\n', '\n# ', value))) + if len(history) > 1: + o.write("# computed:\n") + o.write('# "%s"\n' % (commentVal)) + else: + o.write("#\n# $%s\n# [no history recorded]\n#\n" % var) + o.write('# "%s"\n' % (commentVal)) if (var.find("-") != -1 or var.find(".") != -1 or var.find('{') != -1 or var.find('}') != -1 or var.find('+') != -1) and not all: return 0 @@ -273,7 +299,7 @@ def emit_func(func, o=sys.__stdout__, d = init()): def update_data(d): """Performs final steps upon the datastore, including application of overrides""" - d.finalize() + d.finalize(parent = True) def build_dependencies(key, keys, shelldeps, vardepvals, d): deps = set() diff --git a/lib/bb/data.pyc b/lib/bb/data.pyc new file mode 100644 index 0000000000000000000000000000000000000000..33a7d8393b625849aac1a9db1170bf9a031a27f3 GIT binary patch literal 14383 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.*?)(?P_append|_prepend)(_(?P< __expand_var_regexp__ = re.compile(r"\${[^{}]+}") __expand_python_regexp__ = re.compile(r"\${@.+?}") +def infer_caller_details(loginfo, parent = False): + """Save the caller the trouble of specifying everything.""" + locals = None + # Save effort. + if 'ignore' in loginfo and loginfo['ignore']: + return + # If nothing was provided, mark this as possibly unneeded. + if not loginfo: + loginfo['ignore'] = True + return + if parent: + depth = 4 + else: + depth = 3 + # Infer caller's likely values for variable (var), loginfo (loginfo), + # and value (value), to reduce clutter in the rest of the code. + if 'variable' not in loginfo or 'value' not in loginfo: + try: + raise Exception + except Exception: + tb = sys.exc_info()[2] + if parent: + above = tb.tb_frame.f_back.f_back + else: + above = tb.tb_frame.f_back + locals = above.f_locals.items() + tb = None + above = None + value = None + variable = None + for k, v in locals: + if k == 'value': + value = v + if k == 'var': + variable = v + if 'value' not in loginfo: + loginfo['value'] = value + if 'variable' not in loginfo: + loginfo['variable'] = variable + locals = None + # Infer file/line/function from traceback + if 'file' not in loginfo: + file, line, func, text = traceback.extract_stack(limit = depth)[0] + loginfo['file'] = file + loginfo['line'] = line + if func not in loginfo: + loginfo['func'] = func class VariableParse: def __init__(self, varname, d, val = None): @@ -115,15 +162,17 @@ class ExpansionError(Exception): return self.msg class IncludeHistory(object): - def __init__(self, parent = None, filename = None): + def __init__(self, parent = None, filename = None, datasmart = None): self.parent = parent - if parent: + if parent is not None: self.top = parent.top else: self.top = self + self.datasmart = datasmart self.filename = filename or '[TOP LEVEL]' self.children = [] self.current = self + self.variables = {} def include(self, filename): newfile = IncludeHistory(self.current, filename) @@ -131,6 +180,34 @@ class IncludeHistory(object): self.current = newfile return self + def record(self, *kwonly, **loginfo): + if len(kwonly) > 0: + raise TypeError + if not self.top.datasmart._tracking: + return + infer_caller_details(loginfo, parent = True) + if 'ignore' in loginfo and loginfo['ignore']: + return + if 'op' not in loginfo or not loginfo['op']: + loginfo['op'] = 'set' + if 'details' in loginfo and loginfo['details']: + loginfo['details'] = str(loginfo['details']) + if 'value' in loginfo and loginfo['value']: + loginfo['value'] = str(loginfo['value']) + if 'variable' not in loginfo or 'file' not in loginfo: + raise ValueError("record() missing variable or file.") + var = loginfo['variable'] + + if var not in self.variables: + self.variables[var] = [] + self.variables[var].append(loginfo.copy()) + + def variable(self, var): + if var in self.variables: + return self.variables[var] + else: + return [] + def __enter__(self): pass @@ -148,11 +225,11 @@ class IncludeHistory(object): o.write("# %s%s" % (spaces, self.filename)) if len(self.children) > 0: o.write(" includes:") - o.write("\n") level = level + 1 else: - o.write("#\n# INCLUDE HISTORY:\n#\n") + o.write("#\n# INCLUDE HISTORY:\n#") for child in self.children: + o.write("\n") child.emit(o, level) @@ -163,10 +240,17 @@ class DataSmart(MutableMapping): # cookie monster tribute self._special_values = special self._seen_overrides = seen - self.history = IncludeHistory() + self.history = IncludeHistory(datasmart = self) + self._tracking = False self.expand_cache = {} + def enableTracking(self): + self._tracking = True + + def disableTracking(self): + self._tracking = False + def expandWithRefs(self, s, varname): if not isinstance(s, basestring): # sanity check @@ -200,10 +284,16 @@ class DataSmart(MutableMapping): return self.expandWithRefs(s, varname).value - def finalize(self): + def finalize(self, parent = False): """Performs final steps upon the datastore, including application of overrides""" overrides = (self.getVar("OVERRIDES", True) or "").split(":") or [] + finalize_caller = { + 'variable': 'ignored', + 'value': 'ignored', + 'op': 'finalize', + } + infer_caller_details(finalize_caller, parent = parent) # # Well let us see what breaks here. We used to iterate @@ -220,6 +310,9 @@ class DataSmart(MutableMapping): # Then we will handle _append and _prepend # + # We only want to report finalization once per variable overridden. + finalizes_reported = {} + for o in overrides: # calculate '_'+override l = len(o) + 1 @@ -232,7 +325,19 @@ class DataSmart(MutableMapping): for var in vars: name = var[:-l] try: - self.setVar(name, self.getVar(var, False)) + # Report only once, even if + if name not in finalizes_reported: + finalizes_reported[name] = True + finalize_caller['variable'] = name + finalize_caller['value'] = 'was: ' + str(self.getVar(name, False)) + self.history.record(**finalize_caller) + # Copy history of the override over. + for event in self.history.variable(var): + loginfo = event.copy() + loginfo['variable'] = name + loginfo['op'] = 'override[%s]:%s' % (o, loginfo['op']) + self.history.record(**loginfo) + self.setVar(name, self.getVar(var, False), op = 'finalize', file = 'override[%s]' % o, line = '') self.delVar(var) except Exception: logger.info("Untracked delVar") @@ -293,7 +398,9 @@ class DataSmart(MutableMapping): else: self.initVar(var) - def setVar(self, var, value): + + def setVar(self, var, value, **loginfo): + infer_caller_details(loginfo) self.expand_cache = {} match = __setvar_regexp__.match(var) if match and match.group("keyword") in __setvar_keyword__: @@ -303,14 +410,21 @@ class DataSmart(MutableMapping): l = self.getVarFlag(base, keyword) or [] l.append([value, override]) self.setVarFlag(base, keyword, l) - + # And cause that to be recorded: + loginfo['details'] = value + loginfo['variable'] = base + if override: + loginfo['op'] = '%s[%s]' % (keyword, override) + else: + loginfo['op'] = keyword + self.history.record(**loginfo) # todo make sure keyword is not __doc__ or __module__ # pay the cookie monster try: - self._special_values[keyword].add( base ) + self._special_values[keyword].add(base) except KeyError: self._special_values[keyword] = set() - self._special_values[keyword].add( base ) + self._special_values[keyword].add(base) return @@ -327,6 +441,7 @@ class DataSmart(MutableMapping): # setting var self.dict[var]["_content"] = value + self.history.record(**loginfo) def getVar(self, var, expand=False, noweakdefault=False): value = self.getVarFlag(var, "_content", False, noweakdefault) @@ -336,13 +451,15 @@ class DataSmart(MutableMapping): return self.expand(value, var) return value - def renameVar(self, key, newkey): + def renameVar(self, key, newkey, **loginfo): """ Rename the variable key to newkey """ val = self.getVar(key, 0) if val is not None: - self.setVar(newkey, val) + loginfo['op'] = 'rename' + loginfo['details'] = key + self.setVar(newkey, val, **loginfo) for i in ('_append', '_prepend'): src = self.getVarFlag(key, i) @@ -359,15 +476,21 @@ class DataSmart(MutableMapping): self.delVar(key) - def appendVar(self, key, value): + def appendVar(self, key, value, **loginfo): + loginfo['details'] = value value = (self.getVar(key, False) or "") + value - self.setVar(key, value) + self.setVar(key, value, **loginfo) - def prependVar(self, key, value): + def prependVar(self, key, value, **loginfo): + loginfo['details'] = value value = value + (self.getVar(key, False) or "") - self.setVar(key, value) + self.setVar(key, value, **loginfo) - def delVar(self, var): + def delVar(self, var, **loginfo): + loginfo['value'] = "" + if not 'op' in loginfo: + loginfo['op'] = 'del' + self.history.record(**loginfo) self.expand_cache = {} self.dict[var] = {} if '_' in var: @@ -375,7 +498,11 @@ class DataSmart(MutableMapping): if override and override in self._seen_overrides and var in self._seen_overrides[override]: self._seen_overrides[override].remove(var) - def setVarFlag(self, var, flag, flagvalue): + def setVarFlag(self, var, flag, flagvalue, **loginfo): + infer_caller_details(loginfo) + loginfo['value'] = flagvalue + loginfo['flag'] = flag + self.history.record(**loginfo) if not var in self.dict: self._makeShadowCopy(var) self.dict[var][flag] = flagvalue @@ -392,31 +519,44 @@ class DataSmart(MutableMapping): value = self.expand(value, None) return value - def delVarFlag(self, var, flag): + def delVarFlag(self, var, flag, **loginfo): + infer_caller_details(loginfo) + loginfo['value'] = "" local_var = self._findVar(var) + if 'op' not in loginfo: + loginfo['op'] = 'delFlag' + loginfo['flag'] = flag if not local_var: return if not var in self.dict: self._makeShadowCopy(var) if var in self.dict and flag in self.dict[var]: + self.history.record(**loginfo) del self.dict[var][flag] - def appendVarFlag(self, key, flag, value): + def appendVarFlag(self, key, flag, value, **loginfo): + infer_caller_details(loginfo) + loginfo['details'] = value value = (self.getVarFlag(key, flag, False) or "") + value - self.setVarFlag(key, flag, value) + self.setVarFlag(key, flag, value, **loginfo) - def prependVarFlag(self, key, flag, value): + def prependVarFlag(self, key, flag, value, **loginfo): + infer_caller_details(loginfo) + loginfo['details'] = value value = value + (self.getVarFlag(key, flag, False) or "") - self.setVarFlag(key, flag, value) + self.setVarFlag(key, flag, value, **loginfo) - def setVarFlags(self, var, flags): + def setVarFlags(self, var, flags, **loginfo): + infer_caller_details(loginfo) if not var in self.dict: self._makeShadowCopy(var) for i in flags: if i == "_content": continue + loginfo['flag'] = i + self.history.record(**loginfo) self.dict[var][i] = flags[i] def getVarFlags(self, var): @@ -434,12 +574,16 @@ class DataSmart(MutableMapping): return flags - def delVarFlags(self, var): + def delVarFlags(self, var, **loginfo): + infer_caller_details(loginfo) + if 'op' not in loginfo: + loginfo['op'] = 'delete flags' if not var in self.dict: self._makeShadowCopy(var) if var in self.dict: content = None + self.history.record(**loginfo) # try to save the content if "_content" in self.dict[var]: @@ -449,7 +593,6 @@ class DataSmart(MutableMapping): else: del self.dict[var] - def createCopy(self): """ Create a copy of self by setting _data to self @@ -458,6 +601,8 @@ class DataSmart(MutableMapping): data = DataSmart(seen=self._seen_overrides.copy(), special=self._special_values.copy()) data.dict["_data"] = self.dict data.history = copy.deepcopy(self.history) + data.history.datasmart = data + data._tracking = self._tracking return data diff --git a/lib/bb/data_smart.pyc b/lib/bb/data_smart.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5477e2e3fc75013f2017aeda921bebaaa0aea693 GIT binary patch literal 21279 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