summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/codeparser.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/bb/codeparser.py')
-rw-r--r--bitbake/lib/bb/codeparser.py195
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
25import sys 27import sys
26import codegen 28import codegen
27import logging 29import logging
30import inspect
28import bb.pysh as pysh 31import bb.pysh as pysh
29import bb.utils, bb.data 32import bb.utils, bb.data
30import hashlib 33import 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 62modulecode_deps = {}
60# avoiding duplication of the attribute names!
61 63
64def 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
104def 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!
63class SetCache(object): 112class SetCache(object):
64 def __init__(self): 113 def __init__(self):
65 self.setcache = {} 114 self.setcache = {}
@@ -79,21 +128,22 @@ class SetCache(object):
79codecache = SetCache() 128codecache = SetCache()
80 129
81class pythonCacheLine(object): 130class 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
170codeparsercache = CodeParserCache() 220codeparsercache = CodeParserCache()
171 221
172def parser_cache_init(d): 222def parser_cache_init(cachedir):
173 codeparsercache.init_cache(d) 223 codeparsercache.init_cache(cachedir)
174 224
175def parser_cache_save(): 225def 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
248class DummyLogger():
249 def flush(self):
250 return
251
198class PythonParser(): 252class 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
319class ShellParser(): 404class 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)