| # ex:ts=4:sw=4:sts=4:et |
| # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- |
| """ |
| BitBake 'Data' implementations |
| |
| Functions for interacting with the data structure used by the |
| BitBake build tools. |
| |
| The expandKeys and update_data are the most expensive |
| operations. At night the cookie monster came by and |
| suggested 'give me cookies on setting the variables and |
| things will work out'. Taking this suggestion into account |
| applying the skills from the not yet passed 'Entwurf und |
| Analyse von Algorithmen' lecture and the cookie |
| monster seems to be right. We will track setVar more carefully |
| to have faster update_data and expandKeys operations. |
| |
| This is a trade-off between speed and memory again but |
| the speed is more critical here. |
| """ |
| |
| # Copyright (C) 2003, 2004 Chris Larson |
| # Copyright (C) 2005 Holger Hans Peter Freyther |
| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License version 2 as |
| # published by the Free Software Foundation. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License along |
| # with this program; if not, write to the Free Software Foundation, Inc., |
| # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| # |
| # Based on functions from the base bb module, Copyright 2003 Holger Schurig |
| |
| import sys, os, re |
| if sys.argv[0][-5:] == "pydoc": |
| path = os.path.dirname(os.path.dirname(sys.argv[1])) |
| else: |
| path = os.path.dirname(os.path.dirname(sys.argv[0])) |
| sys.path.insert(0, path) |
| from itertools import groupby |
| |
| from bb import data_smart |
| from bb import codeparser |
| import bb |
| |
| logger = data_smart.logger |
| _dict_type = data_smart.DataSmart |
| |
| def init(): |
| """Return a new object representing the Bitbake data""" |
| return _dict_type() |
| |
| def init_db(parent = None): |
| """Return a new object representing the Bitbake data, |
| optionally based on an existing object""" |
| if parent is not None: |
| return parent.createCopy() |
| else: |
| return _dict_type() |
| |
| def createCopy(source): |
| """Link the source set to the destination |
| If one does not find the value in the destination set, |
| search will go on to the source set to get the value. |
| Value from source are copy-on-write. i.e. any try to |
| modify one of them will end up putting the modified value |
| in the destination set. |
| """ |
| return source.createCopy() |
| |
| def initVar(var, d): |
| """Non-destructive var init for data structure""" |
| d.initVar(var) |
| |
| |
| def setVar(var, value, d): |
| """Set a variable to a given value""" |
| d.setVar(var, value) |
| |
| |
| def getVar(var, d, exp = False): |
| """Gets the value of a variable""" |
| return d.getVar(var, exp) |
| |
| |
| def renameVar(key, newkey, d): |
| """Renames a variable from key to newkey""" |
| d.renameVar(key, newkey) |
| |
| def delVar(var, d): |
| """Removes a variable from the data set""" |
| d.delVar(var) |
| |
| def appendVar(var, value, d): |
| """Append additional value to a variable""" |
| d.appendVar(var, value) |
| |
| def setVarFlag(var, flag, flagvalue, d): |
| """Set a flag for a given variable to a given value""" |
| d.setVarFlag(var, flag, flagvalue) |
| |
| def getVarFlag(var, flag, d): |
| """Gets given flag from given var""" |
| return d.getVarFlag(var, flag, False) |
| |
| def delVarFlag(var, flag, d): |
| """Removes a given flag from the variable's flags""" |
| d.delVarFlag(var, flag) |
| |
| def setVarFlags(var, flags, d): |
| """Set the flags for a given variable |
| |
| Note: |
| setVarFlags will not clear previous |
| flags. Think of this method as |
| addVarFlags |
| """ |
| d.setVarFlags(var, flags) |
| |
| def getVarFlags(var, d): |
| """Gets a variable's flags""" |
| return d.getVarFlags(var) |
| |
| def delVarFlags(var, d): |
| """Removes a variable's flags""" |
| d.delVarFlags(var) |
| |
| def keys(d): |
| """Return a list of keys in d""" |
| return d.keys() |
| |
| |
| __expand_var_regexp__ = re.compile(r"\${[^{}]+}") |
| __expand_python_regexp__ = re.compile(r"\${@.+?}") |
| |
| def expand(s, d, varname = None): |
| """Variable expansion using the data store""" |
| return d.expand(s, varname) |
| |
| def expandKeys(alterdata, readdata = None): |
| if readdata == None: |
| readdata = alterdata |
| |
| todolist = {} |
| for key in alterdata: |
| if not '${' in key: |
| continue |
| |
| ekey = expand(key, readdata) |
| if key == ekey: |
| continue |
| todolist[key] = ekey |
| |
| # These two for loops are split for performance to maximise the |
| # usefulness of the expand cache |
| for key in sorted(todolist): |
| ekey = todolist[key] |
| newval = alterdata.getVar(ekey, False) |
| if newval is not None: |
| val = alterdata.getVar(key, False) |
| if val is not None: |
| bb.warn("Variable key %s (%s) replaces original key %s (%s)." % (key, val, ekey, newval)) |
| alterdata.renameVar(key, ekey) |
| |
| def inheritFromOS(d, savedenv, permitted): |
| """Inherit variables from the initial environment.""" |
| exportlist = bb.utils.preserved_envvars_exported() |
| for s in savedenv.keys(): |
| if s in permitted: |
| try: |
| d.setVar(s, savedenv.getVar(s, True), op = 'from env') |
| if s in exportlist: |
| d.setVarFlag(s, "export", True, op = 'auto env export') |
| except TypeError: |
| pass |
| |
| def emit_var(var, o=sys.__stdout__, d = init(), all=False): |
| """Emit a variable to be sourced by a shell.""" |
| func = d.getVarFlag(var, "func", False) |
| if d.getVarFlag(var, 'python', False) and func: |
| return False |
| |
| export = d.getVarFlag(var, "export", False) |
| unexport = d.getVarFlag(var, "unexport", False) |
| if not all and not export and not unexport and not func: |
| return False |
| |
| try: |
| if all: |
| oval = d.getVar(var, False) |
| val = d.getVar(var, True) |
| except (KeyboardInterrupt, bb.build.FuncFailed): |
| raise |
| except Exception as exc: |
| o.write('# expansion of %s threw %s: %s\n' % (var, exc.__class__.__name__, str(exc))) |
| return False |
| |
| if all: |
| d.varhistory.emit(var, oval, val, o, d) |
| |
| if (var.find("-") != -1 or var.find(".") != -1 or var.find('{') != -1 or var.find('}') != -1 or var.find('+') != -1) and not all: |
| return False |
| |
| varExpanded = d.expand(var) |
| |
| if unexport: |
| o.write('unset %s\n' % varExpanded) |
| return False |
| |
| if val is None: |
| return False |
| |
| val = str(val) |
| |
| if varExpanded.startswith("BASH_FUNC_"): |
| varExpanded = varExpanded[10:-2] |
| val = val[3:] # Strip off "() " |
| o.write("%s() %s\n" % (varExpanded, val)) |
| o.write("export -f %s\n" % (varExpanded)) |
| return True |
| |
| if func: |
| # NOTE: should probably check for unbalanced {} within the var |
| val = val.rstrip('\n') |
| o.write("%s() {\n%s\n}\n" % (varExpanded, val)) |
| return 1 |
| |
| if export: |
| o.write('export ') |
| |
| # if we're going to output this within doublequotes, |
| # to a shell, we need to escape the quotes in the var |
| alter = re.sub('"', '\\"', val) |
| alter = re.sub('\n', ' \\\n', alter) |
| alter = re.sub('\\$', '\\\\$', alter) |
| o.write('%s="%s"\n' % (varExpanded, alter)) |
| return False |
| |
| def emit_env(o=sys.__stdout__, d = init(), all=False): |
| """Emits all items in the data store in a format such that it can be sourced by a shell.""" |
| |
| isfunc = lambda key: bool(d.getVarFlag(key, "func", False)) |
| keys = sorted((key for key in d.keys() if not key.startswith("__")), key=isfunc) |
| grouped = groupby(keys, isfunc) |
| for isfunc, keys in grouped: |
| for key in keys: |
| emit_var(key, o, d, all and not isfunc) and o.write('\n') |
| |
| def exported_keys(d): |
| return (key for key in d.keys() if not key.startswith('__') and |
| d.getVarFlag(key, 'export', False) and |
| not d.getVarFlag(key, 'unexport', False)) |
| |
| def exported_vars(d): |
| k = list(exported_keys(d)) |
| for key in k: |
| try: |
| value = d.getVar(key, True) |
| except Exception as err: |
| bb.warn("%s: Unable to export ${%s}: %s" % (d.getVar("FILE", True), key, err)) |
| continue |
| |
| if value is not None: |
| yield key, str(value) |
| |
| def emit_func(func, o=sys.__stdout__, d = init()): |
| """Emits all items in the data store in a format such that it can be sourced by a shell.""" |
| |
| keys = (key for key in d.keys() if not key.startswith("__") and not d.getVarFlag(key, "func", False)) |
| for key in keys: |
| emit_var(key, o, d, False) |
| |
| o.write('\n') |
| emit_var(func, o, d, False) and o.write('\n') |
| newdeps = bb.codeparser.ShellParser(func, logger).parse_shell(d.getVar(func, True)) |
| newdeps |= set((d.getVarFlag(func, "vardeps", True) or "").split()) |
| seen = set() |
| while newdeps: |
| deps = newdeps |
| seen |= deps |
| newdeps = set() |
| for dep in deps: |
| if d.getVarFlag(dep, "func", False) and not d.getVarFlag(dep, "python", False): |
| emit_var(dep, o, d, False) and o.write('\n') |
| newdeps |= bb.codeparser.ShellParser(dep, logger).parse_shell(d.getVar(dep, True)) |
| newdeps |= set((d.getVarFlag(dep, "vardeps", True) or "").split()) |
| newdeps -= seen |
| |
| _functionfmt = """ |
| def {function}(d): |
| {body}""" |
| |
| def emit_func_python(func, o=sys.__stdout__, d = init()): |
| """Emits all items in the data store in a format such that it can be sourced by a shell.""" |
| |
| def write_func(func, o, call = False): |
| body = d.getVar(func, False) |
| if not body.startswith("def"): |
| body = _functionfmt.format(function=func, body=body) |
| |
| o.write(body.strip() + "\n\n") |
| if call: |
| o.write(func + "(d)" + "\n\n") |
| |
| write_func(func, o, True) |
| pp = bb.codeparser.PythonParser(func, logger) |
| pp.parse_python(d.getVar(func, False)) |
| newdeps = pp.execs |
| newdeps |= set((d.getVarFlag(func, "vardeps", True) or "").split()) |
| seen = set() |
| while newdeps: |
| deps = newdeps |
| seen |= deps |
| newdeps = set() |
| for dep in deps: |
| if d.getVarFlag(dep, "func", False) and d.getVarFlag(dep, "python", False): |
| write_func(dep, o) |
| pp = bb.codeparser.PythonParser(dep, logger) |
| pp.parse_python(d.getVar(dep, False)) |
| newdeps |= pp.execs |
| newdeps |= set((d.getVarFlag(dep, "vardeps", True) or "").split()) |
| newdeps -= seen |
| |
| def update_data(d): |
| """Performs final steps upon the datastore, including application of overrides""" |
| d.finalize(parent = True) |
| |
| def build_dependencies(key, keys, shelldeps, varflagsexcl, d): |
| deps = set() |
| try: |
| if key[-1] == ']': |
| vf = key[:-1].split('[') |
| value = d.getVarFlag(vf[0], vf[1], False) |
| parser = d.expandWithRefs(value, key) |
| deps |= parser.references |
| deps = deps | (keys & parser.execs) |
| return deps, value |
| varflags = d.getVarFlags(key, ["vardeps", "vardepvalue", "vardepsexclude", "exports", "postfuncs", "prefuncs", "lineno", "filename"]) or {} |
| vardeps = varflags.get("vardeps") |
| value = d.getVar(key, False) |
| |
| def handle_contains(value, contains, d): |
| newvalue = "" |
| for k in sorted(contains): |
| l = (d.getVar(k, True) or "").split() |
| for word in sorted(contains[k]): |
| if word in l: |
| newvalue += "\n%s{%s} = Set" % (k, word) |
| else: |
| newvalue += "\n%s{%s} = Unset" % (k, word) |
| if not newvalue: |
| return value |
| if not value: |
| return newvalue |
| return value + newvalue |
| |
| if "vardepvalue" in varflags: |
| value = varflags.get("vardepvalue") |
| elif varflags.get("func"): |
| if varflags.get("python"): |
| parser = bb.codeparser.PythonParser(key, logger) |
| if value and "\t" in value: |
| logger.warning("Variable %s contains tabs, please remove these (%s)" % (key, d.getVar("FILE", True))) |
| parser.parse_python(value, filename=varflags.get("filename"), lineno=varflags.get("lineno")) |
| deps = deps | parser.references |
| deps = deps | (keys & parser.execs) |
| value = handle_contains(value, parser.contains, d) |
| else: |
| parsedvar = d.expandWithRefs(value, key) |
| parser = bb.codeparser.ShellParser(key, logger) |
| parser.parse_shell(parsedvar.value) |
| deps = deps | shelldeps |
| deps = deps | parsedvar.references |
| deps = deps | (keys & parser.execs) | (keys & parsedvar.execs) |
| value = handle_contains(value, parsedvar.contains, d) |
| if vardeps is None: |
| parser.log.flush() |
| if "prefuncs" in varflags: |
| deps = deps | set(varflags["prefuncs"].split()) |
| if "postfuncs" in varflags: |
| deps = deps | set(varflags["postfuncs"].split()) |
| if "exports" in varflags: |
| deps = deps | set(varflags["exports"].split()) |
| else: |
| parser = d.expandWithRefs(value, key) |
| deps |= parser.references |
| deps = deps | (keys & parser.execs) |
| value = handle_contains(value, parser.contains, d) |
| |
| if "vardepvalueexclude" in varflags: |
| exclude = varflags.get("vardepvalueexclude") |
| for excl in exclude.split('|'): |
| if excl: |
| value = value.replace(excl, '') |
| |
| # Add varflags, assuming an exclusion list is set |
| if varflagsexcl: |
| varfdeps = [] |
| for f in varflags: |
| if f not in varflagsexcl: |
| varfdeps.append('%s[%s]' % (key, f)) |
| if varfdeps: |
| deps |= set(varfdeps) |
| |
| deps |= set((vardeps or "").split()) |
| deps -= set(varflags.get("vardepsexclude", "").split()) |
| except Exception as e: |
| bb.warn("Exception during build_dependencies for %s" % key) |
| raise |
| return deps, value |
| #bb.note("Variable %s references %s and calls %s" % (key, str(deps), str(execs))) |
| #d.setVarFlag(key, "vardeps", deps) |
| |
| def generate_dependencies(d): |
| |
| keys = set(key for key in d if not key.startswith("__")) |
| shelldeps = set(key for key in d.getVar("__exportlist", False) if d.getVarFlag(key, "export", False) and not d.getVarFlag(key, "unexport", False)) |
| varflagsexcl = d.getVar('BB_SIGNATURE_EXCLUDE_FLAGS', True) |
| |
| deps = {} |
| values = {} |
| |
| tasklist = d.getVar('__BBTASKS', False) or [] |
| for task in tasklist: |
| deps[task], values[task] = build_dependencies(task, keys, shelldeps, varflagsexcl, d) |
| newdeps = deps[task] |
| seen = set() |
| while newdeps: |
| nextdeps = newdeps |
| seen |= nextdeps |
| newdeps = set() |
| for dep in nextdeps: |
| if dep not in deps: |
| deps[dep], values[dep] = build_dependencies(dep, keys, shelldeps, varflagsexcl, d) |
| newdeps |= deps[dep] |
| newdeps -= seen |
| #print "For %s: %s" % (task, str(deps[task])) |
| return tasklist, deps, values |
| |
| def inherits_class(klass, d): |
| val = d.getVar('__inherit_cache', False) or [] |
| needle = os.path.join('classes', '%s.bbclass' % klass) |
| for v in val: |
| if v.endswith(needle): |
| return True |
| return False |