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