xref: /OK3568_Linux_fs/yocto/poky/bitbake/lib/bb/data_smart.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun"""
2*4882a593SmuzhiyunBitBake Smart Dictionary Implementation
3*4882a593Smuzhiyun
4*4882a593SmuzhiyunFunctions for interacting with the data structure used by the
5*4882a593SmuzhiyunBitBake build tools.
6*4882a593Smuzhiyun
7*4882a593Smuzhiyun"""
8*4882a593Smuzhiyun
9*4882a593Smuzhiyun# Copyright (C) 2003, 2004  Chris Larson
10*4882a593Smuzhiyun# Copyright (C) 2004, 2005  Seb Frankengul
11*4882a593Smuzhiyun# Copyright (C) 2005, 2006  Holger Hans Peter Freyther
12*4882a593Smuzhiyun# Copyright (C) 2005        Uli Luckas
13*4882a593Smuzhiyun# Copyright (C) 2005        ROAD GmbH
14*4882a593Smuzhiyun#
15*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only
16*4882a593Smuzhiyun#
17*4882a593Smuzhiyun# Based on functions from the base bb module, Copyright 2003 Holger Schurig
18*4882a593Smuzhiyun
19*4882a593Smuzhiyunimport copy, re, sys, traceback
20*4882a593Smuzhiyunfrom collections.abc import MutableMapping
21*4882a593Smuzhiyunimport logging
22*4882a593Smuzhiyunimport hashlib
23*4882a593Smuzhiyunimport bb, bb.codeparser
24*4882a593Smuzhiyunfrom bb   import utils
25*4882a593Smuzhiyunfrom bb.COW  import COWDictBase
26*4882a593Smuzhiyun
27*4882a593Smuzhiyunlogger = logging.getLogger("BitBake.Data")
28*4882a593Smuzhiyun
29*4882a593Smuzhiyun__setvar_keyword__ = [":append", ":prepend", ":remove"]
30*4882a593Smuzhiyun__setvar_regexp__ = re.compile(r'(?P<base>.*?)(?P<keyword>:append|:prepend|:remove)(:(?P<add>[^A-Z]*))?$')
31*4882a593Smuzhiyun__expand_var_regexp__ = re.compile(r"\${[a-zA-Z0-9\-_+./~:]+?}")
32*4882a593Smuzhiyun__expand_python_regexp__ = re.compile(r"\${@.+?}")
33*4882a593Smuzhiyun__whitespace_split__ = re.compile(r'(\s)')
34*4882a593Smuzhiyun__override_regexp__ = re.compile(r'[a-z0-9]+')
35*4882a593Smuzhiyun
36*4882a593Smuzhiyunbitbake_renamed_vars = {
37*4882a593Smuzhiyun    "BB_ENV_WHITELIST": "BB_ENV_PASSTHROUGH",
38*4882a593Smuzhiyun    "BB_ENV_EXTRAWHITE": "BB_ENV_PASSTHROUGH_ADDITIONS",
39*4882a593Smuzhiyun    "BB_HASHBASE_WHITELIST": "BB_BASEHASH_IGNORE_VARS",
40*4882a593Smuzhiyun    "BB_HASHCONFIG_WHITELIST": "BB_HASHCONFIG_IGNORE_VARS",
41*4882a593Smuzhiyun    "BB_HASHTASK_WHITELIST": "BB_TASKHASH_IGNORE_TASKS",
42*4882a593Smuzhiyun    "BB_SETSCENE_ENFORCE_WHITELIST": "BB_SETSCENE_ENFORCE_IGNORE_TASKS",
43*4882a593Smuzhiyun    "MULTI_PROVIDER_WHITELIST": "BB_MULTI_PROVIDER_ALLOWED",
44*4882a593Smuzhiyun    "BB_STAMP_WHITELIST": "is a deprecated variable and support has been removed",
45*4882a593Smuzhiyun    "BB_STAMP_POLICY": "is a deprecated variable and support has been removed",
46*4882a593Smuzhiyun}
47*4882a593Smuzhiyun
48*4882a593Smuzhiyundef infer_caller_details(loginfo, parent = False, varval = True):
49*4882a593Smuzhiyun    """Save the caller the trouble of specifying everything."""
50*4882a593Smuzhiyun    # Save effort.
51*4882a593Smuzhiyun    if 'ignore' in loginfo and loginfo['ignore']:
52*4882a593Smuzhiyun        return
53*4882a593Smuzhiyun    # If nothing was provided, mark this as possibly unneeded.
54*4882a593Smuzhiyun    if not loginfo:
55*4882a593Smuzhiyun        loginfo['ignore'] = True
56*4882a593Smuzhiyun        return
57*4882a593Smuzhiyun    # Infer caller's likely values for variable (var) and value (value),
58*4882a593Smuzhiyun    # to reduce clutter in the rest of the code.
59*4882a593Smuzhiyun    above = None
60*4882a593Smuzhiyun    def set_above():
61*4882a593Smuzhiyun        try:
62*4882a593Smuzhiyun            raise Exception
63*4882a593Smuzhiyun        except Exception:
64*4882a593Smuzhiyun            tb = sys.exc_info()[2]
65*4882a593Smuzhiyun            if parent:
66*4882a593Smuzhiyun                return tb.tb_frame.f_back.f_back.f_back
67*4882a593Smuzhiyun            else:
68*4882a593Smuzhiyun                return tb.tb_frame.f_back.f_back
69*4882a593Smuzhiyun
70*4882a593Smuzhiyun    if varval and ('variable' not in loginfo or 'detail' not in loginfo):
71*4882a593Smuzhiyun        if not above:
72*4882a593Smuzhiyun            above = set_above()
73*4882a593Smuzhiyun        lcls = above.f_locals.items()
74*4882a593Smuzhiyun        for k, v in lcls:
75*4882a593Smuzhiyun            if k == 'value' and 'detail' not in loginfo:
76*4882a593Smuzhiyun                loginfo['detail'] = v
77*4882a593Smuzhiyun            if k == 'var' and 'variable' not in loginfo:
78*4882a593Smuzhiyun                loginfo['variable'] = v
79*4882a593Smuzhiyun    # Infer file/line/function from traceback
80*4882a593Smuzhiyun    # Don't use traceback.extract_stack() since it fills the line contents which
81*4882a593Smuzhiyun    # we don't need and that hits stat syscalls
82*4882a593Smuzhiyun    if 'file' not in loginfo:
83*4882a593Smuzhiyun        if not above:
84*4882a593Smuzhiyun            above = set_above()
85*4882a593Smuzhiyun        f = above.f_back
86*4882a593Smuzhiyun        line = f.f_lineno
87*4882a593Smuzhiyun        file = f.f_code.co_filename
88*4882a593Smuzhiyun        func = f.f_code.co_name
89*4882a593Smuzhiyun        loginfo['file'] = file
90*4882a593Smuzhiyun        loginfo['line'] = line
91*4882a593Smuzhiyun        if func not in loginfo:
92*4882a593Smuzhiyun            loginfo['func'] = func
93*4882a593Smuzhiyun
94*4882a593Smuzhiyunclass VariableParse:
95*4882a593Smuzhiyun    def __init__(self, varname, d, val = None):
96*4882a593Smuzhiyun        self.varname = varname
97*4882a593Smuzhiyun        self.d = d
98*4882a593Smuzhiyun        self.value = val
99*4882a593Smuzhiyun
100*4882a593Smuzhiyun        self.references = set()
101*4882a593Smuzhiyun        self.execs = set()
102*4882a593Smuzhiyun        self.contains = {}
103*4882a593Smuzhiyun
104*4882a593Smuzhiyun    def var_sub(self, match):
105*4882a593Smuzhiyun            key = match.group()[2:-1]
106*4882a593Smuzhiyun            if self.varname and key:
107*4882a593Smuzhiyun                if self.varname == key:
108*4882a593Smuzhiyun                    raise Exception("variable %s references itself!" % self.varname)
109*4882a593Smuzhiyun            var = self.d.getVarFlag(key, "_content")
110*4882a593Smuzhiyun            self.references.add(key)
111*4882a593Smuzhiyun            if var is not None:
112*4882a593Smuzhiyun                return var
113*4882a593Smuzhiyun            else:
114*4882a593Smuzhiyun                return match.group()
115*4882a593Smuzhiyun
116*4882a593Smuzhiyun    def python_sub(self, match):
117*4882a593Smuzhiyun            if isinstance(match, str):
118*4882a593Smuzhiyun                code = match
119*4882a593Smuzhiyun            else:
120*4882a593Smuzhiyun                code = match.group()[3:-1]
121*4882a593Smuzhiyun
122*4882a593Smuzhiyun            if self.varname:
123*4882a593Smuzhiyun                varname = 'Var <%s>' % self.varname
124*4882a593Smuzhiyun            else:
125*4882a593Smuzhiyun                varname = '<expansion>'
126*4882a593Smuzhiyun            codeobj = compile(code.strip(), varname, "eval")
127*4882a593Smuzhiyun
128*4882a593Smuzhiyun            parser = bb.codeparser.PythonParser(self.varname, logger)
129*4882a593Smuzhiyun            parser.parse_python(code)
130*4882a593Smuzhiyun            if self.varname:
131*4882a593Smuzhiyun                vardeps = self.d.getVarFlag(self.varname, "vardeps")
132*4882a593Smuzhiyun                if vardeps is None:
133*4882a593Smuzhiyun                    parser.log.flush()
134*4882a593Smuzhiyun            else:
135*4882a593Smuzhiyun                parser.log.flush()
136*4882a593Smuzhiyun            self.references |= parser.references
137*4882a593Smuzhiyun            self.execs |= parser.execs
138*4882a593Smuzhiyun
139*4882a593Smuzhiyun            for k in parser.contains:
140*4882a593Smuzhiyun                if k not in self.contains:
141*4882a593Smuzhiyun                    self.contains[k] = parser.contains[k].copy()
142*4882a593Smuzhiyun                else:
143*4882a593Smuzhiyun                    self.contains[k].update(parser.contains[k])
144*4882a593Smuzhiyun            value = utils.better_eval(codeobj, DataContext(self.d), {'d' : self.d})
145*4882a593Smuzhiyun            return str(value)
146*4882a593Smuzhiyun
147*4882a593Smuzhiyun
148*4882a593Smuzhiyunclass DataContext(dict):
149*4882a593Smuzhiyun    def __init__(self, metadata, **kwargs):
150*4882a593Smuzhiyun        self.metadata = metadata
151*4882a593Smuzhiyun        dict.__init__(self, **kwargs)
152*4882a593Smuzhiyun        self['d'] = metadata
153*4882a593Smuzhiyun
154*4882a593Smuzhiyun    def __missing__(self, key):
155*4882a593Smuzhiyun        # Skip commonly accessed invalid variables
156*4882a593Smuzhiyun        if key in ['bb', 'oe', 'int', 'bool', 'time', 'str', 'os']:
157*4882a593Smuzhiyun            raise KeyError(key)
158*4882a593Smuzhiyun        value = self.metadata.getVar(key)
159*4882a593Smuzhiyun        if value is None or self.metadata.getVarFlag(key, 'func', False):
160*4882a593Smuzhiyun            raise KeyError(key)
161*4882a593Smuzhiyun        else:
162*4882a593Smuzhiyun            return value
163*4882a593Smuzhiyun
164*4882a593Smuzhiyunclass ExpansionError(Exception):
165*4882a593Smuzhiyun    def __init__(self, varname, expression, exception):
166*4882a593Smuzhiyun        self.expression = expression
167*4882a593Smuzhiyun        self.variablename = varname
168*4882a593Smuzhiyun        self.exception = exception
169*4882a593Smuzhiyun        self.varlist = [varname or expression or ""]
170*4882a593Smuzhiyun        if varname:
171*4882a593Smuzhiyun            if expression:
172*4882a593Smuzhiyun                self.msg = "Failure expanding variable %s, expression was %s which triggered exception %s: %s" % (varname, expression, type(exception).__name__, exception)
173*4882a593Smuzhiyun            else:
174*4882a593Smuzhiyun                self.msg = "Failure expanding variable %s: %s: %s" % (varname, type(exception).__name__, exception)
175*4882a593Smuzhiyun        else:
176*4882a593Smuzhiyun            self.msg = "Failure expanding expression %s which triggered exception %s: %s" % (expression, type(exception).__name__, exception)
177*4882a593Smuzhiyun        Exception.__init__(self, self.msg)
178*4882a593Smuzhiyun        self.args = (varname, expression, exception)
179*4882a593Smuzhiyun
180*4882a593Smuzhiyun    def addVar(self, varname):
181*4882a593Smuzhiyun        if varname:
182*4882a593Smuzhiyun            self.varlist.append(varname)
183*4882a593Smuzhiyun
184*4882a593Smuzhiyun    def __str__(self):
185*4882a593Smuzhiyun        chain = "\nThe variable dependency chain for the failure is: " + " -> ".join(self.varlist)
186*4882a593Smuzhiyun        return self.msg + chain
187*4882a593Smuzhiyun
188*4882a593Smuzhiyunclass IncludeHistory(object):
189*4882a593Smuzhiyun    def __init__(self, parent = None, filename = '[TOP LEVEL]'):
190*4882a593Smuzhiyun        self.parent = parent
191*4882a593Smuzhiyun        self.filename = filename
192*4882a593Smuzhiyun        self.children = []
193*4882a593Smuzhiyun        self.current = self
194*4882a593Smuzhiyun
195*4882a593Smuzhiyun    def copy(self):
196*4882a593Smuzhiyun        new = IncludeHistory(self.parent, self.filename)
197*4882a593Smuzhiyun        for c in self.children:
198*4882a593Smuzhiyun            new.children.append(c)
199*4882a593Smuzhiyun        return new
200*4882a593Smuzhiyun
201*4882a593Smuzhiyun    def include(self, filename):
202*4882a593Smuzhiyun        newfile = IncludeHistory(self.current, filename)
203*4882a593Smuzhiyun        self.current.children.append(newfile)
204*4882a593Smuzhiyun        self.current = newfile
205*4882a593Smuzhiyun        return self
206*4882a593Smuzhiyun
207*4882a593Smuzhiyun    def __enter__(self):
208*4882a593Smuzhiyun        pass
209*4882a593Smuzhiyun
210*4882a593Smuzhiyun    def __exit__(self, a, b, c):
211*4882a593Smuzhiyun        if self.current.parent:
212*4882a593Smuzhiyun            self.current = self.current.parent
213*4882a593Smuzhiyun        else:
214*4882a593Smuzhiyun            bb.warn("Include log: Tried to finish '%s' at top level." % self.filename)
215*4882a593Smuzhiyun        return False
216*4882a593Smuzhiyun
217*4882a593Smuzhiyun    def emit(self, o, level = 0):
218*4882a593Smuzhiyun        """Emit an include history file, and its children."""
219*4882a593Smuzhiyun        if level:
220*4882a593Smuzhiyun            spaces = "  " * (level - 1)
221*4882a593Smuzhiyun            o.write("# %s%s" % (spaces, self.filename))
222*4882a593Smuzhiyun            if len(self.children) > 0:
223*4882a593Smuzhiyun                o.write(" includes:")
224*4882a593Smuzhiyun        else:
225*4882a593Smuzhiyun            o.write("#\n# INCLUDE HISTORY:\n#")
226*4882a593Smuzhiyun        level = level + 1
227*4882a593Smuzhiyun        for child in self.children:
228*4882a593Smuzhiyun            o.write("\n")
229*4882a593Smuzhiyun            child.emit(o, level)
230*4882a593Smuzhiyun
231*4882a593Smuzhiyunclass VariableHistory(object):
232*4882a593Smuzhiyun    def __init__(self, dataroot):
233*4882a593Smuzhiyun        self.dataroot = dataroot
234*4882a593Smuzhiyun        self.variables = COWDictBase.copy()
235*4882a593Smuzhiyun
236*4882a593Smuzhiyun    def copy(self):
237*4882a593Smuzhiyun        new = VariableHistory(self.dataroot)
238*4882a593Smuzhiyun        new.variables = self.variables.copy()
239*4882a593Smuzhiyun        return new
240*4882a593Smuzhiyun
241*4882a593Smuzhiyun    def __getstate__(self):
242*4882a593Smuzhiyun        vardict = {}
243*4882a593Smuzhiyun        for k, v in self.variables.iteritems():
244*4882a593Smuzhiyun            vardict[k] = v
245*4882a593Smuzhiyun        return {'dataroot': self.dataroot,
246*4882a593Smuzhiyun                'variables': vardict}
247*4882a593Smuzhiyun
248*4882a593Smuzhiyun    def __setstate__(self, state):
249*4882a593Smuzhiyun        self.dataroot = state['dataroot']
250*4882a593Smuzhiyun        self.variables = COWDictBase.copy()
251*4882a593Smuzhiyun        for k, v in state['variables'].items():
252*4882a593Smuzhiyun            self.variables[k] = v
253*4882a593Smuzhiyun
254*4882a593Smuzhiyun    def record(self, *kwonly, **loginfo):
255*4882a593Smuzhiyun        if not self.dataroot._tracking:
256*4882a593Smuzhiyun            return
257*4882a593Smuzhiyun        if len(kwonly) > 0:
258*4882a593Smuzhiyun            raise TypeError
259*4882a593Smuzhiyun        infer_caller_details(loginfo, parent = True)
260*4882a593Smuzhiyun        if 'ignore' in loginfo and loginfo['ignore']:
261*4882a593Smuzhiyun            return
262*4882a593Smuzhiyun        if 'op' not in loginfo or not loginfo['op']:
263*4882a593Smuzhiyun            loginfo['op'] = 'set'
264*4882a593Smuzhiyun        if 'detail' in loginfo:
265*4882a593Smuzhiyun            loginfo['detail'] = str(loginfo['detail'])
266*4882a593Smuzhiyun        if 'variable' not in loginfo or 'file' not in loginfo:
267*4882a593Smuzhiyun            raise ValueError("record() missing variable or file.")
268*4882a593Smuzhiyun        var = loginfo['variable']
269*4882a593Smuzhiyun
270*4882a593Smuzhiyun        if var not in self.variables:
271*4882a593Smuzhiyun            self.variables[var] = []
272*4882a593Smuzhiyun        if not isinstance(self.variables[var], list):
273*4882a593Smuzhiyun            return
274*4882a593Smuzhiyun        if 'nodups' in loginfo and loginfo in self.variables[var]:
275*4882a593Smuzhiyun            return
276*4882a593Smuzhiyun        self.variables[var].append(loginfo.copy())
277*4882a593Smuzhiyun
278*4882a593Smuzhiyun    def rename_variable_hist(self, oldvar, newvar):
279*4882a593Smuzhiyun        if not self.dataroot._tracking:
280*4882a593Smuzhiyun            return
281*4882a593Smuzhiyun        if oldvar not in self.variables:
282*4882a593Smuzhiyun            return
283*4882a593Smuzhiyun        if newvar not in self.variables:
284*4882a593Smuzhiyun            self.variables[newvar] = []
285*4882a593Smuzhiyun        for i in self.variables[oldvar]:
286*4882a593Smuzhiyun            self.variables[newvar].append(i.copy())
287*4882a593Smuzhiyun
288*4882a593Smuzhiyun    def variable(self, var):
289*4882a593Smuzhiyun        varhistory = []
290*4882a593Smuzhiyun        if var in self.variables:
291*4882a593Smuzhiyun            varhistory.extend(self.variables[var])
292*4882a593Smuzhiyun        return varhistory
293*4882a593Smuzhiyun
294*4882a593Smuzhiyun    def emit(self, var, oval, val, o, d):
295*4882a593Smuzhiyun        history = self.variable(var)
296*4882a593Smuzhiyun
297*4882a593Smuzhiyun        # Append override history
298*4882a593Smuzhiyun        if var in d.overridedata:
299*4882a593Smuzhiyun            for (r, override) in d.overridedata[var]:
300*4882a593Smuzhiyun                for event in self.variable(r):
301*4882a593Smuzhiyun                    loginfo = event.copy()
302*4882a593Smuzhiyun                    if 'flag' in loginfo and not loginfo['flag'].startswith(("_", ":")):
303*4882a593Smuzhiyun                        continue
304*4882a593Smuzhiyun                    loginfo['variable'] = var
305*4882a593Smuzhiyun                    loginfo['op'] = 'override[%s]:%s' % (override, loginfo['op'])
306*4882a593Smuzhiyun                    history.append(loginfo)
307*4882a593Smuzhiyun
308*4882a593Smuzhiyun        commentVal = re.sub('\n', '\n#', str(oval))
309*4882a593Smuzhiyun        if history:
310*4882a593Smuzhiyun            if len(history) == 1:
311*4882a593Smuzhiyun                o.write("#\n# $%s\n" % var)
312*4882a593Smuzhiyun            else:
313*4882a593Smuzhiyun                o.write("#\n# $%s [%d operations]\n" % (var, len(history)))
314*4882a593Smuzhiyun            for event in history:
315*4882a593Smuzhiyun                # o.write("# %s\n" % str(event))
316*4882a593Smuzhiyun                if 'func' in event:
317*4882a593Smuzhiyun                    # If we have a function listed, this is internal
318*4882a593Smuzhiyun                    # code, not an operation in a config file, and the
319*4882a593Smuzhiyun                    # full path is distracting.
320*4882a593Smuzhiyun                    event['file'] = re.sub('.*/', '', event['file'])
321*4882a593Smuzhiyun                    display_func = ' [%s]' % event['func']
322*4882a593Smuzhiyun                else:
323*4882a593Smuzhiyun                    display_func = ''
324*4882a593Smuzhiyun                if 'flag' in event:
325*4882a593Smuzhiyun                    flag = '[%s] ' % (event['flag'])
326*4882a593Smuzhiyun                else:
327*4882a593Smuzhiyun                    flag = ''
328*4882a593Smuzhiyun                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'])))
329*4882a593Smuzhiyun            if len(history) > 1:
330*4882a593Smuzhiyun                o.write("# pre-expansion value:\n")
331*4882a593Smuzhiyun                o.write('#   "%s"\n' % (commentVal))
332*4882a593Smuzhiyun        else:
333*4882a593Smuzhiyun            o.write("#\n# $%s\n#   [no history recorded]\n#\n" % var)
334*4882a593Smuzhiyun            o.write('#   "%s"\n' % (commentVal))
335*4882a593Smuzhiyun
336*4882a593Smuzhiyun    def get_variable_files(self, var):
337*4882a593Smuzhiyun        """Get the files where operations are made on a variable"""
338*4882a593Smuzhiyun        var_history = self.variable(var)
339*4882a593Smuzhiyun        files = []
340*4882a593Smuzhiyun        for event in var_history:
341*4882a593Smuzhiyun            files.append(event['file'])
342*4882a593Smuzhiyun        return files
343*4882a593Smuzhiyun
344*4882a593Smuzhiyun    def get_variable_lines(self, var, f):
345*4882a593Smuzhiyun        """Get the line where a operation is made on a variable in file f"""
346*4882a593Smuzhiyun        var_history = self.variable(var)
347*4882a593Smuzhiyun        lines = []
348*4882a593Smuzhiyun        for event in var_history:
349*4882a593Smuzhiyun            if f== event['file']:
350*4882a593Smuzhiyun                line = event['line']
351*4882a593Smuzhiyun                lines.append(line)
352*4882a593Smuzhiyun        return lines
353*4882a593Smuzhiyun
354*4882a593Smuzhiyun    def get_variable_refs(self, var):
355*4882a593Smuzhiyun        """Return a dict of file/line references"""
356*4882a593Smuzhiyun        var_history = self.variable(var)
357*4882a593Smuzhiyun        refs = {}
358*4882a593Smuzhiyun        for event in var_history:
359*4882a593Smuzhiyun            if event['file'] not in refs:
360*4882a593Smuzhiyun                refs[event['file']] = []
361*4882a593Smuzhiyun            refs[event['file']].append(event['line'])
362*4882a593Smuzhiyun        return refs
363*4882a593Smuzhiyun
364*4882a593Smuzhiyun    def get_variable_items_files(self, var):
365*4882a593Smuzhiyun        """
366*4882a593Smuzhiyun        Use variable history to map items added to a list variable and
367*4882a593Smuzhiyun        the files in which they were added.
368*4882a593Smuzhiyun        """
369*4882a593Smuzhiyun        d = self.dataroot
370*4882a593Smuzhiyun        history = self.variable(var)
371*4882a593Smuzhiyun        finalitems = (d.getVar(var) or '').split()
372*4882a593Smuzhiyun        filemap = {}
373*4882a593Smuzhiyun        isset = False
374*4882a593Smuzhiyun        for event in history:
375*4882a593Smuzhiyun            if 'flag' in event:
376*4882a593Smuzhiyun                continue
377*4882a593Smuzhiyun            if event['op'] == ':remove':
378*4882a593Smuzhiyun                continue
379*4882a593Smuzhiyun            if isset and event['op'] == 'set?':
380*4882a593Smuzhiyun                continue
381*4882a593Smuzhiyun            isset = True
382*4882a593Smuzhiyun            items = d.expand(event['detail']).split()
383*4882a593Smuzhiyun            for item in items:
384*4882a593Smuzhiyun                # This is a little crude but is belt-and-braces to avoid us
385*4882a593Smuzhiyun                # having to handle every possible operation type specifically
386*4882a593Smuzhiyun                if item in finalitems and not item in filemap:
387*4882a593Smuzhiyun                    filemap[item] = event['file']
388*4882a593Smuzhiyun        return filemap
389*4882a593Smuzhiyun
390*4882a593Smuzhiyun    def del_var_history(self, var, f=None, line=None):
391*4882a593Smuzhiyun        """If file f and line are not given, the entire history of var is deleted"""
392*4882a593Smuzhiyun        if var in self.variables:
393*4882a593Smuzhiyun            if f and line:
394*4882a593Smuzhiyun                self.variables[var] = [ x for x in self.variables[var] if x['file']!=f and x['line']!=line]
395*4882a593Smuzhiyun            else:
396*4882a593Smuzhiyun                self.variables[var] = []
397*4882a593Smuzhiyun
398*4882a593Smuzhiyundef _print_rename_error(var, loginfo, renamedvars, fullvar=None):
399*4882a593Smuzhiyun    info = ""
400*4882a593Smuzhiyun    if "file" in loginfo:
401*4882a593Smuzhiyun        info = " file: %s" % loginfo["file"]
402*4882a593Smuzhiyun    if "line" in loginfo:
403*4882a593Smuzhiyun        info += " line: %s" % loginfo["line"]
404*4882a593Smuzhiyun    if fullvar and fullvar != var:
405*4882a593Smuzhiyun        info += " referenced as: %s" % fullvar
406*4882a593Smuzhiyun    if info:
407*4882a593Smuzhiyun        info = " (%s)" % info.strip()
408*4882a593Smuzhiyun    renameinfo = renamedvars[var]
409*4882a593Smuzhiyun    if " " in renameinfo:
410*4882a593Smuzhiyun        # A space signals a string to display instead of a rename
411*4882a593Smuzhiyun        bb.erroronce('Variable %s %s%s' % (var, renameinfo, info))
412*4882a593Smuzhiyun    else:
413*4882a593Smuzhiyun        bb.erroronce('Variable %s has been renamed to %s%s' % (var, renameinfo, info))
414*4882a593Smuzhiyun
415*4882a593Smuzhiyunclass DataSmart(MutableMapping):
416*4882a593Smuzhiyun    def __init__(self):
417*4882a593Smuzhiyun        self.dict = {}
418*4882a593Smuzhiyun
419*4882a593Smuzhiyun        self.inchistory = IncludeHistory()
420*4882a593Smuzhiyun        self.varhistory = VariableHistory(self)
421*4882a593Smuzhiyun        self._tracking = False
422*4882a593Smuzhiyun        self._var_renames = {}
423*4882a593Smuzhiyun        self._var_renames.update(bitbake_renamed_vars)
424*4882a593Smuzhiyun
425*4882a593Smuzhiyun        self.expand_cache = {}
426*4882a593Smuzhiyun
427*4882a593Smuzhiyun        # cookie monster tribute
428*4882a593Smuzhiyun        # Need to be careful about writes to overridedata as
429*4882a593Smuzhiyun        # its only a shallow copy, could influence other data store
430*4882a593Smuzhiyun        # copies!
431*4882a593Smuzhiyun        self.overridedata = {}
432*4882a593Smuzhiyun        self.overrides = None
433*4882a593Smuzhiyun        self.overridevars = set(["OVERRIDES", "FILE"])
434*4882a593Smuzhiyun        self.inoverride = False
435*4882a593Smuzhiyun
436*4882a593Smuzhiyun    def enableTracking(self):
437*4882a593Smuzhiyun        self._tracking = True
438*4882a593Smuzhiyun
439*4882a593Smuzhiyun    def disableTracking(self):
440*4882a593Smuzhiyun        self._tracking = False
441*4882a593Smuzhiyun
442*4882a593Smuzhiyun    def expandWithRefs(self, s, varname):
443*4882a593Smuzhiyun
444*4882a593Smuzhiyun        if not isinstance(s, str): # sanity check
445*4882a593Smuzhiyun            return VariableParse(varname, self, s)
446*4882a593Smuzhiyun
447*4882a593Smuzhiyun        varparse = VariableParse(varname, self)
448*4882a593Smuzhiyun
449*4882a593Smuzhiyun        while s.find('${') != -1:
450*4882a593Smuzhiyun            olds = s
451*4882a593Smuzhiyun            try:
452*4882a593Smuzhiyun                s = __expand_var_regexp__.sub(varparse.var_sub, s)
453*4882a593Smuzhiyun                try:
454*4882a593Smuzhiyun                    s = __expand_python_regexp__.sub(varparse.python_sub, s)
455*4882a593Smuzhiyun                except SyntaxError as e:
456*4882a593Smuzhiyun                    # Likely unmatched brackets, just don't expand the expression
457*4882a593Smuzhiyun                    if e.msg != "EOL while scanning string literal" and not e.msg.startswith("unterminated string literal"):
458*4882a593Smuzhiyun                        raise
459*4882a593Smuzhiyun                if s == olds:
460*4882a593Smuzhiyun                    break
461*4882a593Smuzhiyun            except ExpansionError as e:
462*4882a593Smuzhiyun                e.addVar(varname)
463*4882a593Smuzhiyun                raise
464*4882a593Smuzhiyun            except bb.parse.SkipRecipe:
465*4882a593Smuzhiyun                raise
466*4882a593Smuzhiyun            except bb.BBHandledException:
467*4882a593Smuzhiyun                raise
468*4882a593Smuzhiyun            except Exception as exc:
469*4882a593Smuzhiyun                tb = sys.exc_info()[2]
470*4882a593Smuzhiyun                raise ExpansionError(varname, s, exc).with_traceback(tb) from exc
471*4882a593Smuzhiyun
472*4882a593Smuzhiyun        varparse.value = s
473*4882a593Smuzhiyun
474*4882a593Smuzhiyun        return varparse
475*4882a593Smuzhiyun
476*4882a593Smuzhiyun    def expand(self, s, varname = None):
477*4882a593Smuzhiyun        return self.expandWithRefs(s, varname).value
478*4882a593Smuzhiyun
479*4882a593Smuzhiyun    def finalize(self, parent = False):
480*4882a593Smuzhiyun        return
481*4882a593Smuzhiyun
482*4882a593Smuzhiyun    def internal_finalize(self, parent = False):
483*4882a593Smuzhiyun        """Performs final steps upon the datastore, including application of overrides"""
484*4882a593Smuzhiyun        self.overrides = None
485*4882a593Smuzhiyun
486*4882a593Smuzhiyun    def need_overrides(self):
487*4882a593Smuzhiyun        if self.overrides is not None:
488*4882a593Smuzhiyun            return
489*4882a593Smuzhiyun        if self.inoverride:
490*4882a593Smuzhiyun            return
491*4882a593Smuzhiyun        for count in range(5):
492*4882a593Smuzhiyun            self.inoverride = True
493*4882a593Smuzhiyun            # Can end up here recursively so setup dummy values
494*4882a593Smuzhiyun            self.overrides = []
495*4882a593Smuzhiyun            self.overridesset = set()
496*4882a593Smuzhiyun            self.overrides = (self.getVar("OVERRIDES") or "").split(":") or []
497*4882a593Smuzhiyun            self.overridesset = set(self.overrides)
498*4882a593Smuzhiyun            self.inoverride = False
499*4882a593Smuzhiyun            self.expand_cache = {}
500*4882a593Smuzhiyun            newoverrides = (self.getVar("OVERRIDES") or "").split(":") or []
501*4882a593Smuzhiyun            if newoverrides == self.overrides:
502*4882a593Smuzhiyun                break
503*4882a593Smuzhiyun            self.overrides = newoverrides
504*4882a593Smuzhiyun            self.overridesset = set(self.overrides)
505*4882a593Smuzhiyun        else:
506*4882a593Smuzhiyun            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.")
507*4882a593Smuzhiyun
508*4882a593Smuzhiyun    def initVar(self, var):
509*4882a593Smuzhiyun        self.expand_cache = {}
510*4882a593Smuzhiyun        if not var in self.dict:
511*4882a593Smuzhiyun            self.dict[var] = {}
512*4882a593Smuzhiyun
513*4882a593Smuzhiyun    def _findVar(self, var):
514*4882a593Smuzhiyun        dest = self.dict
515*4882a593Smuzhiyun        while dest:
516*4882a593Smuzhiyun            if var in dest:
517*4882a593Smuzhiyun                return dest[var], self.overridedata.get(var, None)
518*4882a593Smuzhiyun
519*4882a593Smuzhiyun            if "_data" not in dest:
520*4882a593Smuzhiyun                break
521*4882a593Smuzhiyun            dest = dest["_data"]
522*4882a593Smuzhiyun        return None, self.overridedata.get(var, None)
523*4882a593Smuzhiyun
524*4882a593Smuzhiyun    def _makeShadowCopy(self, var):
525*4882a593Smuzhiyun        if var in self.dict:
526*4882a593Smuzhiyun            return
527*4882a593Smuzhiyun
528*4882a593Smuzhiyun        local_var, _ = self._findVar(var)
529*4882a593Smuzhiyun
530*4882a593Smuzhiyun        if local_var:
531*4882a593Smuzhiyun            self.dict[var] = copy.copy(local_var)
532*4882a593Smuzhiyun        else:
533*4882a593Smuzhiyun            self.initVar(var)
534*4882a593Smuzhiyun
535*4882a593Smuzhiyun    def hasOverrides(self, var):
536*4882a593Smuzhiyun        return var in self.overridedata
537*4882a593Smuzhiyun
538*4882a593Smuzhiyun    def setVar(self, var, value, **loginfo):
539*4882a593Smuzhiyun        #print("var=" + str(var) + "  val=" + str(value))
540*4882a593Smuzhiyun
541*4882a593Smuzhiyun        if not var.startswith("__anon_") and ("_append" in var or "_prepend" in var or "_remove" in var):
542*4882a593Smuzhiyun            info = "%s" % var
543*4882a593Smuzhiyun            if "file" in loginfo:
544*4882a593Smuzhiyun                info += " file: %s" % loginfo["file"]
545*4882a593Smuzhiyun            if "line" in loginfo:
546*4882a593Smuzhiyun                info += " line: %s" % loginfo["line"]
547*4882a593Smuzhiyun            bb.fatal("Variable %s contains an operation using the old override syntax. Please convert this layer/metadata before attempting to use with a newer bitbake." % info)
548*4882a593Smuzhiyun
549*4882a593Smuzhiyun        shortvar = var.split(":", 1)[0]
550*4882a593Smuzhiyun        if shortvar in self._var_renames:
551*4882a593Smuzhiyun            _print_rename_error(shortvar, loginfo, self._var_renames, fullvar=var)
552*4882a593Smuzhiyun            # Mark that we have seen a renamed variable
553*4882a593Smuzhiyun            self.setVar("_FAILPARSINGERRORHANDLED", True)
554*4882a593Smuzhiyun
555*4882a593Smuzhiyun        self.expand_cache = {}
556*4882a593Smuzhiyun        parsing=False
557*4882a593Smuzhiyun        if 'parsing' in loginfo:
558*4882a593Smuzhiyun            parsing=True
559*4882a593Smuzhiyun
560*4882a593Smuzhiyun        if 'op' not in loginfo:
561*4882a593Smuzhiyun            loginfo['op'] = "set"
562*4882a593Smuzhiyun
563*4882a593Smuzhiyun        match  = __setvar_regexp__.match(var)
564*4882a593Smuzhiyun        if match and match.group("keyword") in __setvar_keyword__:
565*4882a593Smuzhiyun            base = match.group('base')
566*4882a593Smuzhiyun            keyword = match.group("keyword")
567*4882a593Smuzhiyun            override = match.group('add')
568*4882a593Smuzhiyun            l = self.getVarFlag(base, keyword, False) or []
569*4882a593Smuzhiyun            l.append([value, override])
570*4882a593Smuzhiyun            self.setVarFlag(base, keyword, l, ignore=True)
571*4882a593Smuzhiyun            # And cause that to be recorded:
572*4882a593Smuzhiyun            loginfo['detail'] = value
573*4882a593Smuzhiyun            loginfo['variable'] = base
574*4882a593Smuzhiyun            if override:
575*4882a593Smuzhiyun                loginfo['op'] = '%s[%s]' % (keyword, override)
576*4882a593Smuzhiyun            else:
577*4882a593Smuzhiyun                loginfo['op'] = keyword
578*4882a593Smuzhiyun            self.varhistory.record(**loginfo)
579*4882a593Smuzhiyun            # todo make sure keyword is not __doc__ or __module__
580*4882a593Smuzhiyun            # pay the cookie monster
581*4882a593Smuzhiyun
582*4882a593Smuzhiyun            # more cookies for the cookie monster
583*4882a593Smuzhiyun            if ':' in var:
584*4882a593Smuzhiyun                self._setvar_update_overrides(base, **loginfo)
585*4882a593Smuzhiyun
586*4882a593Smuzhiyun            if base in self.overridevars:
587*4882a593Smuzhiyun                self._setvar_update_overridevars(var, value)
588*4882a593Smuzhiyun            return
589*4882a593Smuzhiyun
590*4882a593Smuzhiyun        if not var in self.dict:
591*4882a593Smuzhiyun            self._makeShadowCopy(var)
592*4882a593Smuzhiyun
593*4882a593Smuzhiyun        if not parsing:
594*4882a593Smuzhiyun            if ":append" in self.dict[var]:
595*4882a593Smuzhiyun                del self.dict[var][":append"]
596*4882a593Smuzhiyun            if ":prepend" in self.dict[var]:
597*4882a593Smuzhiyun                del self.dict[var][":prepend"]
598*4882a593Smuzhiyun            if ":remove" in self.dict[var]:
599*4882a593Smuzhiyun                del self.dict[var][":remove"]
600*4882a593Smuzhiyun            if var in self.overridedata:
601*4882a593Smuzhiyun                active = []
602*4882a593Smuzhiyun                self.need_overrides()
603*4882a593Smuzhiyun                for (r, o) in self.overridedata[var]:
604*4882a593Smuzhiyun                    if o in self.overridesset:
605*4882a593Smuzhiyun                        active.append(r)
606*4882a593Smuzhiyun                    elif ":" in o:
607*4882a593Smuzhiyun                        if set(o.split(":")).issubset(self.overridesset):
608*4882a593Smuzhiyun                            active.append(r)
609*4882a593Smuzhiyun                for a in active:
610*4882a593Smuzhiyun                    self.delVar(a)
611*4882a593Smuzhiyun                del self.overridedata[var]
612*4882a593Smuzhiyun
613*4882a593Smuzhiyun        # more cookies for the cookie monster
614*4882a593Smuzhiyun        if ':' in var:
615*4882a593Smuzhiyun            self._setvar_update_overrides(var, **loginfo)
616*4882a593Smuzhiyun
617*4882a593Smuzhiyun        # setting var
618*4882a593Smuzhiyun        self.dict[var]["_content"] = value
619*4882a593Smuzhiyun        self.varhistory.record(**loginfo)
620*4882a593Smuzhiyun
621*4882a593Smuzhiyun        if var in self.overridevars:
622*4882a593Smuzhiyun            self._setvar_update_overridevars(var, value)
623*4882a593Smuzhiyun
624*4882a593Smuzhiyun    def _setvar_update_overridevars(self, var, value):
625*4882a593Smuzhiyun        vardata = self.expandWithRefs(value, var)
626*4882a593Smuzhiyun        new = vardata.references
627*4882a593Smuzhiyun        new.update(vardata.contains.keys())
628*4882a593Smuzhiyun        while not new.issubset(self.overridevars):
629*4882a593Smuzhiyun            nextnew = set()
630*4882a593Smuzhiyun            self.overridevars.update(new)
631*4882a593Smuzhiyun            for i in new:
632*4882a593Smuzhiyun                vardata = self.expandWithRefs(self.getVar(i), i)
633*4882a593Smuzhiyun                nextnew.update(vardata.references)
634*4882a593Smuzhiyun                nextnew.update(vardata.contains.keys())
635*4882a593Smuzhiyun            new = nextnew
636*4882a593Smuzhiyun        self.internal_finalize(True)
637*4882a593Smuzhiyun
638*4882a593Smuzhiyun    def _setvar_update_overrides(self, var, **loginfo):
639*4882a593Smuzhiyun        # aka pay the cookie monster
640*4882a593Smuzhiyun        override = var[var.rfind(':')+1:]
641*4882a593Smuzhiyun        shortvar = var[:var.rfind(':')]
642*4882a593Smuzhiyun        while override and __override_regexp__.match(override):
643*4882a593Smuzhiyun            if shortvar not in self.overridedata:
644*4882a593Smuzhiyun                self.overridedata[shortvar] = []
645*4882a593Smuzhiyun            if [var, override] not in self.overridedata[shortvar]:
646*4882a593Smuzhiyun                # Force CoW by recreating the list first
647*4882a593Smuzhiyun                self.overridedata[shortvar] = list(self.overridedata[shortvar])
648*4882a593Smuzhiyun                self.overridedata[shortvar].append([var, override])
649*4882a593Smuzhiyun            override = None
650*4882a593Smuzhiyun            if ":" in shortvar:
651*4882a593Smuzhiyun                override = var[shortvar.rfind(':')+1:]
652*4882a593Smuzhiyun                shortvar = var[:shortvar.rfind(':')]
653*4882a593Smuzhiyun                if len(shortvar) == 0:
654*4882a593Smuzhiyun                    override = None
655*4882a593Smuzhiyun
656*4882a593Smuzhiyun    def getVar(self, var, expand=True, noweakdefault=False, parsing=False):
657*4882a593Smuzhiyun        return self.getVarFlag(var, "_content", expand, noweakdefault, parsing)
658*4882a593Smuzhiyun
659*4882a593Smuzhiyun    def renameVar(self, key, newkey, **loginfo):
660*4882a593Smuzhiyun        """
661*4882a593Smuzhiyun        Rename the variable key to newkey
662*4882a593Smuzhiyun        """
663*4882a593Smuzhiyun        if key == newkey:
664*4882a593Smuzhiyun            bb.warn("Calling renameVar with equivalent keys (%s) is invalid" % key)
665*4882a593Smuzhiyun            return
666*4882a593Smuzhiyun
667*4882a593Smuzhiyun        val = self.getVar(key, 0, parsing=True)
668*4882a593Smuzhiyun        if val is not None:
669*4882a593Smuzhiyun            self.varhistory.rename_variable_hist(key, newkey)
670*4882a593Smuzhiyun            loginfo['variable'] = newkey
671*4882a593Smuzhiyun            loginfo['op'] = 'rename from %s' % key
672*4882a593Smuzhiyun            loginfo['detail'] = val
673*4882a593Smuzhiyun            self.varhistory.record(**loginfo)
674*4882a593Smuzhiyun            self.setVar(newkey, val, ignore=True, parsing=True)
675*4882a593Smuzhiyun
676*4882a593Smuzhiyun        srcflags = self.getVarFlags(key, False, True) or {}
677*4882a593Smuzhiyun        for i in srcflags:
678*4882a593Smuzhiyun            if i not in (__setvar_keyword__):
679*4882a593Smuzhiyun                continue
680*4882a593Smuzhiyun            src = srcflags[i]
681*4882a593Smuzhiyun
682*4882a593Smuzhiyun            dest = self.getVarFlag(newkey, i, False) or []
683*4882a593Smuzhiyun            dest.extend(src)
684*4882a593Smuzhiyun            self.setVarFlag(newkey, i, dest, ignore=True)
685*4882a593Smuzhiyun
686*4882a593Smuzhiyun        if key in self.overridedata:
687*4882a593Smuzhiyun            self.overridedata[newkey] = []
688*4882a593Smuzhiyun            for (v, o) in self.overridedata[key]:
689*4882a593Smuzhiyun                self.overridedata[newkey].append([v.replace(key, newkey), o])
690*4882a593Smuzhiyun                self.renameVar(v, v.replace(key, newkey))
691*4882a593Smuzhiyun
692*4882a593Smuzhiyun        if ':' in newkey and val is None:
693*4882a593Smuzhiyun            self._setvar_update_overrides(newkey, **loginfo)
694*4882a593Smuzhiyun
695*4882a593Smuzhiyun        loginfo['variable'] = key
696*4882a593Smuzhiyun        loginfo['op'] = 'rename (to)'
697*4882a593Smuzhiyun        loginfo['detail'] = newkey
698*4882a593Smuzhiyun        self.varhistory.record(**loginfo)
699*4882a593Smuzhiyun        self.delVar(key, ignore=True)
700*4882a593Smuzhiyun
701*4882a593Smuzhiyun    def appendVar(self, var, value, **loginfo):
702*4882a593Smuzhiyun        loginfo['op'] = 'append'
703*4882a593Smuzhiyun        self.varhistory.record(**loginfo)
704*4882a593Smuzhiyun        self.setVar(var + ":append", value, ignore=True, parsing=True)
705*4882a593Smuzhiyun
706*4882a593Smuzhiyun    def prependVar(self, var, value, **loginfo):
707*4882a593Smuzhiyun        loginfo['op'] = 'prepend'
708*4882a593Smuzhiyun        self.varhistory.record(**loginfo)
709*4882a593Smuzhiyun        self.setVar(var + ":prepend", value, ignore=True, parsing=True)
710*4882a593Smuzhiyun
711*4882a593Smuzhiyun    def delVar(self, var, **loginfo):
712*4882a593Smuzhiyun        self.expand_cache = {}
713*4882a593Smuzhiyun
714*4882a593Smuzhiyun        loginfo['detail'] = ""
715*4882a593Smuzhiyun        loginfo['op'] = 'del'
716*4882a593Smuzhiyun        self.varhistory.record(**loginfo)
717*4882a593Smuzhiyun        self.dict[var] = {}
718*4882a593Smuzhiyun        if var in self.overridedata:
719*4882a593Smuzhiyun            del self.overridedata[var]
720*4882a593Smuzhiyun        if ':' in var:
721*4882a593Smuzhiyun            override = var[var.rfind(':')+1:]
722*4882a593Smuzhiyun            shortvar = var[:var.rfind(':')]
723*4882a593Smuzhiyun            while override and override.islower():
724*4882a593Smuzhiyun                try:
725*4882a593Smuzhiyun                    if shortvar in self.overridedata:
726*4882a593Smuzhiyun                        # Force CoW by recreating the list first
727*4882a593Smuzhiyun                        self.overridedata[shortvar] = list(self.overridedata[shortvar])
728*4882a593Smuzhiyun                        self.overridedata[shortvar].remove([var, override])
729*4882a593Smuzhiyun                except ValueError as e:
730*4882a593Smuzhiyun                    pass
731*4882a593Smuzhiyun                override = None
732*4882a593Smuzhiyun                if ":" in shortvar:
733*4882a593Smuzhiyun                    override = var[shortvar.rfind(':')+1:]
734*4882a593Smuzhiyun                    shortvar = var[:shortvar.rfind(':')]
735*4882a593Smuzhiyun                    if len(shortvar) == 0:
736*4882a593Smuzhiyun                         override = None
737*4882a593Smuzhiyun
738*4882a593Smuzhiyun    def setVarFlag(self, var, flag, value, **loginfo):
739*4882a593Smuzhiyun        self.expand_cache = {}
740*4882a593Smuzhiyun
741*4882a593Smuzhiyun        if var == "BB_RENAMED_VARIABLES":
742*4882a593Smuzhiyun            self._var_renames[flag] = value
743*4882a593Smuzhiyun
744*4882a593Smuzhiyun        if var in self._var_renames:
745*4882a593Smuzhiyun            _print_rename_error(var, loginfo, self._var_renames)
746*4882a593Smuzhiyun            # Mark that we have seen a renamed variable
747*4882a593Smuzhiyun            self.setVar("_FAILPARSINGERRORHANDLED", True)
748*4882a593Smuzhiyun
749*4882a593Smuzhiyun        if 'op' not in loginfo:
750*4882a593Smuzhiyun            loginfo['op'] = "set"
751*4882a593Smuzhiyun        loginfo['flag'] = flag
752*4882a593Smuzhiyun        self.varhistory.record(**loginfo)
753*4882a593Smuzhiyun        if not var in self.dict:
754*4882a593Smuzhiyun            self._makeShadowCopy(var)
755*4882a593Smuzhiyun        self.dict[var][flag] = value
756*4882a593Smuzhiyun
757*4882a593Smuzhiyun        if flag == "_defaultval" and ':' in var:
758*4882a593Smuzhiyun            self._setvar_update_overrides(var, **loginfo)
759*4882a593Smuzhiyun        if flag == "_defaultval" and var in self.overridevars:
760*4882a593Smuzhiyun            self._setvar_update_overridevars(var, value)
761*4882a593Smuzhiyun
762*4882a593Smuzhiyun        if flag == "unexport" or flag == "export":
763*4882a593Smuzhiyun            if not "__exportlist" in self.dict:
764*4882a593Smuzhiyun                self._makeShadowCopy("__exportlist")
765*4882a593Smuzhiyun            if not "_content" in self.dict["__exportlist"]:
766*4882a593Smuzhiyun                self.dict["__exportlist"]["_content"] = set()
767*4882a593Smuzhiyun            self.dict["__exportlist"]["_content"].add(var)
768*4882a593Smuzhiyun
769*4882a593Smuzhiyun    def getVarFlag(self, var, flag, expand=True, noweakdefault=False, parsing=False, retparser=False):
770*4882a593Smuzhiyun        if flag == "_content":
771*4882a593Smuzhiyun            cachename = var
772*4882a593Smuzhiyun        else:
773*4882a593Smuzhiyun            if not flag:
774*4882a593Smuzhiyun                bb.warn("Calling getVarFlag with flag unset is invalid")
775*4882a593Smuzhiyun                return None
776*4882a593Smuzhiyun            cachename = var + "[" + flag + "]"
777*4882a593Smuzhiyun
778*4882a593Smuzhiyun        if expand and cachename in self.expand_cache:
779*4882a593Smuzhiyun            return self.expand_cache[cachename].value
780*4882a593Smuzhiyun
781*4882a593Smuzhiyun        local_var, overridedata = self._findVar(var)
782*4882a593Smuzhiyun        value = None
783*4882a593Smuzhiyun        removes = set()
784*4882a593Smuzhiyun        if flag == "_content" and overridedata is not None and not parsing:
785*4882a593Smuzhiyun            match = False
786*4882a593Smuzhiyun            active = {}
787*4882a593Smuzhiyun            self.need_overrides()
788*4882a593Smuzhiyun            for (r, o) in overridedata:
789*4882a593Smuzhiyun                # FIXME What about double overrides both with "_" in the name?
790*4882a593Smuzhiyun                if o in self.overridesset:
791*4882a593Smuzhiyun                    active[o] = r
792*4882a593Smuzhiyun                elif ":" in o:
793*4882a593Smuzhiyun                    if set(o.split(":")).issubset(self.overridesset):
794*4882a593Smuzhiyun                        active[o] = r
795*4882a593Smuzhiyun
796*4882a593Smuzhiyun            mod = True
797*4882a593Smuzhiyun            while mod:
798*4882a593Smuzhiyun                mod = False
799*4882a593Smuzhiyun                for o in self.overrides:
800*4882a593Smuzhiyun                    for a in active.copy():
801*4882a593Smuzhiyun                        if a.endswith(":" + o):
802*4882a593Smuzhiyun                            t = active[a]
803*4882a593Smuzhiyun                            del active[a]
804*4882a593Smuzhiyun                            active[a.replace(":" + o, "")] = t
805*4882a593Smuzhiyun                            mod = True
806*4882a593Smuzhiyun                        elif a == o:
807*4882a593Smuzhiyun                            match = active[a]
808*4882a593Smuzhiyun                            del active[a]
809*4882a593Smuzhiyun            if match:
810*4882a593Smuzhiyun                value, subparser = self.getVarFlag(match, "_content", False, retparser=True)
811*4882a593Smuzhiyun                if hasattr(subparser, "removes"):
812*4882a593Smuzhiyun                    # We have to carry the removes from the overridden variable to apply at the
813*4882a593Smuzhiyun                    # end of processing
814*4882a593Smuzhiyun                    removes = subparser.removes
815*4882a593Smuzhiyun
816*4882a593Smuzhiyun        if local_var is not None and value is None:
817*4882a593Smuzhiyun            if flag in local_var:
818*4882a593Smuzhiyun                value = copy.copy(local_var[flag])
819*4882a593Smuzhiyun            elif flag == "_content" and "_defaultval" in local_var and not noweakdefault:
820*4882a593Smuzhiyun                value = copy.copy(local_var["_defaultval"])
821*4882a593Smuzhiyun
822*4882a593Smuzhiyun
823*4882a593Smuzhiyun        if flag == "_content" and local_var is not None and ":append" in local_var and not parsing:
824*4882a593Smuzhiyun            self.need_overrides()
825*4882a593Smuzhiyun            for (r, o) in local_var[":append"]:
826*4882a593Smuzhiyun                match = True
827*4882a593Smuzhiyun                if o:
828*4882a593Smuzhiyun                    for o2 in o.split(":"):
829*4882a593Smuzhiyun                        if not o2 in self.overrides:
830*4882a593Smuzhiyun                            match = False
831*4882a593Smuzhiyun                if match:
832*4882a593Smuzhiyun                    if value is None:
833*4882a593Smuzhiyun                        value = ""
834*4882a593Smuzhiyun                    value = value + r
835*4882a593Smuzhiyun
836*4882a593Smuzhiyun        if flag == "_content" and local_var is not None and ":prepend" in local_var and not parsing:
837*4882a593Smuzhiyun            self.need_overrides()
838*4882a593Smuzhiyun            for (r, o) in local_var[":prepend"]:
839*4882a593Smuzhiyun
840*4882a593Smuzhiyun                match = True
841*4882a593Smuzhiyun                if o:
842*4882a593Smuzhiyun                    for o2 in o.split(":"):
843*4882a593Smuzhiyun                        if not o2 in self.overrides:
844*4882a593Smuzhiyun                            match = False
845*4882a593Smuzhiyun                if match:
846*4882a593Smuzhiyun                    if value is None:
847*4882a593Smuzhiyun                        value = ""
848*4882a593Smuzhiyun                    value = r + value
849*4882a593Smuzhiyun
850*4882a593Smuzhiyun        parser = None
851*4882a593Smuzhiyun        if expand or retparser:
852*4882a593Smuzhiyun            parser = self.expandWithRefs(value, cachename)
853*4882a593Smuzhiyun        if expand:
854*4882a593Smuzhiyun            value = parser.value
855*4882a593Smuzhiyun
856*4882a593Smuzhiyun        if value and flag == "_content" and local_var is not None and ":remove" in local_var and not parsing:
857*4882a593Smuzhiyun            self.need_overrides()
858*4882a593Smuzhiyun            for (r, o) in local_var[":remove"]:
859*4882a593Smuzhiyun                match = True
860*4882a593Smuzhiyun                if o:
861*4882a593Smuzhiyun                    for o2 in o.split(":"):
862*4882a593Smuzhiyun                        if not o2 in self.overrides:
863*4882a593Smuzhiyun                            match = False
864*4882a593Smuzhiyun                if match:
865*4882a593Smuzhiyun                    removes.add(r)
866*4882a593Smuzhiyun
867*4882a593Smuzhiyun        if value and flag == "_content" and not parsing:
868*4882a593Smuzhiyun            if removes and parser:
869*4882a593Smuzhiyun                expanded_removes = {}
870*4882a593Smuzhiyun                for r in removes:
871*4882a593Smuzhiyun                    expanded_removes[r] = self.expand(r).split()
872*4882a593Smuzhiyun
873*4882a593Smuzhiyun                parser.removes = set()
874*4882a593Smuzhiyun                val = []
875*4882a593Smuzhiyun                for v in __whitespace_split__.split(parser.value):
876*4882a593Smuzhiyun                    skip = False
877*4882a593Smuzhiyun                    for r in removes:
878*4882a593Smuzhiyun                        if v in expanded_removes[r]:
879*4882a593Smuzhiyun                            parser.removes.add(r)
880*4882a593Smuzhiyun                            skip = True
881*4882a593Smuzhiyun                    if skip:
882*4882a593Smuzhiyun                        continue
883*4882a593Smuzhiyun                    val.append(v)
884*4882a593Smuzhiyun                parser.value = "".join(val)
885*4882a593Smuzhiyun                if expand:
886*4882a593Smuzhiyun                    value = parser.value
887*4882a593Smuzhiyun
888*4882a593Smuzhiyun        if parser:
889*4882a593Smuzhiyun            self.expand_cache[cachename] = parser
890*4882a593Smuzhiyun
891*4882a593Smuzhiyun        if retparser:
892*4882a593Smuzhiyun            return value, parser
893*4882a593Smuzhiyun
894*4882a593Smuzhiyun        return value
895*4882a593Smuzhiyun
896*4882a593Smuzhiyun    def delVarFlag(self, var, flag, **loginfo):
897*4882a593Smuzhiyun        self.expand_cache = {}
898*4882a593Smuzhiyun
899*4882a593Smuzhiyun        local_var, _ = self._findVar(var)
900*4882a593Smuzhiyun        if not local_var:
901*4882a593Smuzhiyun            return
902*4882a593Smuzhiyun        if not var in self.dict:
903*4882a593Smuzhiyun            self._makeShadowCopy(var)
904*4882a593Smuzhiyun
905*4882a593Smuzhiyun        if var in self.dict and flag in self.dict[var]:
906*4882a593Smuzhiyun            loginfo['detail'] = ""
907*4882a593Smuzhiyun            loginfo['op'] = 'delFlag'
908*4882a593Smuzhiyun            loginfo['flag'] = flag
909*4882a593Smuzhiyun            self.varhistory.record(**loginfo)
910*4882a593Smuzhiyun
911*4882a593Smuzhiyun            del self.dict[var][flag]
912*4882a593Smuzhiyun
913*4882a593Smuzhiyun    def appendVarFlag(self, var, flag, value, **loginfo):
914*4882a593Smuzhiyun        loginfo['op'] = 'append'
915*4882a593Smuzhiyun        loginfo['flag'] = flag
916*4882a593Smuzhiyun        self.varhistory.record(**loginfo)
917*4882a593Smuzhiyun        newvalue = (self.getVarFlag(var, flag, False) or "") + value
918*4882a593Smuzhiyun        self.setVarFlag(var, flag, newvalue, ignore=True)
919*4882a593Smuzhiyun
920*4882a593Smuzhiyun    def prependVarFlag(self, var, flag, value, **loginfo):
921*4882a593Smuzhiyun        loginfo['op'] = 'prepend'
922*4882a593Smuzhiyun        loginfo['flag'] = flag
923*4882a593Smuzhiyun        self.varhistory.record(**loginfo)
924*4882a593Smuzhiyun        newvalue = value + (self.getVarFlag(var, flag, False) or "")
925*4882a593Smuzhiyun        self.setVarFlag(var, flag, newvalue, ignore=True)
926*4882a593Smuzhiyun
927*4882a593Smuzhiyun    def setVarFlags(self, var, flags, **loginfo):
928*4882a593Smuzhiyun        self.expand_cache = {}
929*4882a593Smuzhiyun        infer_caller_details(loginfo)
930*4882a593Smuzhiyun        if not var in self.dict:
931*4882a593Smuzhiyun            self._makeShadowCopy(var)
932*4882a593Smuzhiyun
933*4882a593Smuzhiyun        for i in flags:
934*4882a593Smuzhiyun            if i == "_content":
935*4882a593Smuzhiyun                continue
936*4882a593Smuzhiyun            loginfo['flag'] = i
937*4882a593Smuzhiyun            loginfo['detail'] = flags[i]
938*4882a593Smuzhiyun            self.varhistory.record(**loginfo)
939*4882a593Smuzhiyun            self.dict[var][i] = flags[i]
940*4882a593Smuzhiyun
941*4882a593Smuzhiyun    def getVarFlags(self, var, expand = False, internalflags=False):
942*4882a593Smuzhiyun        local_var, _ = self._findVar(var)
943*4882a593Smuzhiyun        flags = {}
944*4882a593Smuzhiyun
945*4882a593Smuzhiyun        if local_var:
946*4882a593Smuzhiyun            for i in local_var:
947*4882a593Smuzhiyun                if i.startswith(("_", ":")) and not internalflags:
948*4882a593Smuzhiyun                    continue
949*4882a593Smuzhiyun                flags[i] = local_var[i]
950*4882a593Smuzhiyun                if expand and i in expand:
951*4882a593Smuzhiyun                    flags[i] = self.expand(flags[i], var + "[" + i + "]")
952*4882a593Smuzhiyun        if len(flags) == 0:
953*4882a593Smuzhiyun            return None
954*4882a593Smuzhiyun        return flags
955*4882a593Smuzhiyun
956*4882a593Smuzhiyun
957*4882a593Smuzhiyun    def delVarFlags(self, var, **loginfo):
958*4882a593Smuzhiyun        self.expand_cache = {}
959*4882a593Smuzhiyun        if not var in self.dict:
960*4882a593Smuzhiyun            self._makeShadowCopy(var)
961*4882a593Smuzhiyun
962*4882a593Smuzhiyun        if var in self.dict:
963*4882a593Smuzhiyun            content = None
964*4882a593Smuzhiyun
965*4882a593Smuzhiyun            loginfo['op'] = 'delete flags'
966*4882a593Smuzhiyun            self.varhistory.record(**loginfo)
967*4882a593Smuzhiyun
968*4882a593Smuzhiyun            # try to save the content
969*4882a593Smuzhiyun            if "_content" in self.dict[var]:
970*4882a593Smuzhiyun                content  = self.dict[var]["_content"]
971*4882a593Smuzhiyun                self.dict[var]            = {}
972*4882a593Smuzhiyun                self.dict[var]["_content"] = content
973*4882a593Smuzhiyun            else:
974*4882a593Smuzhiyun                del self.dict[var]
975*4882a593Smuzhiyun
976*4882a593Smuzhiyun    def createCopy(self):
977*4882a593Smuzhiyun        """
978*4882a593Smuzhiyun        Create a copy of self by setting _data to self
979*4882a593Smuzhiyun        """
980*4882a593Smuzhiyun        # we really want this to be a DataSmart...
981*4882a593Smuzhiyun        data = DataSmart()
982*4882a593Smuzhiyun        data.dict["_data"] = self.dict
983*4882a593Smuzhiyun        data.varhistory = self.varhistory.copy()
984*4882a593Smuzhiyun        data.varhistory.dataroot = data
985*4882a593Smuzhiyun        data.inchistory = self.inchistory.copy()
986*4882a593Smuzhiyun
987*4882a593Smuzhiyun        data._tracking = self._tracking
988*4882a593Smuzhiyun        data._var_renames = self._var_renames
989*4882a593Smuzhiyun
990*4882a593Smuzhiyun        data.overrides = None
991*4882a593Smuzhiyun        data.overridevars = copy.copy(self.overridevars)
992*4882a593Smuzhiyun        # Should really be a deepcopy but has heavy overhead.
993*4882a593Smuzhiyun        # Instead, we're careful with writes.
994*4882a593Smuzhiyun        data.overridedata = copy.copy(self.overridedata)
995*4882a593Smuzhiyun
996*4882a593Smuzhiyun        return data
997*4882a593Smuzhiyun
998*4882a593Smuzhiyun    def expandVarref(self, variable, parents=False):
999*4882a593Smuzhiyun        """Find all references to variable in the data and expand it
1000*4882a593Smuzhiyun           in place, optionally descending to parent datastores."""
1001*4882a593Smuzhiyun
1002*4882a593Smuzhiyun        if parents:
1003*4882a593Smuzhiyun            keys = iter(self)
1004*4882a593Smuzhiyun        else:
1005*4882a593Smuzhiyun            keys = self.localkeys()
1006*4882a593Smuzhiyun
1007*4882a593Smuzhiyun        ref = '${%s}' % variable
1008*4882a593Smuzhiyun        value = self.getVar(variable, False)
1009*4882a593Smuzhiyun        for key in keys:
1010*4882a593Smuzhiyun            referrervalue = self.getVar(key, False)
1011*4882a593Smuzhiyun            if referrervalue and isinstance(referrervalue, str) and ref in referrervalue:
1012*4882a593Smuzhiyun                self.setVar(key, referrervalue.replace(ref, value))
1013*4882a593Smuzhiyun
1014*4882a593Smuzhiyun    def localkeys(self):
1015*4882a593Smuzhiyun        for key in self.dict:
1016*4882a593Smuzhiyun            if key not in ['_data']:
1017*4882a593Smuzhiyun                yield key
1018*4882a593Smuzhiyun
1019*4882a593Smuzhiyun    def __iter__(self):
1020*4882a593Smuzhiyun        deleted = set()
1021*4882a593Smuzhiyun        overrides = set()
1022*4882a593Smuzhiyun        def keylist(d):
1023*4882a593Smuzhiyun            klist = set()
1024*4882a593Smuzhiyun            for key in d:
1025*4882a593Smuzhiyun                if key in ["_data"]:
1026*4882a593Smuzhiyun                    continue
1027*4882a593Smuzhiyun                if key in deleted:
1028*4882a593Smuzhiyun                    continue
1029*4882a593Smuzhiyun                if key in overrides:
1030*4882a593Smuzhiyun                    continue
1031*4882a593Smuzhiyun                if not d[key]:
1032*4882a593Smuzhiyun                    deleted.add(key)
1033*4882a593Smuzhiyun                    continue
1034*4882a593Smuzhiyun                klist.add(key)
1035*4882a593Smuzhiyun
1036*4882a593Smuzhiyun            if "_data" in d:
1037*4882a593Smuzhiyun                klist |= keylist(d["_data"])
1038*4882a593Smuzhiyun
1039*4882a593Smuzhiyun            return klist
1040*4882a593Smuzhiyun
1041*4882a593Smuzhiyun        self.need_overrides()
1042*4882a593Smuzhiyun        for var in self.overridedata:
1043*4882a593Smuzhiyun            for (r, o) in self.overridedata[var]:
1044*4882a593Smuzhiyun                if o in self.overridesset:
1045*4882a593Smuzhiyun                    overrides.add(var)
1046*4882a593Smuzhiyun                elif ":" in o:
1047*4882a593Smuzhiyun                    if set(o.split(":")).issubset(self.overridesset):
1048*4882a593Smuzhiyun                        overrides.add(var)
1049*4882a593Smuzhiyun
1050*4882a593Smuzhiyun        for k in keylist(self.dict):
1051*4882a593Smuzhiyun             yield k
1052*4882a593Smuzhiyun
1053*4882a593Smuzhiyun        for k in overrides:
1054*4882a593Smuzhiyun             yield k
1055*4882a593Smuzhiyun
1056*4882a593Smuzhiyun    def __len__(self):
1057*4882a593Smuzhiyun        return len(frozenset(iter(self)))
1058*4882a593Smuzhiyun
1059*4882a593Smuzhiyun    def __getitem__(self, item):
1060*4882a593Smuzhiyun        value = self.getVar(item, False)
1061*4882a593Smuzhiyun        if value is None:
1062*4882a593Smuzhiyun            raise KeyError(item)
1063*4882a593Smuzhiyun        else:
1064*4882a593Smuzhiyun            return value
1065*4882a593Smuzhiyun
1066*4882a593Smuzhiyun    def __setitem__(self, var, value):
1067*4882a593Smuzhiyun        self.setVar(var, value)
1068*4882a593Smuzhiyun
1069*4882a593Smuzhiyun    def __delitem__(self, var):
1070*4882a593Smuzhiyun        self.delVar(var)
1071*4882a593Smuzhiyun
1072*4882a593Smuzhiyun    def get_hash(self):
1073*4882a593Smuzhiyun        data = {}
1074*4882a593Smuzhiyun        d = self.createCopy()
1075*4882a593Smuzhiyun        bb.data.expandKeys(d)
1076*4882a593Smuzhiyun
1077*4882a593Smuzhiyun        config_ignore_vars = set((d.getVar("BB_HASHCONFIG_IGNORE_VARS") or "").split())
1078*4882a593Smuzhiyun        keys = set(key for key in iter(d) if not key.startswith("__"))
1079*4882a593Smuzhiyun        for key in keys:
1080*4882a593Smuzhiyun            if key in config_ignore_vars:
1081*4882a593Smuzhiyun                continue
1082*4882a593Smuzhiyun
1083*4882a593Smuzhiyun            value = d.getVar(key, False) or ""
1084*4882a593Smuzhiyun            if type(value) is type(self):
1085*4882a593Smuzhiyun                data.update({key:value.get_hash()})
1086*4882a593Smuzhiyun            else:
1087*4882a593Smuzhiyun                data.update({key:value})
1088*4882a593Smuzhiyun
1089*4882a593Smuzhiyun            varflags = d.getVarFlags(key, internalflags = True, expand=["vardepvalue"])
1090*4882a593Smuzhiyun            if not varflags:
1091*4882a593Smuzhiyun                continue
1092*4882a593Smuzhiyun            for f in varflags:
1093*4882a593Smuzhiyun                if f == "_content":
1094*4882a593Smuzhiyun                    continue
1095*4882a593Smuzhiyun                data.update({'%s[%s]' % (key, f):varflags[f]})
1096*4882a593Smuzhiyun
1097*4882a593Smuzhiyun        for key in ["__BBTASKS", "__BBANONFUNCS", "__BBHANDLERS"]:
1098*4882a593Smuzhiyun            bb_list = d.getVar(key, False) or []
1099*4882a593Smuzhiyun            data.update({key:str(bb_list)})
1100*4882a593Smuzhiyun
1101*4882a593Smuzhiyun            if key == "__BBANONFUNCS":
1102*4882a593Smuzhiyun                for i in bb_list:
1103*4882a593Smuzhiyun                    value = d.getVar(i, False) or ""
1104*4882a593Smuzhiyun                    data.update({i:value})
1105*4882a593Smuzhiyun
1106*4882a593Smuzhiyun        data_str = str([(k, data[k]) for k in sorted(data.keys())])
1107*4882a593Smuzhiyun        return hashlib.sha256(data_str.encode("utf-8")).hexdigest()
1108