1""" 2BitBake 'Data' implementations 3 4Functions for interacting with the data structure used by the 5BitBake build tools. 6 7expandKeys and datastore iteration are the most expensive 8operations. Updating overrides is now "on the fly" but still based 9on the idea of the cookie monster introduced by zecke: 10"At night the cookie monster came by and 11suggested 'give me cookies on setting the variables and 12things will work out'. Taking this suggestion into account 13applying the skills from the not yet passed 'Entwurf und 14Analyse von Algorithmen' lecture and the cookie 15monster seems to be right. We will track setVar more carefully 16to have faster datastore operations." 17 18This is a trade-off between speed and memory again but 19the speed is more critical here. 20""" 21 22# Copyright (C) 2003, 2004 Chris Larson 23# Copyright (C) 2005 Holger Hans Peter Freyther 24# 25# SPDX-License-Identifier: GPL-2.0-only 26# 27# Based on functions from the base bb module, Copyright 2003 Holger Schurig 28 29import sys, os, re 30import hashlib 31from itertools import groupby 32 33from bb import data_smart 34from bb import codeparser 35import bb 36 37logger = data_smart.logger 38_dict_type = data_smart.DataSmart 39 40def init(): 41 """Return a new object representing the Bitbake data""" 42 return _dict_type() 43 44def init_db(parent = None): 45 """Return a new object representing the Bitbake data, 46 optionally based on an existing object""" 47 if parent is not None: 48 return parent.createCopy() 49 else: 50 return _dict_type() 51 52def createCopy(source): 53 """Link the source set to the destination 54 If one does not find the value in the destination set, 55 search will go on to the source set to get the value. 56 Value from source are copy-on-write. i.e. any try to 57 modify one of them will end up putting the modified value 58 in the destination set. 59 """ 60 return source.createCopy() 61 62def initVar(var, d): 63 """Non-destructive var init for data structure""" 64 d.initVar(var) 65 66def keys(d): 67 """Return a list of keys in d""" 68 return d.keys() 69 70def expand(s, d, varname = None): 71 """Variable expansion using the data store""" 72 return d.expand(s, varname) 73 74def expandKeys(alterdata, readdata = None): 75 if readdata is None: 76 readdata = alterdata 77 78 todolist = {} 79 for key in alterdata: 80 if not '${' in key: 81 continue 82 83 ekey = expand(key, readdata) 84 if key == ekey: 85 continue 86 todolist[key] = ekey 87 88 # These two for loops are split for performance to maximise the 89 # usefulness of the expand cache 90 for key in sorted(todolist): 91 ekey = todolist[key] 92 newval = alterdata.getVar(ekey, False) 93 if newval is not None: 94 val = alterdata.getVar(key, False) 95 if val is not None: 96 bb.warn("Variable key %s (%s) replaces original key %s (%s)." % (key, val, ekey, newval)) 97 alterdata.renameVar(key, ekey) 98 99def inheritFromOS(d, savedenv, permitted): 100 """Inherit variables from the initial environment.""" 101 exportlist = bb.utils.preserved_envvars_exported() 102 for s in savedenv.keys(): 103 if s in permitted: 104 try: 105 d.setVar(s, savedenv.getVar(s), op = 'from env') 106 if s in exportlist: 107 d.setVarFlag(s, "export", True, op = 'auto env export') 108 except TypeError: 109 pass 110 111def emit_var(var, o=sys.__stdout__, d = init(), all=False): 112 """Emit a variable to be sourced by a shell.""" 113 func = d.getVarFlag(var, "func", False) 114 if d.getVarFlag(var, 'python', False) and func: 115 return False 116 117 export = bb.utils.to_boolean(d.getVarFlag(var, "export")) 118 unexport = bb.utils.to_boolean(d.getVarFlag(var, "unexport")) 119 if not all and not export and not unexport and not func: 120 return False 121 122 try: 123 if all: 124 oval = d.getVar(var, False) 125 val = d.getVar(var) 126 except (KeyboardInterrupt): 127 raise 128 except Exception as exc: 129 o.write('# expansion of %s threw %s: %s\n' % (var, exc.__class__.__name__, str(exc))) 130 return False 131 132 if all: 133 d.varhistory.emit(var, oval, val, o, d) 134 135 if (var.find("-") != -1 or var.find(".") != -1 or var.find('{') != -1 or var.find('}') != -1 or var.find('+') != -1) and not all: 136 return False 137 138 varExpanded = d.expand(var) 139 140 if unexport: 141 o.write('unset %s\n' % varExpanded) 142 return False 143 144 if val is None: 145 return False 146 147 val = str(val) 148 149 if varExpanded.startswith("BASH_FUNC_"): 150 varExpanded = varExpanded[10:-2] 151 val = val[3:] # Strip off "() " 152 o.write("%s() %s\n" % (varExpanded, val)) 153 o.write("export -f %s\n" % (varExpanded)) 154 return True 155 156 if func: 157 # Write a comment indicating where the shell function came from (line number and filename) to make it easier 158 # for the user to diagnose task failures. This comment is also used by build.py to determine the metadata 159 # location of shell functions. 160 o.write("# line: {0}, file: {1}\n".format( 161 d.getVarFlag(var, "lineno", False), 162 d.getVarFlag(var, "filename", False))) 163 # NOTE: should probably check for unbalanced {} within the var 164 val = val.rstrip('\n') 165 o.write("%s() {\n%s\n}\n" % (varExpanded, val)) 166 return 1 167 168 if export: 169 o.write('export ') 170 171 # if we're going to output this within doublequotes, 172 # to a shell, we need to escape the quotes in the var 173 alter = re.sub('"', '\\"', val) 174 alter = re.sub('\n', ' \\\n', alter) 175 alter = re.sub('\\$', '\\\\$', alter) 176 o.write('%s="%s"\n' % (varExpanded, alter)) 177 return False 178 179def emit_env(o=sys.__stdout__, d = init(), all=False): 180 """Emits all items in the data store in a format such that it can be sourced by a shell.""" 181 182 isfunc = lambda key: bool(d.getVarFlag(key, "func", False)) 183 keys = sorted((key for key in d.keys() if not key.startswith("__")), key=isfunc) 184 grouped = groupby(keys, isfunc) 185 for isfunc, keys in grouped: 186 for key in sorted(keys): 187 emit_var(key, o, d, all and not isfunc) and o.write('\n') 188 189def exported_keys(d): 190 return (key for key in d.keys() if not key.startswith('__') and 191 bb.utils.to_boolean(d.getVarFlag(key, 'export')) and 192 not bb.utils.to_boolean(d.getVarFlag(key, 'unexport'))) 193 194def exported_vars(d): 195 k = list(exported_keys(d)) 196 for key in k: 197 try: 198 value = d.getVar(key) 199 except Exception as err: 200 bb.warn("%s: Unable to export ${%s}: %s" % (d.getVar("FILE"), key, err)) 201 continue 202 203 if value is not None: 204 yield key, str(value) 205 206def emit_func(func, o=sys.__stdout__, d = init()): 207 """Emits all items in the data store in a format such that it can be sourced by a shell.""" 208 209 keys = (key for key in d.keys() if not key.startswith("__") and not d.getVarFlag(key, "func", False)) 210 for key in sorted(keys): 211 emit_var(key, o, d, False) 212 213 o.write('\n') 214 emit_var(func, o, d, False) and o.write('\n') 215 newdeps = bb.codeparser.ShellParser(func, logger).parse_shell(d.getVar(func)) 216 newdeps |= set((d.getVarFlag(func, "vardeps") or "").split()) 217 seen = set() 218 while newdeps: 219 deps = newdeps 220 seen |= deps 221 newdeps = set() 222 for dep in sorted(deps): 223 if d.getVarFlag(dep, "func", False) and not d.getVarFlag(dep, "python", False): 224 emit_var(dep, o, d, False) and o.write('\n') 225 newdeps |= bb.codeparser.ShellParser(dep, logger).parse_shell(d.getVar(dep)) 226 newdeps |= set((d.getVarFlag(dep, "vardeps") or "").split()) 227 newdeps -= seen 228 229_functionfmt = """ 230def {function}(d): 231{body}""" 232 233def emit_func_python(func, o=sys.__stdout__, d = init()): 234 """Emits all items in the data store in a format such that it can be sourced by a shell.""" 235 236 def write_func(func, o, call = False): 237 body = d.getVar(func, False) 238 if not body.startswith("def"): 239 body = _functionfmt.format(function=func, body=body) 240 241 o.write(body.strip() + "\n\n") 242 if call: 243 o.write(func + "(d)" + "\n\n") 244 245 write_func(func, o, True) 246 pp = bb.codeparser.PythonParser(func, logger) 247 pp.parse_python(d.getVar(func, False)) 248 newdeps = pp.execs 249 newdeps |= set((d.getVarFlag(func, "vardeps") or "").split()) 250 seen = set() 251 while newdeps: 252 deps = newdeps 253 seen |= deps 254 newdeps = set() 255 for dep in deps: 256 if d.getVarFlag(dep, "func", False) and d.getVarFlag(dep, "python", False): 257 write_func(dep, o) 258 pp = bb.codeparser.PythonParser(dep, logger) 259 pp.parse_python(d.getVar(dep, False)) 260 newdeps |= pp.execs 261 newdeps |= set((d.getVarFlag(dep, "vardeps") or "").split()) 262 newdeps -= seen 263 264def build_dependencies(key, keys, mod_funcs, shelldeps, varflagsexcl, ignored_vars, d, codeparsedata): 265 def handle_contains(value, contains, exclusions, d): 266 newvalue = [] 267 if value: 268 newvalue.append(str(value)) 269 for k in sorted(contains): 270 if k in exclusions or k in ignored_vars: 271 continue 272 l = (d.getVar(k) or "").split() 273 for item in sorted(contains[k]): 274 for word in item.split(): 275 if not word in l: 276 newvalue.append("\n%s{%s} = Unset" % (k, item)) 277 break 278 else: 279 newvalue.append("\n%s{%s} = Set" % (k, item)) 280 return "".join(newvalue) 281 282 def handle_remove(value, deps, removes, d): 283 for r in sorted(removes): 284 r2 = d.expandWithRefs(r, None) 285 value += "\n_remove of %s" % r 286 deps |= r2.references 287 deps = deps | (keys & r2.execs) 288 value = handle_contains(value, r2.contains, exclusions, d) 289 return value 290 291 deps = set() 292 try: 293 if key in mod_funcs: 294 exclusions = set() 295 moddep = bb.codeparser.modulecode_deps[key] 296 value = handle_contains("", moddep[3], exclusions, d) 297 return frozenset((moddep[0] | keys & moddep[1]) - ignored_vars), value 298 299 if key[-1] == ']': 300 vf = key[:-1].split('[') 301 if vf[1] == "vardepvalueexclude": 302 return deps, "" 303 value, parser = d.getVarFlag(vf[0], vf[1], False, retparser=True) 304 deps |= parser.references 305 deps = deps | (keys & parser.execs) 306 deps -= ignored_vars 307 return frozenset(deps), value 308 varflags = d.getVarFlags(key, ["vardeps", "vardepvalue", "vardepsexclude", "exports", "postfuncs", "prefuncs", "lineno", "filename"]) or {} 309 vardeps = varflags.get("vardeps") 310 exclusions = varflags.get("vardepsexclude", "").split() 311 312 if "vardepvalue" in varflags: 313 value = varflags.get("vardepvalue") 314 elif varflags.get("func"): 315 if varflags.get("python"): 316 value = codeparsedata.getVarFlag(key, "_content", False) 317 parser = bb.codeparser.PythonParser(key, logger) 318 parser.parse_python(value, filename=varflags.get("filename"), lineno=varflags.get("lineno")) 319 deps = deps | parser.references 320 deps = deps | (keys & parser.execs) 321 value = handle_contains(value, parser.contains, exclusions, d) 322 else: 323 value, parsedvar = codeparsedata.getVarFlag(key, "_content", False, retparser=True) 324 parser = bb.codeparser.ShellParser(key, logger) 325 parser.parse_shell(parsedvar.value) 326 deps = deps | shelldeps 327 deps = deps | parsedvar.references 328 deps = deps | (keys & parser.execs) | (keys & parsedvar.execs) 329 value = handle_contains(value, parsedvar.contains, exclusions, d) 330 if hasattr(parsedvar, "removes"): 331 value = handle_remove(value, deps, parsedvar.removes, d) 332 if vardeps is None: 333 parser.log.flush() 334 if "prefuncs" in varflags: 335 deps = deps | set(varflags["prefuncs"].split()) 336 if "postfuncs" in varflags: 337 deps = deps | set(varflags["postfuncs"].split()) 338 if "exports" in varflags: 339 deps = deps | set(varflags["exports"].split()) 340 else: 341 value, parser = d.getVarFlag(key, "_content", False, retparser=True) 342 deps |= parser.references 343 deps = deps | (keys & parser.execs) 344 value = handle_contains(value, parser.contains, exclusions, d) 345 if hasattr(parser, "removes"): 346 value = handle_remove(value, deps, parser.removes, d) 347 348 if "vardepvalueexclude" in varflags: 349 exclude = varflags.get("vardepvalueexclude") 350 for excl in exclude.split('|'): 351 if excl: 352 value = value.replace(excl, '') 353 354 # Add varflags, assuming an exclusion list is set 355 if varflagsexcl: 356 varfdeps = [] 357 for f in varflags: 358 if f not in varflagsexcl: 359 varfdeps.append('%s[%s]' % (key, f)) 360 if varfdeps: 361 deps |= set(varfdeps) 362 363 deps |= set((vardeps or "").split()) 364 deps -= set(exclusions) 365 deps -= ignored_vars 366 except bb.parse.SkipRecipe: 367 raise 368 except Exception as e: 369 bb.warn("Exception during build_dependencies for %s" % key) 370 raise 371 return frozenset(deps), value 372 #bb.note("Variable %s references %s and calls %s" % (key, str(deps), str(execs))) 373 #d.setVarFlag(key, "vardeps", deps) 374 375def generate_dependencies(d, ignored_vars): 376 377 mod_funcs = set(bb.codeparser.modulecode_deps.keys()) 378 keys = set(key for key in d if not key.startswith("__")) | mod_funcs 379 shelldeps = set(key for key in d.getVar("__exportlist", False) if bb.utils.to_boolean(d.getVarFlag(key, "export")) and not bb.utils.to_boolean(d.getVarFlag(key, "unexport"))) 380 varflagsexcl = d.getVar('BB_SIGNATURE_EXCLUDE_FLAGS') 381 382 codeparserd = d.createCopy() 383 for forced in (d.getVar('BB_HASH_CODEPARSER_VALS') or "").split(): 384 key, value = forced.split("=", 1) 385 codeparserd.setVar(key, value) 386 387 deps = {} 388 values = {} 389 390 tasklist = d.getVar('__BBTASKS', False) or [] 391 for task in tasklist: 392 deps[task], values[task] = build_dependencies(task, keys, mod_funcs, shelldeps, varflagsexcl, ignored_vars, d, codeparserd) 393 newdeps = deps[task] 394 seen = set() 395 while newdeps: 396 nextdeps = newdeps 397 seen |= nextdeps 398 newdeps = set() 399 for dep in nextdeps: 400 if dep not in deps: 401 deps[dep], values[dep] = build_dependencies(dep, keys, mod_funcs, shelldeps, varflagsexcl, ignored_vars, d, codeparserd) 402 newdeps |= deps[dep] 403 newdeps -= seen 404 #print "For %s: %s" % (task, str(deps[task])) 405 return tasklist, deps, values 406 407def generate_dependency_hash(tasklist, gendeps, lookupcache, ignored_vars, fn): 408 taskdeps = {} 409 basehash = {} 410 411 for task in tasklist: 412 data = lookupcache[task] 413 414 if data is None: 415 bb.error("Task %s from %s seems to be empty?!" % (task, fn)) 416 data = [] 417 else: 418 data = [data] 419 420 newdeps = gendeps[task] 421 seen = set() 422 while newdeps: 423 nextdeps = newdeps 424 seen |= nextdeps 425 newdeps = set() 426 for dep in nextdeps: 427 newdeps |= gendeps[dep] 428 newdeps -= seen 429 430 alldeps = sorted(seen) 431 for dep in alldeps: 432 data.append(dep) 433 var = lookupcache[dep] 434 if var is not None: 435 data.append(str(var)) 436 k = fn + ":" + task 437 basehash[k] = hashlib.sha256("".join(data).encode("utf-8")).hexdigest() 438 taskdeps[task] = frozenset(seen) 439 440 return taskdeps, basehash 441 442def inherits_class(klass, d): 443 val = d.getVar('__inherit_cache', False) or [] 444 needle = '/%s.bbclass' % klass 445 for v in val: 446 if v.endswith(needle): 447 return True 448 return False 449