summaryrefslogtreecommitdiffstats
path: root/lib/bb/data_smart.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bb/data_smart.py')
-rw-r--r--lib/bb/data_smart.py256
1 files changed, 219 insertions, 37 deletions
diff --git a/lib/bb/data_smart.py b/lib/bb/data_smart.py
index 5fdfeee2c..ddf98e6a2 100644
--- a/lib/bb/data_smart.py
+++ b/lib/bb/data_smart.py
@@ -28,7 +28,7 @@ BitBake build tools.
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# Based on functions from the base bb module, Copyright 2003 Holger Schurig
-import copy, re
+import copy, re, sys, traceback
from collections import MutableMapping
import logging
import hashlib
@@ -43,6 +43,42 @@ __setvar_regexp__ = re.compile('(?P<base>.*?)(?P<keyword>_append|_prepend)(_(?P<
__expand_var_regexp__ = re.compile(r"\${[^{}]+}")
__expand_python_regexp__ = re.compile(r"\${@.+?}")
+def infer_caller_details(loginfo, parent = False, varval = True):
+ """Save the caller the trouble of specifying everything."""
+ # 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
+ # Infer caller's likely values for variable (var) and value (value),
+ # to reduce clutter in the rest of the code.
+ if varval and ('variable' not in loginfo or 'detail' 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
+ lcls = above.f_locals.items()
+ for k, v in lcls:
+ if k == 'value' and 'detail' not in loginfo:
+ loginfo['detail'] = v
+ if k == 'var' and 'variable' not in loginfo:
+ loginfo['variable'] = v
+ # Infer file/line/function from traceback
+ if 'file' not in loginfo:
+ depth = 3
+ if parent:
+ depth = 4
+ 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):
@@ -157,11 +193,80 @@ class IncludeHistory(object):
o.write("\n")
child.emit(o, level)
+class VariableHistory(object):
+ def __init__(self, dataroot):
+ self.dataroot = dataroot
+ self.variables = COWDictBase.copy()
+
+ def copy(self):
+ new = VariableHistory(self.dataroot)
+ new.variables = self.variables.copy()
+ return new
+
+ def record(self, *kwonly, **loginfo):
+ if not self.dataroot._tracking:
+ return
+ if len(kwonly) > 0:
+ raise TypeError
+ 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 'detail' in loginfo:
+ loginfo['detail'] = str(loginfo['detail'])
+ 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 emit(self, var, oval, val, o):
+ history = self.variable(var)
+ commentVal = re.sub('\n', '\n#', str(oval))
+ 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 'func' in event:
+ # If we have a function listed, this is internal
+ # code, not an operation in a config file, and the
+ # full path is distracting.
+ event['file'] = re.sub('.*/', '', event['file'])
+ display_func = ' [%s]' % event['func']
+ else:
+ display_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'], display_func, flag, re.sub('\n', '\n# ', event['detail'])))
+ 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))
+
+
class DataSmart(MutableMapping):
def __init__(self, special = COWDictBase.copy(), seen = COWDictBase.copy() ):
self.dict = {}
self.inchistory = IncludeHistory()
+ self.varhistory = VariableHistory(self)
+ self._tracking = False
# cookie monster tribute
self._special_values = special
@@ -169,6 +274,12 @@ class DataSmart(MutableMapping):
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
@@ -204,10 +315,14 @@ 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 = {
+ 'op': 'finalize',
+ }
+ infer_caller_details(finalize_caller, parent = parent, varval = False)
#
# Well let us see what breaks here. We used to iterate
@@ -224,6 +339,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
@@ -236,7 +354,19 @@ class DataSmart(MutableMapping):
for var in vars:
name = var[:-l]
try:
- self.setVar(name, self.getVar(var, False))
+ # Report only once, even if multiple changes.
+ if name not in finalizes_reported:
+ finalizes_reported[name] = True
+ finalize_caller['variable'] = name
+ finalize_caller['detail'] = 'was: ' + str(self.getVar(name, False))
+ self.varhistory.record(**finalize_caller)
+ # Copy history of the override over.
+ for event in self.varhistory.variable(var):
+ loginfo = event.copy()
+ loginfo['variable'] = name
+ loginfo['op'] = 'override[%s]:%s' % (o, loginfo['op'])
+ self.varhistory.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")
@@ -267,9 +397,9 @@ class DataSmart(MutableMapping):
# We save overrides that may be applied at some later stage
if keep:
- self.setVarFlag(append, op, keep)
+ self.setVarFlag(append, op, keep, ignore=True)
else:
- self.delVarFlag(append, op)
+ self.delVarFlag(append, op, ignore=True)
def initVar(self, var):
self.expand_cache = {}
@@ -297,7 +427,10 @@ class DataSmart(MutableMapping):
else:
self.initVar(var)
- def setVar(self, var, value):
+
+ def setVar(self, var, value, **loginfo):
+ if 'op' not in loginfo:
+ loginfo['op'] = "set"
self.expand_cache = {}
match = __setvar_regexp__.match(var)
if match and match.group("keyword") in __setvar_keyword__:
@@ -306,15 +439,22 @@ class DataSmart(MutableMapping):
override = match.group('add')
l = self.getVarFlag(base, keyword) or []
l.append([value, override])
- self.setVarFlag(base, keyword, l)
-
+ self.setVarFlag(base, keyword, l, ignore=True)
+ # And cause that to be recorded:
+ loginfo['detail'] = value
+ loginfo['variable'] = base
+ if override:
+ loginfo['op'] = '%s[%s]' % (keyword, override)
+ else:
+ loginfo['op'] = keyword
+ self.varhistory.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
@@ -331,6 +471,7 @@ class DataSmart(MutableMapping):
# setting var
self.dict[var]["_content"] = value
+ self.varhistory.record(**loginfo)
def getVar(self, var, expand=False, noweakdefault=False):
value = self.getVarFlag(var, "_content", False, noweakdefault)
@@ -340,13 +481,17 @@ 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['variable'] = newkey
+ loginfo['op'] = 'rename from %s' % key
+ loginfo['detail'] = val
+ self.varhistory.record(**loginfo)
+ self.setVar(newkey, val, ignore=True)
for i in ('_append', '_prepend'):
src = self.getVarFlag(key, i)
@@ -355,23 +500,34 @@ class DataSmart(MutableMapping):
dest = self.getVarFlag(newkey, i) or []
dest.extend(src)
- self.setVarFlag(newkey, i, dest)
+ self.setVarFlag(newkey, i, dest, ignore=True)
if i in self._special_values and key in self._special_values[i]:
self._special_values[i].remove(key)
self._special_values[i].add(newkey)
- self.delVar(key)
-
- def appendVar(self, key, value):
- value = (self.getVar(key, False) or "") + value
- self.setVar(key, value)
-
- def prependVar(self, key, value):
- value = value + (self.getVar(key, False) or "")
- self.setVar(key, value)
-
- def delVar(self, var):
+ loginfo['variable'] = key
+ loginfo['op'] = 'rename (to)'
+ loginfo['detail'] = newkey
+ self.varhistory.record(**loginfo)
+ self.delVar(key, ignore=True)
+
+ def appendVar(self, var, value, **loginfo):
+ loginfo['op'] = 'append'
+ self.varhistory.record(**loginfo)
+ newvalue = (self.getVar(var, False) or "") + value
+ self.setVar(var, newvalue, ignore=True)
+
+ def prependVar(self, var, value, **loginfo):
+ loginfo['op'] = 'prepend'
+ self.varhistory.record(**loginfo)
+ newvalue = value + (self.getVar(var, False) or "")
+ self.setVar(var, newvalue, ignore=True)
+
+ def delVar(self, var, **loginfo):
+ loginfo['detail'] = ""
+ loginfo['op'] = 'del'
+ self.varhistory.record(**loginfo)
self.expand_cache = {}
self.dict[var] = {}
if '_' in var:
@@ -379,10 +535,14 @@ 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, value, **loginfo):
+ if 'op' not in loginfo:
+ loginfo['op'] = "set"
+ loginfo['flag'] = flag
+ self.varhistory.record(**loginfo)
if not var in self.dict:
self._makeShadowCopy(var)
- self.dict[var][flag] = flagvalue
+ self.dict[var][flag] = value
def getVarFlag(self, var, flag, expand=False, noweakdefault=False):
local_var = self._findVar(var)
@@ -396,7 +556,7 @@ class DataSmart(MutableMapping):
value = self.expand(value, None)
return value
- def delVarFlag(self, var, flag):
+ def delVarFlag(self, var, flag, **loginfo):
local_var = self._findVar(var)
if not local_var:
return
@@ -404,23 +564,38 @@ class DataSmart(MutableMapping):
self._makeShadowCopy(var)
if var in self.dict and flag in self.dict[var]:
- del self.dict[var][flag]
-
- def appendVarFlag(self, key, flag, value):
- value = (self.getVarFlag(key, flag, False) or "") + value
- self.setVarFlag(key, flag, value)
+ loginfo['detail'] = ""
+ loginfo['op'] = 'delFlag'
+ loginfo['flag'] = flag
+ self.varhistory.record(**loginfo)
- def prependVarFlag(self, key, flag, value):
- value = value + (self.getVarFlag(key, flag, False) or "")
- self.setVarFlag(key, flag, value)
+ del self.dict[var][flag]
- def setVarFlags(self, var, flags):
+ def appendVarFlag(self, var, flag, value, **loginfo):
+ loginfo['op'] = 'append'
+ loginfo['flag'] = flag
+ self.varhistory.record(**loginfo)
+ newvalue = (self.getVarFlag(var, flag, False) or "") + value
+ self.setVarFlag(var, flag, newvalue, ignore=True)
+
+ def prependVarFlag(self, var, flag, value, **loginfo):
+ loginfo['op'] = 'prepend'
+ loginfo['flag'] = flag
+ self.varhistory.record(**loginfo)
+ newvalue = value + (self.getVarFlag(var, flag, False) or "")
+ self.setVarFlag(var, flag, newvalue, ignore=True)
+
+ 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
+ loginfo['detail'] = flags[i]
+ self.varhistory.record(**loginfo)
self.dict[var][i] = flags[i]
def getVarFlags(self, var):
@@ -438,13 +613,16 @@ class DataSmart(MutableMapping):
return flags
- def delVarFlags(self, var):
+ def delVarFlags(self, var, **loginfo):
if not var in self.dict:
self._makeShadowCopy(var)
if var in self.dict:
content = None
+ loginfo['op'] = 'delete flags'
+ self.varhistory.record(**loginfo)
+
# try to save the content
if "_content" in self.dict[var]:
content = self.dict[var]["_content"]
@@ -461,8 +639,12 @@ class DataSmart(MutableMapping):
# we really want this to be a DataSmart...
data = DataSmart(seen=self._seen_overrides.copy(), special=self._special_values.copy())
data.dict["_data"] = self.dict
+ data.varhistory = self.varhistory.copy()
+ data.varhistory.datasmart = data
data.inchistory = self.inchistory.copy()
+ data._tracking = self._tracking
+
return data
def expandVarref(self, variable, parents=False):