blob: bef3af31a332cca78959dfc8e8a9bcd40b20eb96 [file] [log] [blame]
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001import collections
2import fnmatch
3import logging
4import sys
5import os
6import re
7
Patrick Williamsc0f7c042017-02-23 20:41:17 -06008import bb.utils
9
10from bblayers.common import LayerPlugin
11
12logger = logging.getLogger('bitbake-layers')
13
14
15def plugin_init(plugins):
16 return QueryPlugin()
17
18
19class QueryPlugin(LayerPlugin):
20 def do_show_layers(self, args):
21 """show current configured layers."""
22 logger.plain("%s %s %s" % ("layer".ljust(20), "path".ljust(40), "priority"))
23 logger.plain('=' * 74)
24 for layer, _, regex, pri in self.tinfoil.cooker.bbfile_config_priorities:
25 layerdir = self.bbfile_collections.get(layer, None)
26 layername = self.get_layer_name(layerdir)
27 logger.plain("%s %s %d" % (layername.ljust(20), layerdir.ljust(40), pri))
28
29 def version_str(self, pe, pv, pr = None):
30 verstr = "%s" % pv
31 if pr:
32 verstr = "%s-%s" % (verstr, pr)
33 if pe:
34 verstr = "%s:%s" % (pe, verstr)
35 return verstr
36
37 def do_show_overlayed(self, args):
38 """list overlayed recipes (where the same recipe exists in another layer)
39
40Lists the names of overlayed recipes and the available versions in each
41layer, with the preferred version first. Note that skipped recipes that
42are overlayed will also be listed, with a " (skipped)" suffix.
43"""
44
45 items_listed = self.list_recipes('Overlayed recipes', None, True, args.same_version, args.filenames, True, None)
46
47 # Check for overlayed .bbclass files
48 classes = collections.defaultdict(list)
49 for layerdir in self.bblayers:
50 classdir = os.path.join(layerdir, 'classes')
51 if os.path.exists(classdir):
52 for classfile in os.listdir(classdir):
53 if os.path.splitext(classfile)[1] == '.bbclass':
54 classes[classfile].append(classdir)
55
56 # Locating classes and other files is a bit more complicated than recipes -
57 # layer priority is not a factor; instead BitBake uses the first matching
58 # file in BBPATH, which is manipulated directly by each layer's
59 # conf/layer.conf in turn, thus the order of layers in bblayers.conf is a
60 # factor - however, each layer.conf is free to either prepend or append to
61 # BBPATH (or indeed do crazy stuff with it). Thus the order in BBPATH might
62 # not be exactly the order present in bblayers.conf either.
Brad Bishop6e60e8b2018-02-01 10:27:11 -050063 bbpath = str(self.tinfoil.config_data.getVar('BBPATH'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -060064 overlayed_class_found = False
65 for (classfile, classdirs) in classes.items():
66 if len(classdirs) > 1:
67 if not overlayed_class_found:
68 logger.plain('=== Overlayed classes ===')
69 overlayed_class_found = True
70
71 mainfile = bb.utils.which(bbpath, os.path.join('classes', classfile))
72 if args.filenames:
73 logger.plain('%s' % mainfile)
74 else:
75 # We effectively have to guess the layer here
76 logger.plain('%s:' % classfile)
77 mainlayername = '?'
78 for layerdir in self.bblayers:
79 classdir = os.path.join(layerdir, 'classes')
80 if mainfile.startswith(classdir):
81 mainlayername = self.get_layer_name(layerdir)
82 logger.plain(' %s' % mainlayername)
83 for classdir in classdirs:
84 fullpath = os.path.join(classdir, classfile)
85 if fullpath != mainfile:
86 if args.filenames:
87 print(' %s' % fullpath)
88 else:
89 print(' %s' % self.get_layer_name(os.path.dirname(classdir)))
90
91 if overlayed_class_found:
92 items_listed = True;
93
94 if not items_listed:
95 logger.plain('No overlayed files found.')
96
97 def do_show_recipes(self, args):
98 """list available recipes, showing the layer they are provided by
99
100Lists the names of recipes and the available versions in each
101layer, with the preferred version first. Optionally you may specify
102pnspec to match a specified recipe name (supports wildcards). Note that
103skipped recipes will also be listed, with a " (skipped)" suffix.
104"""
105
106 inheritlist = args.inherits.split(',') if args.inherits else []
107 if inheritlist or args.pnspec or args.multiple:
108 title = 'Matching recipes:'
109 else:
110 title = 'Available recipes:'
111 self.list_recipes(title, args.pnspec, False, False, args.filenames, args.multiple, inheritlist)
112
113 def list_recipes(self, title, pnspec, show_overlayed_only, show_same_ver_only, show_filenames, show_multi_provider_only, inherits):
114 if inherits:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500115 bbpath = str(self.tinfoil.config_data.getVar('BBPATH'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600116 for classname in inherits:
117 classfile = 'classes/%s.bbclass' % classname
118 if not bb.utils.which(bbpath, classfile, history=False):
119 logger.error('No class named %s found in BBPATH', classfile)
120 sys.exit(1)
121
122 pkg_pn = self.tinfoil.cooker.recipecaches[''].pkg_pn
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500123 (latest_versions, preferred_versions) = self.tinfoil.find_providers()
124 allproviders = self.tinfoil.get_all_providers()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600125
126 # Ensure we list skipped recipes
127 # We are largely guessing about PN, PV and the preferred version here,
128 # but we have no choice since skipped recipes are not fully parsed
129 skiplist = list(self.tinfoil.cooker.skiplist.keys())
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600130 for fn in skiplist:
131 recipe_parts = os.path.splitext(os.path.basename(fn))[0].split('_')
132 p = recipe_parts[0]
133 if len(recipe_parts) > 1:
134 ver = (None, recipe_parts[1], None)
135 else:
136 ver = (None, 'unknown', None)
137 allproviders[p].append((ver, fn))
138 if not p in pkg_pn:
139 pkg_pn[p] = 'dummy'
140 preferred_versions[p] = (ver, fn)
141
142 def print_item(f, pn, ver, layer, ispref):
143 if f in skiplist:
144 skipped = ' (skipped)'
145 else:
146 skipped = ''
147 if show_filenames:
148 if ispref:
149 logger.plain("%s%s", f, skipped)
150 else:
151 logger.plain(" %s%s", f, skipped)
152 else:
153 if ispref:
154 logger.plain("%s:", pn)
155 logger.plain(" %s %s%s", layer.ljust(20), ver, skipped)
156
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500157 global_inherit = (self.tinfoil.config_data.getVar('INHERIT') or "").split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600158 cls_re = re.compile('classes/')
159
160 preffiles = []
161 items_listed = False
162 for p in sorted(pkg_pn):
163 if pnspec:
164 if not fnmatch.fnmatch(p, pnspec):
165 continue
166
167 if len(allproviders[p]) > 1 or not show_multi_provider_only:
168 pref = preferred_versions[p]
169 realfn = bb.cache.virtualfn2realfn(pref[1])
170 preffile = realfn[0]
171
172 # We only display once per recipe, we should prefer non extended versions of the
173 # recipe if present (so e.g. in OpenEmbedded, openssl rather than nativesdk-openssl
174 # which would otherwise sort first).
175 if realfn[1] and realfn[0] in self.tinfoil.cooker.recipecaches[''].pkg_fn:
176 continue
177
178 if inherits:
179 matchcount = 0
180 recipe_inherits = self.tinfoil.cooker_data.inherits.get(preffile, [])
181 for cls in recipe_inherits:
182 if cls_re.match(cls):
183 continue
184 classname = os.path.splitext(os.path.basename(cls))[0]
185 if classname in global_inherit:
186 continue
187 elif classname in inherits:
188 matchcount += 1
189 if matchcount != len(inherits):
190 # No match - skip this recipe
191 continue
192
193 if preffile not in preffiles:
194 preflayer = self.get_file_layer(preffile)
195 multilayer = False
196 same_ver = True
197 provs = []
198 for prov in allproviders[p]:
199 provfile = bb.cache.virtualfn2realfn(prov[1])[0]
200 provlayer = self.get_file_layer(provfile)
201 provs.append((provfile, provlayer, prov[0]))
202 if provlayer != preflayer:
203 multilayer = True
204 if prov[0] != pref[0]:
205 same_ver = False
206
207 if (multilayer or not show_overlayed_only) and (same_ver or not show_same_ver_only):
208 if not items_listed:
209 logger.plain('=== %s ===' % title)
210 items_listed = True
211 print_item(preffile, p, self.version_str(pref[0][0], pref[0][1]), preflayer, True)
212 for (provfile, provlayer, provver) in provs:
213 if provfile != preffile:
214 print_item(provfile, p, self.version_str(provver[0], provver[1]), provlayer, False)
215 # Ensure we don't show two entries for BBCLASSEXTENDed recipes
216 preffiles.append(preffile)
217
218 return items_listed
219
220 def get_file_layer(self, filename):
221 layerdir = self.get_file_layerdir(filename)
222 if layerdir:
223 return self.get_layer_name(layerdir)
224 else:
225 return '?'
226
227 def get_file_layerdir(self, filename):
228 layer = bb.utils.get_file_layer(filename, self.tinfoil.config_data)
229 return self.bbfile_collections.get(layer, None)
230
231 def remove_layer_prefix(self, f):
232 """Remove the layer_dir prefix, e.g., f = /path/to/layer_dir/foo/blah, the
233 return value will be: layer_dir/foo/blah"""
234 f_layerdir = self.get_file_layerdir(f)
235 if not f_layerdir:
236 return f
237 prefix = os.path.join(os.path.dirname(f_layerdir), '')
238 return f[len(prefix):] if f.startswith(prefix) else f
239
240 def do_show_appends(self, args):
241 """list bbappend files and recipe files they apply to
242
243Lists recipes with the bbappends that apply to them as subitems.
244"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500245 if args.pnspec:
246 logger.plain('=== Matched appended recipes ===')
247 else:
248 logger.plain('=== Appended recipes ===')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600249
250 pnlist = list(self.tinfoil.cooker_data.pkg_pn.keys())
251 pnlist.sort()
252 appends = False
253 for pn in pnlist:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500254 if args.pnspec and pn != args.pnspec:
255 continue
256
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600257 if self.show_appends_for_pn(pn):
258 appends = True
259
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500260 if not args.pnspec and self.show_appends_for_skipped():
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600261 appends = True
262
263 if not appends:
264 logger.plain('No append files found')
265
266 def show_appends_for_pn(self, pn):
267 filenames = self.tinfoil.cooker_data.pkg_pn[pn]
268
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500269 best = self.tinfoil.find_best_provider(pn)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600270 best_filename = os.path.basename(best[3])
271
272 return self.show_appends_output(filenames, best_filename)
273
274 def show_appends_for_skipped(self):
275 filenames = [os.path.basename(f)
276 for f in self.tinfoil.cooker.skiplist.keys()]
277 return self.show_appends_output(filenames, None, " (skipped)")
278
279 def show_appends_output(self, filenames, best_filename, name_suffix = ''):
280 appended, missing = self.get_appends_for_files(filenames)
281 if appended:
282 for basename, appends in appended:
283 logger.plain('%s%s:', basename, name_suffix)
284 for append in appends:
285 logger.plain(' %s', append)
286
287 if best_filename:
288 if best_filename in missing:
289 logger.warning('%s: missing append for preferred version',
290 best_filename)
291 return True
292 else:
293 return False
294
295 def get_appends_for_files(self, filenames):
296 appended, notappended = [], []
297 for filename in filenames:
298 _, cls, _ = bb.cache.virtualfn2realfn(filename)
299 if cls:
300 continue
301
302 basename = os.path.basename(filename)
303 appends = self.tinfoil.cooker.collection.get_file_appends(basename)
304 if appends:
305 appended.append((basename, list(appends)))
306 else:
307 notappended.append(basename)
308 return appended, notappended
309
310 def do_show_cross_depends(self, args):
311 """Show dependencies between recipes that cross layer boundaries.
312
313Figure out the dependencies between recipes that cross layer boundaries.
314
315NOTE: .bbappend files can impact the dependencies.
316"""
317 ignore_layers = (args.ignore or '').split(',')
318
319 pkg_fn = self.tinfoil.cooker_data.pkg_fn
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500320 bbpath = str(self.tinfoil.config_data.getVar('BBPATH'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600321 self.require_re = re.compile(r"require\s+(.+)")
322 self.include_re = re.compile(r"include\s+(.+)")
323 self.inherit_re = re.compile(r"inherit\s+(.+)")
324
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500325 global_inherit = (self.tinfoil.config_data.getVar('INHERIT') or "").split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600326
327 # The bb's DEPENDS and RDEPENDS
328 for f in pkg_fn:
329 f = bb.cache.virtualfn2realfn(f)[0]
330 # Get the layername that the file is in
331 layername = self.get_file_layer(f)
332
333 # The DEPENDS
334 deps = self.tinfoil.cooker_data.deps[f]
335 for pn in deps:
336 if pn in self.tinfoil.cooker_data.pkg_pn:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500337 best = self.tinfoil.find_best_provider(pn)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600338 self.check_cross_depends("DEPENDS", layername, f, best[3], args.filenames, ignore_layers)
339
340 # The RDPENDS
341 all_rdeps = self.tinfoil.cooker_data.rundeps[f].values()
342 # Remove the duplicated or null one.
343 sorted_rdeps = {}
344 # The all_rdeps is the list in list, so we need two for loops
345 for k1 in all_rdeps:
346 for k2 in k1:
347 sorted_rdeps[k2] = 1
348 all_rdeps = sorted_rdeps.keys()
349 for rdep in all_rdeps:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500350 all_p, best = self.tinfoil.get_runtime_providers(rdep)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600351 if all_p:
352 if f in all_p:
353 # The recipe provides this one itself, ignore
354 continue
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600355 self.check_cross_depends("RDEPENDS", layername, f, best, args.filenames, ignore_layers)
356
357 # The RRECOMMENDS
358 all_rrecs = self.tinfoil.cooker_data.runrecs[f].values()
359 # Remove the duplicated or null one.
360 sorted_rrecs = {}
361 # The all_rrecs is the list in list, so we need two for loops
362 for k1 in all_rrecs:
363 for k2 in k1:
364 sorted_rrecs[k2] = 1
365 all_rrecs = sorted_rrecs.keys()
366 for rrec in all_rrecs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500367 all_p, best = self.tinfoil.get_runtime_providers(rrec)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600368 if all_p:
369 if f in all_p:
370 # The recipe provides this one itself, ignore
371 continue
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600372 self.check_cross_depends("RRECOMMENDS", layername, f, best, args.filenames, ignore_layers)
373
374 # The inherit class
375 cls_re = re.compile('classes/')
376 if f in self.tinfoil.cooker_data.inherits:
377 inherits = self.tinfoil.cooker_data.inherits[f]
378 for cls in inherits:
379 # The inherits' format is [classes/cls, /path/to/classes/cls]
380 # ignore the classes/cls.
381 if not cls_re.match(cls):
382 classname = os.path.splitext(os.path.basename(cls))[0]
383 if classname in global_inherit:
384 continue
385 inherit_layername = self.get_file_layer(cls)
386 if inherit_layername != layername and not inherit_layername in ignore_layers:
387 if not args.filenames:
388 f_short = self.remove_layer_prefix(f)
389 cls = self.remove_layer_prefix(cls)
390 else:
391 f_short = f
392 logger.plain("%s inherits %s" % (f_short, cls))
393
394 # The 'require/include xxx' in the bb file
395 pv_re = re.compile(r"\${PV}")
396 with open(f, 'r') as fnfile:
397 line = fnfile.readline()
398 while line:
399 m, keyword = self.match_require_include(line)
400 # Found the 'require/include xxxx'
401 if m:
402 needed_file = m.group(1)
403 # Replace the ${PV} with the real PV
404 if pv_re.search(needed_file) and f in self.tinfoil.cooker_data.pkg_pepvpr:
405 pv = self.tinfoil.cooker_data.pkg_pepvpr[f][1]
406 needed_file = re.sub(r"\${PV}", pv, needed_file)
407 self.print_cross_files(bbpath, keyword, layername, f, needed_file, args.filenames, ignore_layers)
408 line = fnfile.readline()
409
410 # The "require/include xxx" in conf/machine/*.conf, .inc and .bbclass
411 conf_re = re.compile(".*/conf/machine/[^\/]*\.conf$")
412 inc_re = re.compile(".*\.inc$")
413 # The "inherit xxx" in .bbclass
414 bbclass_re = re.compile(".*\.bbclass$")
415 for layerdir in self.bblayers:
416 layername = self.get_layer_name(layerdir)
417 for dirpath, dirnames, filenames in os.walk(layerdir):
418 for name in filenames:
419 f = os.path.join(dirpath, name)
420 s = conf_re.match(f) or inc_re.match(f) or bbclass_re.match(f)
421 if s:
422 with open(f, 'r') as ffile:
423 line = ffile.readline()
424 while line:
425 m, keyword = self.match_require_include(line)
426 # Only bbclass has the "inherit xxx" here.
427 bbclass=""
428 if not m and f.endswith(".bbclass"):
429 m, keyword = self.match_inherit(line)
430 bbclass=".bbclass"
431 # Find a 'require/include xxxx'
432 if m:
433 self.print_cross_files(bbpath, keyword, layername, f, m.group(1) + bbclass, args.filenames, ignore_layers)
434 line = ffile.readline()
435
436 def print_cross_files(self, bbpath, keyword, layername, f, needed_filename, show_filenames, ignore_layers):
437 """Print the depends that crosses a layer boundary"""
438 needed_file = bb.utils.which(bbpath, needed_filename)
439 if needed_file:
440 # Which layer is this file from
441 needed_layername = self.get_file_layer(needed_file)
442 if needed_layername != layername and not needed_layername in ignore_layers:
443 if not show_filenames:
444 f = self.remove_layer_prefix(f)
445 needed_file = self.remove_layer_prefix(needed_file)
446 logger.plain("%s %s %s" %(f, keyword, needed_file))
447
448 def match_inherit(self, line):
449 """Match the inherit xxx line"""
450 return (self.inherit_re.match(line), "inherits")
451
452 def match_require_include(self, line):
453 """Match the require/include xxx line"""
454 m = self.require_re.match(line)
455 keyword = "requires"
456 if not m:
457 m = self.include_re.match(line)
458 keyword = "includes"
459 return (m, keyword)
460
461 def check_cross_depends(self, keyword, layername, f, needed_file, show_filenames, ignore_layers):
462 """Print the DEPENDS/RDEPENDS file that crosses a layer boundary"""
463 best_realfn = bb.cache.virtualfn2realfn(needed_file)[0]
464 needed_layername = self.get_file_layer(best_realfn)
465 if needed_layername != layername and not needed_layername in ignore_layers:
466 if not show_filenames:
467 f = self.remove_layer_prefix(f)
468 best_realfn = self.remove_layer_prefix(best_realfn)
469
470 logger.plain("%s %s %s" % (f, keyword, best_realfn))
471
472 def register_commands(self, sp):
473 self.add_command(sp, 'show-layers', self.do_show_layers, parserecipes=False)
474
475 parser_show_overlayed = self.add_command(sp, 'show-overlayed', self.do_show_overlayed)
476 parser_show_overlayed.add_argument('-f', '--filenames', help='instead of the default formatting, list filenames of higher priority recipes with the ones they overlay indented underneath', action='store_true')
477 parser_show_overlayed.add_argument('-s', '--same-version', help='only list overlayed recipes where the version is the same', action='store_true')
478
479 parser_show_recipes = self.add_command(sp, 'show-recipes', self.do_show_recipes)
480 parser_show_recipes.add_argument('-f', '--filenames', help='instead of the default formatting, list filenames of higher priority recipes with the ones they overlay indented underneath', action='store_true')
481 parser_show_recipes.add_argument('-m', '--multiple', help='only list where multiple recipes (in the same layer or different layers) exist for the same recipe name', action='store_true')
482 parser_show_recipes.add_argument('-i', '--inherits', help='only list recipes that inherit the named class', metavar='CLASS', default='')
483 parser_show_recipes.add_argument('pnspec', nargs='?', help='optional recipe name specification (wildcards allowed, enclose in quotes to avoid shell expansion)')
484
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500485 parser_show_appends = self.add_command(sp, 'show-appends', self.do_show_appends)
486 parser_show_appends.add_argument('pnspec', nargs='?', help='optional recipe name specification (wildcards allowed, enclose in quotes to avoid shell expansion)')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600487
488 parser_show_cross_depends = self.add_command(sp, 'show-cross-depends', self.do_show_cross_depends)
489 parser_show_cross_depends.add_argument('-f', '--filenames', help='show full file path', action='store_true')
490 parser_show_cross_depends.add_argument('-i', '--ignore', help='ignore dependencies on items in the specified layer(s) (split multiple layer names with commas, no spaces)', metavar='LAYERNAME')