diff options
Diffstat (limited to 'bitbake/lib/bb/codeparser.py')
-rw-r--r-- | bitbake/lib/bb/codeparser.py | 195 |
1 files changed, 151 insertions, 44 deletions
diff --git a/bitbake/lib/bb/codeparser.py b/bitbake/lib/bb/codeparser.py index 25a7ac69d3..4f70cf7fe7 100644 --- a/bitbake/lib/bb/codeparser.py +++ b/bitbake/lib/bb/codeparser.py | |||
@@ -1,4 +1,6 @@ | |||
1 | # | 1 | # |
2 | # Copyright BitBake Contributors | ||
3 | # | ||
2 | # SPDX-License-Identifier: GPL-2.0-only | 4 | # SPDX-License-Identifier: GPL-2.0-only |
3 | # | 5 | # |
4 | 6 | ||
@@ -25,6 +27,7 @@ import ast | |||
25 | import sys | 27 | import sys |
26 | import codegen | 28 | import codegen |
27 | import logging | 29 | import logging |
30 | import inspect | ||
28 | import bb.pysh as pysh | 31 | import bb.pysh as pysh |
29 | import bb.utils, bb.data | 32 | import bb.utils, bb.data |
30 | import hashlib | 33 | import hashlib |
@@ -56,10 +59,56 @@ def check_indent(codestr): | |||
56 | 59 | ||
57 | return codestr | 60 | return codestr |
58 | 61 | ||
59 | # A custom getstate/setstate using tuples is actually worth 15% cachesize by | 62 | modulecode_deps = {} |
60 | # avoiding duplication of the attribute names! | ||
61 | 63 | ||
64 | def add_module_functions(fn, functions, namespace): | ||
65 | import os | ||
66 | fstat = os.stat(fn) | ||
67 | fixedhash = fn + ":" + str(fstat.st_size) + ":" + str(fstat.st_mtime) | ||
68 | for f in functions: | ||
69 | name = "%s.%s" % (namespace, f) | ||
70 | parser = PythonParser(name, logger) | ||
71 | try: | ||
72 | parser.parse_python(None, filename=fn, lineno=1, fixedhash=fixedhash+f, func=functions[f]) | ||
73 | #bb.warn("Cached %s" % f) | ||
74 | except KeyError: | ||
75 | try: | ||
76 | targetfn = inspect.getsourcefile(functions[f]) | ||
77 | except TypeError: | ||
78 | # Builtin | ||
79 | continue | ||
80 | if fn != targetfn: | ||
81 | # Skip references to other modules outside this file | ||
82 | #bb.warn("Skipping %s" % name) | ||
83 | continue | ||
84 | try: | ||
85 | lines, lineno = inspect.getsourcelines(functions[f]) | ||
86 | except TypeError: | ||
87 | # Builtin | ||
88 | continue | ||
89 | src = "".join(lines) | ||
90 | parser.parse_python(src, filename=fn, lineno=lineno, fixedhash=fixedhash+f, func=functions[f]) | ||
91 | #bb.warn("Not cached %s" % f) | ||
92 | execs = parser.execs.copy() | ||
93 | # Expand internal module exec references | ||
94 | for e in parser.execs: | ||
95 | if e in functions: | ||
96 | execs.remove(e) | ||
97 | execs.add(namespace + "." + e) | ||
98 | visitorcode = None | ||
99 | if hasattr(functions[f], 'visitorcode'): | ||
100 | visitorcode = getattr(functions[f], "visitorcode") | ||
101 | modulecode_deps[name] = [parser.references.copy(), execs, parser.var_execs.copy(), parser.contains.copy(), parser.extra, visitorcode] | ||
102 | #bb.warn("%s: %s\nRefs:%s Execs: %s %s %s" % (name, fn, parser.references, parser.execs, parser.var_execs, parser.contains)) | ||
103 | |||
104 | def update_module_dependencies(d): | ||
105 | for mod in modulecode_deps: | ||
106 | excludes = set((d.getVarFlag(mod, "vardepsexclude") or "").split()) | ||
107 | if excludes: | ||
108 | modulecode_deps[mod] = [modulecode_deps[mod][0] - excludes, modulecode_deps[mod][1] - excludes, modulecode_deps[mod][2] - excludes, modulecode_deps[mod][3], modulecode_deps[mod][4], modulecode_deps[mod][5]] | ||
62 | 109 | ||
110 | # A custom getstate/setstate using tuples is actually worth 15% cachesize by | ||
111 | # avoiding duplication of the attribute names! | ||
63 | class SetCache(object): | 112 | class SetCache(object): |
64 | def __init__(self): | 113 | def __init__(self): |
65 | self.setcache = {} | 114 | self.setcache = {} |
@@ -79,21 +128,22 @@ class SetCache(object): | |||
79 | codecache = SetCache() | 128 | codecache = SetCache() |
80 | 129 | ||
81 | class pythonCacheLine(object): | 130 | class pythonCacheLine(object): |
82 | def __init__(self, refs, execs, contains): | 131 | def __init__(self, refs, execs, contains, extra): |
83 | self.refs = codecache.internSet(refs) | 132 | self.refs = codecache.internSet(refs) |
84 | self.execs = codecache.internSet(execs) | 133 | self.execs = codecache.internSet(execs) |
85 | self.contains = {} | 134 | self.contains = {} |
86 | for c in contains: | 135 | for c in contains: |
87 | self.contains[c] = codecache.internSet(contains[c]) | 136 | self.contains[c] = codecache.internSet(contains[c]) |
137 | self.extra = extra | ||
88 | 138 | ||
89 | def __getstate__(self): | 139 | def __getstate__(self): |
90 | return (self.refs, self.execs, self.contains) | 140 | return (self.refs, self.execs, self.contains, self.extra) |
91 | 141 | ||
92 | def __setstate__(self, state): | 142 | def __setstate__(self, state): |
93 | (refs, execs, contains) = state | 143 | (refs, execs, contains, extra) = state |
94 | self.__init__(refs, execs, contains) | 144 | self.__init__(refs, execs, contains, extra) |
95 | def __hash__(self): | 145 | def __hash__(self): |
96 | l = (hash(self.refs), hash(self.execs)) | 146 | l = (hash(self.refs), hash(self.execs), hash(self.extra)) |
97 | for c in sorted(self.contains.keys()): | 147 | for c in sorted(self.contains.keys()): |
98 | l = l + (c, hash(self.contains[c])) | 148 | l = l + (c, hash(self.contains[c])) |
99 | return hash(l) | 149 | return hash(l) |
@@ -122,7 +172,7 @@ class CodeParserCache(MultiProcessCache): | |||
122 | # so that an existing cache gets invalidated. Additionally you'll need | 172 | # so that an existing cache gets invalidated. Additionally you'll need |
123 | # to increment __cache_version__ in cache.py in order to ensure that old | 173 | # to increment __cache_version__ in cache.py in order to ensure that old |
124 | # recipe caches don't trigger "Taskhash mismatch" errors. | 174 | # recipe caches don't trigger "Taskhash mismatch" errors. |
125 | CACHE_VERSION = 11 | 175 | CACHE_VERSION = 14 |
126 | 176 | ||
127 | def __init__(self): | 177 | def __init__(self): |
128 | MultiProcessCache.__init__(self) | 178 | MultiProcessCache.__init__(self) |
@@ -136,8 +186,8 @@ class CodeParserCache(MultiProcessCache): | |||
136 | self.pythoncachelines = {} | 186 | self.pythoncachelines = {} |
137 | self.shellcachelines = {} | 187 | self.shellcachelines = {} |
138 | 188 | ||
139 | def newPythonCacheLine(self, refs, execs, contains): | 189 | def newPythonCacheLine(self, refs, execs, contains, extra): |
140 | cacheline = pythonCacheLine(refs, execs, contains) | 190 | cacheline = pythonCacheLine(refs, execs, contains, extra) |
141 | h = hash(cacheline) | 191 | h = hash(cacheline) |
142 | if h in self.pythoncachelines: | 192 | if h in self.pythoncachelines: |
143 | return self.pythoncachelines[h] | 193 | return self.pythoncachelines[h] |
@@ -152,12 +202,12 @@ class CodeParserCache(MultiProcessCache): | |||
152 | self.shellcachelines[h] = cacheline | 202 | self.shellcachelines[h] = cacheline |
153 | return cacheline | 203 | return cacheline |
154 | 204 | ||
155 | def init_cache(self, d): | 205 | def init_cache(self, cachedir): |
156 | # Check if we already have the caches | 206 | # Check if we already have the caches |
157 | if self.pythoncache: | 207 | if self.pythoncache: |
158 | return | 208 | return |
159 | 209 | ||
160 | MultiProcessCache.init_cache(self, d) | 210 | MultiProcessCache.init_cache(self, cachedir) |
161 | 211 | ||
162 | # cachedata gets re-assigned in the parent | 212 | # cachedata gets re-assigned in the parent |
163 | self.pythoncache = self.cachedata[0] | 213 | self.pythoncache = self.cachedata[0] |
@@ -169,8 +219,8 @@ class CodeParserCache(MultiProcessCache): | |||
169 | 219 | ||
170 | codeparsercache = CodeParserCache() | 220 | codeparsercache = CodeParserCache() |
171 | 221 | ||
172 | def parser_cache_init(d): | 222 | def parser_cache_init(cachedir): |
173 | codeparsercache.init_cache(d) | 223 | codeparsercache.init_cache(cachedir) |
174 | 224 | ||
175 | def parser_cache_save(): | 225 | def parser_cache_save(): |
176 | codeparsercache.save_extras() | 226 | codeparsercache.save_extras() |
@@ -195,6 +245,10 @@ class BufferedLogger(Logger): | |||
195 | self.target.handle(record) | 245 | self.target.handle(record) |
196 | self.buffer = [] | 246 | self.buffer = [] |
197 | 247 | ||
248 | class DummyLogger(): | ||
249 | def flush(self): | ||
250 | return | ||
251 | |||
198 | class PythonParser(): | 252 | class PythonParser(): |
199 | getvars = (".getVar", ".appendVar", ".prependVar", "oe.utils.conditional") | 253 | getvars = (".getVar", ".appendVar", ".prependVar", "oe.utils.conditional") |
200 | getvarflags = (".getVarFlag", ".appendVarFlag", ".prependVarFlag") | 254 | getvarflags = (".getVarFlag", ".appendVarFlag", ".prependVarFlag") |
@@ -212,26 +266,34 @@ class PythonParser(): | |||
212 | funcstr = codegen.to_source(func) | 266 | funcstr = codegen.to_source(func) |
213 | argstr = codegen.to_source(arg) | 267 | argstr = codegen.to_source(arg) |
214 | except TypeError: | 268 | except TypeError: |
215 | self.log.debug(2, 'Failed to convert function and argument to source form') | 269 | self.log.debug2('Failed to convert function and argument to source form') |
216 | else: | 270 | else: |
217 | self.log.debug(1, self.unhandled_message % (funcstr, argstr)) | 271 | self.log.debug(self.unhandled_message % (funcstr, argstr)) |
218 | 272 | ||
219 | def visit_Call(self, node): | 273 | def visit_Call(self, node): |
220 | name = self.called_node_name(node.func) | 274 | name = self.called_node_name(node.func) |
221 | if name and (name.endswith(self.getvars) or name.endswith(self.getvarflags) or name in self.containsfuncs or name in self.containsanyfuncs): | 275 | if name and name in modulecode_deps and modulecode_deps[name][5]: |
222 | if isinstance(node.args[0], ast.Str): | 276 | visitorcode = modulecode_deps[name][5] |
223 | varname = node.args[0].s | 277 | contains, execs, warn = visitorcode(name, node.args) |
224 | if name in self.containsfuncs and isinstance(node.args[1], ast.Str): | 278 | for i in contains: |
279 | self.contains[i] = contains[i] | ||
280 | self.execs |= execs | ||
281 | if warn: | ||
282 | self.warn(node.func, warn) | ||
283 | elif name and (name.endswith(self.getvars) or name.endswith(self.getvarflags) or name in self.containsfuncs or name in self.containsanyfuncs): | ||
284 | if isinstance(node.args[0], ast.Constant) and isinstance(node.args[0].value, str): | ||
285 | varname = node.args[0].value | ||
286 | if name in self.containsfuncs and isinstance(node.args[1], ast.Constant): | ||
225 | if varname not in self.contains: | 287 | if varname not in self.contains: |
226 | self.contains[varname] = set() | 288 | self.contains[varname] = set() |
227 | self.contains[varname].add(node.args[1].s) | 289 | self.contains[varname].add(node.args[1].value) |
228 | elif name in self.containsanyfuncs and isinstance(node.args[1], ast.Str): | 290 | elif name in self.containsanyfuncs and isinstance(node.args[1], ast.Constant): |
229 | if varname not in self.contains: | 291 | if varname not in self.contains: |
230 | self.contains[varname] = set() | 292 | self.contains[varname] = set() |
231 | self.contains[varname].update(node.args[1].s.split()) | 293 | self.contains[varname].update(node.args[1].value.split()) |
232 | elif name.endswith(self.getvarflags): | 294 | elif name.endswith(self.getvarflags): |
233 | if isinstance(node.args[1], ast.Str): | 295 | if isinstance(node.args[1], ast.Constant): |
234 | self.references.add('%s[%s]' % (varname, node.args[1].s)) | 296 | self.references.add('%s[%s]' % (varname, node.args[1].value)) |
235 | else: | 297 | else: |
236 | self.warn(node.func, node.args[1]) | 298 | self.warn(node.func, node.args[1]) |
237 | else: | 299 | else: |
@@ -239,8 +301,8 @@ class PythonParser(): | |||
239 | else: | 301 | else: |
240 | self.warn(node.func, node.args[0]) | 302 | self.warn(node.func, node.args[0]) |
241 | elif name and name.endswith(".expand"): | 303 | elif name and name.endswith(".expand"): |
242 | if isinstance(node.args[0], ast.Str): | 304 | if isinstance(node.args[0], ast.Constant): |
243 | value = node.args[0].s | 305 | value = node.args[0].value |
244 | d = bb.data.init() | 306 | d = bb.data.init() |
245 | parser = d.expandWithRefs(value, self.name) | 307 | parser = d.expandWithRefs(value, self.name) |
246 | self.references |= parser.references | 308 | self.references |= parser.references |
@@ -250,8 +312,8 @@ class PythonParser(): | |||
250 | self.contains[varname] = set() | 312 | self.contains[varname] = set() |
251 | self.contains[varname] |= parser.contains[varname] | 313 | self.contains[varname] |= parser.contains[varname] |
252 | elif name in self.execfuncs: | 314 | elif name in self.execfuncs: |
253 | if isinstance(node.args[0], ast.Str): | 315 | if isinstance(node.args[0], ast.Constant): |
254 | self.var_execs.add(node.args[0].s) | 316 | self.var_execs.add(node.args[0].value) |
255 | else: | 317 | else: |
256 | self.warn(node.func, node.args[0]) | 318 | self.warn(node.func, node.args[0]) |
257 | elif name and isinstance(node.func, (ast.Name, ast.Attribute)): | 319 | elif name and isinstance(node.func, (ast.Name, ast.Attribute)): |
@@ -276,16 +338,24 @@ class PythonParser(): | |||
276 | self.contains = {} | 338 | self.contains = {} |
277 | self.execs = set() | 339 | self.execs = set() |
278 | self.references = set() | 340 | self.references = set() |
279 | self.log = BufferedLogger('BitBake.Data.PythonParser', logging.DEBUG, log) | 341 | self._log = log |
342 | # Defer init as expensive | ||
343 | self.log = DummyLogger() | ||
280 | 344 | ||
281 | self.unhandled_message = "in call of %s, argument '%s' is not a string literal" | 345 | self.unhandled_message = "in call of %s, argument '%s' is not a string literal" |
282 | self.unhandled_message = "while parsing %s, %s" % (name, self.unhandled_message) | 346 | self.unhandled_message = "while parsing %s, %s" % (name, self.unhandled_message) |
283 | 347 | ||
284 | def parse_python(self, node, lineno=0, filename="<string>"): | 348 | # For the python module code it is expensive to have the function text so it is |
285 | if not node or not node.strip(): | 349 | # uses a different fixedhash to cache against. We can take the hit on obtaining the |
350 | # text if it isn't in the cache. | ||
351 | def parse_python(self, node, lineno=0, filename="<string>", fixedhash=None, func=None): | ||
352 | if not fixedhash and (not node or not node.strip()): | ||
286 | return | 353 | return |
287 | 354 | ||
288 | h = bbhash(str(node)) | 355 | if fixedhash: |
356 | h = fixedhash | ||
357 | else: | ||
358 | h = bbhash(str(node)) | ||
289 | 359 | ||
290 | if h in codeparsercache.pythoncache: | 360 | if h in codeparsercache.pythoncache: |
291 | self.references = set(codeparsercache.pythoncache[h].refs) | 361 | self.references = set(codeparsercache.pythoncache[h].refs) |
@@ -293,6 +363,7 @@ class PythonParser(): | |||
293 | self.contains = {} | 363 | self.contains = {} |
294 | for i in codeparsercache.pythoncache[h].contains: | 364 | for i in codeparsercache.pythoncache[h].contains: |
295 | self.contains[i] = set(codeparsercache.pythoncache[h].contains[i]) | 365 | self.contains[i] = set(codeparsercache.pythoncache[h].contains[i]) |
366 | self.extra = codeparsercache.pythoncache[h].extra | ||
296 | return | 367 | return |
297 | 368 | ||
298 | if h in codeparsercache.pythoncacheextras: | 369 | if h in codeparsercache.pythoncacheextras: |
@@ -301,8 +372,15 @@ class PythonParser(): | |||
301 | self.contains = {} | 372 | self.contains = {} |
302 | for i in codeparsercache.pythoncacheextras[h].contains: | 373 | for i in codeparsercache.pythoncacheextras[h].contains: |
303 | self.contains[i] = set(codeparsercache.pythoncacheextras[h].contains[i]) | 374 | self.contains[i] = set(codeparsercache.pythoncacheextras[h].contains[i]) |
375 | self.extra = codeparsercache.pythoncacheextras[h].extra | ||
304 | return | 376 | return |
305 | 377 | ||
378 | if fixedhash and not node: | ||
379 | raise KeyError | ||
380 | |||
381 | # Need to parse so take the hit on the real log buffer | ||
382 | self.log = BufferedLogger('BitBake.Data.PythonParser', logging.DEBUG, self._log) | ||
383 | |||
306 | # We can't add to the linenumbers for compile, we can pad to the correct number of blank lines though | 384 | # We can't add to the linenumbers for compile, we can pad to the correct number of blank lines though |
307 | node = "\n" * int(lineno) + node | 385 | node = "\n" * int(lineno) + node |
308 | code = compile(check_indent(str(node)), filename, "exec", | 386 | code = compile(check_indent(str(node)), filename, "exec", |
@@ -312,16 +390,27 @@ class PythonParser(): | |||
312 | if n.__class__.__name__ == "Call": | 390 | if n.__class__.__name__ == "Call": |
313 | self.visit_Call(n) | 391 | self.visit_Call(n) |
314 | 392 | ||
393 | if func is not None: | ||
394 | self.references |= getattr(func, "bb_vardeps", set()) | ||
395 | self.references -= getattr(func, "bb_vardepsexclude", set()) | ||
396 | |||
315 | self.execs.update(self.var_execs) | 397 | self.execs.update(self.var_execs) |
398 | self.extra = None | ||
399 | if fixedhash: | ||
400 | self.extra = bbhash(str(node)) | ||
316 | 401 | ||
317 | codeparsercache.pythoncacheextras[h] = codeparsercache.newPythonCacheLine(self.references, self.execs, self.contains) | 402 | codeparsercache.pythoncacheextras[h] = codeparsercache.newPythonCacheLine(self.references, self.execs, self.contains, self.extra) |
318 | 403 | ||
319 | class ShellParser(): | 404 | class ShellParser(): |
320 | def __init__(self, name, log): | 405 | def __init__(self, name, log): |
321 | self.funcdefs = set() | 406 | self.funcdefs = set() |
322 | self.allexecs = set() | 407 | self.allexecs = set() |
323 | self.execs = set() | 408 | self.execs = set() |
324 | self.log = BufferedLogger('BitBake.Data.%s' % name, logging.DEBUG, log) | 409 | self._name = name |
410 | self._log = log | ||
411 | # Defer init as expensive | ||
412 | self.log = DummyLogger() | ||
413 | |||
325 | self.unhandled_template = "unable to handle non-literal command '%s'" | 414 | self.unhandled_template = "unable to handle non-literal command '%s'" |
326 | self.unhandled_template = "while parsing %s, %s" % (name, self.unhandled_template) | 415 | self.unhandled_template = "while parsing %s, %s" % (name, self.unhandled_template) |
327 | 416 | ||
@@ -340,6 +429,9 @@ class ShellParser(): | |||
340 | self.execs = set(codeparsercache.shellcacheextras[h].execs) | 429 | self.execs = set(codeparsercache.shellcacheextras[h].execs) |
341 | return self.execs | 430 | return self.execs |
342 | 431 | ||
432 | # Need to parse so take the hit on the real log buffer | ||
433 | self.log = BufferedLogger('BitBake.Data.%s' % self._name, logging.DEBUG, self._log) | ||
434 | |||
343 | self._parse_shell(value) | 435 | self._parse_shell(value) |
344 | self.execs = set(cmd for cmd in self.allexecs if cmd not in self.funcdefs) | 436 | self.execs = set(cmd for cmd in self.allexecs if cmd not in self.funcdefs) |
345 | 437 | ||
@@ -426,19 +518,34 @@ class ShellParser(): | |||
426 | """ | 518 | """ |
427 | 519 | ||
428 | words = list(words) | 520 | words = list(words) |
429 | for word in list(words): | 521 | for word in words: |
430 | wtree = pyshlex.make_wordtree(word[1]) | 522 | wtree = pyshlex.make_wordtree(word[1]) |
431 | for part in wtree: | 523 | for part in wtree: |
432 | if not isinstance(part, list): | 524 | if not isinstance(part, list): |
433 | continue | 525 | continue |
434 | 526 | ||
435 | if part[0] in ('`', '$('): | 527 | candidates = [part] |
436 | command = pyshlex.wordtree_as_string(part[1:-1]) | 528 | |
437 | self._parse_shell(command) | 529 | # If command is of type: |
438 | 530 | # | |
439 | if word[0] in ("cmd_name", "cmd_word"): | 531 | # var="... $(cmd [...]) ..." |
440 | if word in words: | 532 | # |
441 | words.remove(word) | 533 | # Then iterate on what's between the quotes and if we find a |
534 | # list, make that what we check for below. | ||
535 | if len(part) >= 3 and part[0] == '"': | ||
536 | for p in part[1:-1]: | ||
537 | if isinstance(p, list): | ||
538 | candidates.append(p) | ||
539 | |||
540 | for candidate in candidates: | ||
541 | if len(candidate) >= 2: | ||
542 | if candidate[0] in ('`', '$('): | ||
543 | command = pyshlex.wordtree_as_string(candidate[1:-1]) | ||
544 | self._parse_shell(command) | ||
545 | |||
546 | if word[0] in ("cmd_name", "cmd_word"): | ||
547 | if word in words: | ||
548 | words.remove(word) | ||
442 | 549 | ||
443 | usetoken = False | 550 | usetoken = False |
444 | for word in words: | 551 | for word in words: |
@@ -450,7 +557,7 @@ class ShellParser(): | |||
450 | 557 | ||
451 | cmd = word[1] | 558 | cmd = word[1] |
452 | if cmd.startswith("$"): | 559 | if cmd.startswith("$"): |
453 | self.log.debug(1, self.unhandled_template % cmd) | 560 | self.log.debug(self.unhandled_template % cmd) |
454 | elif cmd == "eval": | 561 | elif cmd == "eval": |
455 | command = " ".join(word for _, word in words[1:]) | 562 | command = " ".join(word for _, word in words[1:]) |
456 | self._parse_shell(command) | 563 | self._parse_shell(command) |