blob: d66d98cc8bfdd08167745eb91a37f96da0b8079d [file] [log] [blame]
rjw1f884582022-01-06 17:20:42 +08001# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3"""
4BitBake 'Data' implementations
5
6Functions for interacting with the data structure used by the
7BitBake build tools.
8
9The expandKeys and update_data are the most expensive
10operations. 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 update_data and expandKeys 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# This program is free software; you can redistribute it and/or modify
26# it under the terms of the GNU General Public License version 2 as
27# published by the Free Software Foundation.
28#
29# This program is distributed in the hope that it will be useful,
30# but WITHOUT ANY WARRANTY; without even the implied warranty of
31# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32# GNU General Public License for more details.
33#
34# You should have received a copy of the GNU General Public License along
35# with this program; if not, write to the Free Software Foundation, Inc.,
36# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
37#
38# Based on functions from the base bb module, Copyright 2003 Holger Schurig
39
40import sys, os, re
41import hashlib
42if sys.argv[0][-5:] == "pydoc":
43 path = os.path.dirname(os.path.dirname(sys.argv[1]))
44else:
45 path = os.path.dirname(os.path.dirname(sys.argv[0]))
46sys.path.insert(0, path)
47from itertools import groupby
48
49from bb import data_smart
50from bb import codeparser
51import bb
52
53logger = data_smart.logger
54_dict_type = data_smart.DataSmart
55
56def init():
57 """Return a new object representing the Bitbake data"""
58 return _dict_type()
59
60def init_db(parent = None):
61 """Return a new object representing the Bitbake data,
62 optionally based on an existing object"""
63 if parent is not None:
64 return parent.createCopy()
65 else:
66 return _dict_type()
67
68def createCopy(source):
69 """Link the source set to the destination
70 If one does not find the value in the destination set,
71 search will go on to the source set to get the value.
72 Value from source are copy-on-write. i.e. any try to
73 modify one of them will end up putting the modified value
74 in the destination set.
75 """
76 return source.createCopy()
77
78def initVar(var, d):
79 """Non-destructive var init for data structure"""
80 d.initVar(var)
81
82def keys(d):
83 """Return a list of keys in d"""
84 return d.keys()
85
86
87__expand_var_regexp__ = re.compile(r"\${[^{}]+}")
88__expand_python_regexp__ = re.compile(r"\${@.+?}")
89
90def expand(s, d, varname = None):
91 """Variable expansion using the data store"""
92 return d.expand(s, varname)
93
94def expandKeys(alterdata, readdata = None):
95 if readdata == None:
96 readdata = alterdata
97
98 todolist = {}
99 for key in alterdata:
100 if not '${' in key:
101 continue
102
103 ekey = expand(key, readdata)
104 if key == ekey:
105 continue
106 todolist[key] = ekey
107
108 # These two for loops are split for performance to maximise the
109 # usefulness of the expand cache
110 for key in sorted(todolist):
111 ekey = todolist[key]
112 newval = alterdata.getVar(ekey, False)
113 if newval is not None:
114 val = alterdata.getVar(key, False)
115 if val is not None:
116 bb.warn("Variable key %s (%s) replaces original key %s (%s)." % (key, val, ekey, newval))
117 alterdata.renameVar(key, ekey)
118
119def inheritFromOS(d, savedenv, permitted):
120 """Inherit variables from the initial environment."""
121 exportlist = bb.utils.preserved_envvars_exported()
122 for s in savedenv.keys():
123 if s in permitted:
124 try:
125 d.setVar(s, savedenv.getVar(s), op = 'from env')
126 if s in exportlist:
127 d.setVarFlag(s, "export", True, op = 'auto env export')
128 except TypeError:
129 pass
130
131def emit_var(var, o=sys.__stdout__, d = init(), all=False):
132 """Emit a variable to be sourced by a shell."""
133 func = d.getVarFlag(var, "func", False)
134 if d.getVarFlag(var, 'python', False) and func:
135 return False
136
137 export = d.getVarFlag(var, "export", False)
138 unexport = d.getVarFlag(var, "unexport", False)
139 if not all and not export and not unexport and not func:
140 return False
141
142 try:
143 if all:
144 oval = d.getVar(var, False)
145 val = d.getVar(var)
146 except (KeyboardInterrupt, bb.build.FuncFailed):
147 raise
148 except Exception as exc:
149 o.write('# expansion of %s threw %s: %s\n' % (var, exc.__class__.__name__, str(exc)))
150 return False
151
152 if all:
153 d.varhistory.emit(var, oval, val, o, d)
154
155 if (var.find("-") != -1 or var.find(".") != -1 or var.find('{') != -1 or var.find('}') != -1 or var.find('+') != -1) and not all:
156 return False
157
158 varExpanded = d.expand(var)
159
160 if unexport:
161 o.write('unset %s\n' % varExpanded)
162 return False
163
164 if val is None:
165 return False
166
167 val = str(val)
168
169 if varExpanded.startswith("BASH_FUNC_"):
170 varExpanded = varExpanded[10:-2]
171 val = val[3:] # Strip off "() "
172 o.write("%s() %s\n" % (varExpanded, val))
173 o.write("export -f %s\n" % (varExpanded))
174 return True
175
176 if func:
177 # NOTE: should probably check for unbalanced {} within the var
178 val = val.rstrip('\n')
179 o.write("%s() {\n%s\n}\n" % (varExpanded, val))
180 return 1
181
182 if export:
183 o.write('export ')
184
185 # if we're going to output this within doublequotes,
186 # to a shell, we need to escape the quotes in the var
187 alter = re.sub('"', '\\"', val)
188 alter = re.sub('\n', ' \\\n', alter)
189 alter = re.sub('\\$', '\\\\$', alter)
190 o.write('%s="%s"\n' % (varExpanded, alter))
191 return False
192
193def emit_env(o=sys.__stdout__, d = init(), all=False):
194 """Emits all items in the data store in a format such that it can be sourced by a shell."""
195
196 isfunc = lambda key: bool(d.getVarFlag(key, "func", False))
197 keys = sorted((key for key in d.keys() if not key.startswith("__")), key=isfunc)
198 grouped = groupby(keys, isfunc)
199 for isfunc, keys in grouped:
200 for key in sorted(keys):
201 emit_var(key, o, d, all and not isfunc) and o.write('\n')
202
203def exported_keys(d):
204 return (key for key in d.keys() if not key.startswith('__') and
205 d.getVarFlag(key, 'export', False) and
206 not d.getVarFlag(key, 'unexport', False))
207
208def exported_vars(d):
209 k = list(exported_keys(d))
210 for key in k:
211 try:
212 value = d.getVar(key)
213 except Exception as err:
214 bb.warn("%s: Unable to export ${%s}: %s" % (d.getVar("FILE"), key, err))
215 continue
216
217 if value is not None:
218 yield key, str(value)
219
220def emit_func(func, o=sys.__stdout__, d = init()):
221 """Emits all items in the data store in a format such that it can be sourced by a shell."""
222
223 keys = (key for key in d.keys() if not key.startswith("__") and not d.getVarFlag(key, "func", False))
224 for key in sorted(keys):
225 emit_var(key, o, d, False)
226
227 o.write('\n')
228 emit_var(func, o, d, False) and o.write('\n')
229 newdeps = bb.codeparser.ShellParser(func, logger).parse_shell(d.getVar(func))
230 newdeps |= set((d.getVarFlag(func, "vardeps") or "").split())
231 seen = set()
232 while newdeps:
233 deps = newdeps
234 seen |= deps
235 newdeps = set()
236 for dep in deps:
237 if d.getVarFlag(dep, "func", False) and not d.getVarFlag(dep, "python", False):
238 emit_var(dep, o, d, False) and o.write('\n')
239 newdeps |= bb.codeparser.ShellParser(dep, logger).parse_shell(d.getVar(dep))
240 newdeps |= set((d.getVarFlag(dep, "vardeps") or "").split())
241 newdeps -= seen
242
243_functionfmt = """
244def {function}(d):
245{body}"""
246
247def emit_func_python(func, o=sys.__stdout__, d = init()):
248 """Emits all items in the data store in a format such that it can be sourced by a shell."""
249
250 def write_func(func, o, call = False):
251 body = d.getVar(func, False)
252 if not body.startswith("def"):
253 body = _functionfmt.format(function=func, body=body)
254
255 o.write(body.strip() + "\n\n")
256 if call:
257 o.write(func + "(d)" + "\n\n")
258
259 write_func(func, o, True)
260 pp = bb.codeparser.PythonParser(func, logger)
261 pp.parse_python(d.getVar(func, False))
262 newdeps = pp.execs
263 newdeps |= set((d.getVarFlag(func, "vardeps") or "").split())
264 seen = set()
265 while newdeps:
266 deps = newdeps
267 seen |= deps
268 newdeps = set()
269 for dep in deps:
270 if d.getVarFlag(dep, "func", False) and d.getVarFlag(dep, "python", False):
271 write_func(dep, o)
272 pp = bb.codeparser.PythonParser(dep, logger)
273 pp.parse_python(d.getVar(dep, False))
274 newdeps |= pp.execs
275 newdeps |= set((d.getVarFlag(dep, "vardeps") or "").split())
276 newdeps -= seen
277
278def update_data(d):
279 """Performs final steps upon the datastore, including application of overrides"""
280 d.finalize(parent = True)
281
282def build_dependencies(key, keys, shelldeps, varflagsexcl, d):
283 deps = set()
284 try:
285 if key[-1] == ']':
286 vf = key[:-1].split('[')
287 value, parser = d.getVarFlag(vf[0], vf[1], False, retparser=True)
288 deps |= parser.references
289 deps = deps | (keys & parser.execs)
290 return deps, value
291 varflags = d.getVarFlags(key, ["vardeps", "vardepvalue", "vardepsexclude", "exports", "postfuncs", "prefuncs", "lineno", "filename"]) or {}
292 vardeps = varflags.get("vardeps")
293
294 def handle_contains(value, contains, d):
295 newvalue = ""
296 for k in sorted(contains):
297 l = (d.getVar(k) or "").split()
298 for item in sorted(contains[k]):
299 for word in item.split():
300 if not word in l:
301 newvalue += "\n%s{%s} = Unset" % (k, item)
302 break
303 else:
304 newvalue += "\n%s{%s} = Set" % (k, item)
305 if not newvalue:
306 return value
307 if not value:
308 return newvalue
309 return value + newvalue
310
311 def handle_remove(value, deps, removes, d):
312 for r in sorted(removes):
313 r2 = d.expandWithRefs(r, None)
314 value += "\n_remove of %s" % r
315 deps |= r2.references
316 deps = deps | (keys & r2.execs)
317 return value
318
319 if "vardepvalue" in varflags:
320 value = varflags.get("vardepvalue")
321 elif varflags.get("func"):
322 if varflags.get("python"):
323 value = d.getVarFlag(key, "_content", False)
324 parser = bb.codeparser.PythonParser(key, logger)
325 if value and "\t" in value:
326 logger.warning("Variable %s contains tabs, please remove these (%s)" % (key, d.getVar("FILE")))
327 parser.parse_python(value, filename=varflags.get("filename"), lineno=varflags.get("lineno"))
328 deps = deps | parser.references
329 deps = deps | (keys & parser.execs)
330 value = handle_contains(value, parser.contains, d)
331 else:
332 value, parsedvar = d.getVarFlag(key, "_content", False, retparser=True)
333 parser = bb.codeparser.ShellParser(key, logger)
334 parser.parse_shell(parsedvar.value)
335 deps = deps | shelldeps
336 deps = deps | parsedvar.references
337 deps = deps | (keys & parser.execs) | (keys & parsedvar.execs)
338 value = handle_contains(value, parsedvar.contains, d)
339 if hasattr(parsedvar, "removes"):
340 value = handle_remove(value, deps, parsedvar.removes, d)
341 if vardeps is None:
342 parser.log.flush()
343 if "prefuncs" in varflags:
344 deps = deps | set(varflags["prefuncs"].split())
345 if "postfuncs" in varflags:
346 deps = deps | set(varflags["postfuncs"].split())
347 if "exports" in varflags:
348 deps = deps | set(varflags["exports"].split())
349 else:
350 value, parser = d.getVarFlag(key, "_content", False, retparser=True)
351 deps |= parser.references
352 deps = deps | (keys & parser.execs)
353 value = handle_contains(value, parser.contains, d)
354 if hasattr(parser, "removes"):
355 value = handle_remove(value, deps, parser.removes, d)
356
357 if "vardepvalueexclude" in varflags:
358 exclude = varflags.get("vardepvalueexclude")
359 for excl in exclude.split('|'):
360 if excl:
361 value = value.replace(excl, '')
362
363 # Add varflags, assuming an exclusion list is set
364 if varflagsexcl:
365 varfdeps = []
366 for f in varflags:
367 if f not in varflagsexcl:
368 varfdeps.append('%s[%s]' % (key, f))
369 if varfdeps:
370 deps |= set(varfdeps)
371
372 deps |= set((vardeps or "").split())
373 deps -= set(varflags.get("vardepsexclude", "").split())
374 except bb.parse.SkipRecipe:
375 raise
376 except Exception as e:
377 bb.warn("Exception during build_dependencies for %s" % key)
378 raise
379 return deps, value
380 #bb.note("Variable %s references %s and calls %s" % (key, str(deps), str(execs)))
381 #d.setVarFlag(key, "vardeps", deps)
382
383def generate_dependencies(d):
384
385 keys = set(key for key in d if not key.startswith("__"))
386 shelldeps = set(key for key in d.getVar("__exportlist", False) if d.getVarFlag(key, "export", False) and not d.getVarFlag(key, "unexport", False))
387 varflagsexcl = d.getVar('BB_SIGNATURE_EXCLUDE_FLAGS')
388
389 deps = {}
390 values = {}
391
392 tasklist = d.getVar('__BBTASKS', False) or []
393 for task in tasklist:
394 deps[task], values[task] = build_dependencies(task, keys, shelldeps, varflagsexcl, d)
395 newdeps = deps[task]
396 seen = set()
397 while newdeps:
398 nextdeps = newdeps
399 seen |= nextdeps
400 newdeps = set()
401 for dep in nextdeps:
402 if dep not in deps:
403 deps[dep], values[dep] = build_dependencies(dep, keys, shelldeps, varflagsexcl, d)
404 newdeps |= deps[dep]
405 newdeps -= seen
406 #print "For %s: %s" % (task, str(deps[task]))
407 return tasklist, deps, values
408
409def generate_dependency_hash(tasklist, gendeps, lookupcache, whitelist, fn):
410 taskdeps = {}
411 basehash = {}
412
413 for task in tasklist:
414 data = lookupcache[task]
415
416 if data is None:
417 bb.error("Task %s from %s seems to be empty?!" % (task, fn))
418 data = ''
419
420 gendeps[task] -= whitelist
421 newdeps = gendeps[task]
422 seen = set()
423 while newdeps:
424 nextdeps = newdeps
425 seen |= nextdeps
426 newdeps = set()
427 for dep in nextdeps:
428 if dep in whitelist:
429 continue
430 gendeps[dep] -= whitelist
431 newdeps |= gendeps[dep]
432 newdeps -= seen
433
434 alldeps = sorted(seen)
435 for dep in alldeps:
436 data = data + dep
437 var = lookupcache[dep]
438 if var is not None:
439 data = data + str(var)
440 k = fn + "." + task
441 basehash[k] = hashlib.md5(data.encode("utf-8")).hexdigest()
442 taskdeps[task] = alldeps
443
444 return taskdeps, basehash
445
446def inherits_class(klass, d):
447 val = d.getVar('__inherit_cache', False) or []
448 needle = os.path.join('classes', '%s.bbclass' % klass)
449 for v in val:
450 if v.endswith(needle):
451 return True
452 return False