blob: 9294dfa8840e8bc1ac79d4c45584d07bce80a6e5 [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:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400164 found=False
165 for pnm in pnspec:
166 if fnmatch.fnmatch(p, pnm):
167 found=True
168 break
169 if not found:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600170 continue
171
172 if len(allproviders[p]) > 1 or not show_multi_provider_only:
173 pref = preferred_versions[p]
174 realfn = bb.cache.virtualfn2realfn(pref[1])
175 preffile = realfn[0]
176
177 # We only display once per recipe, we should prefer non extended versions of the
178 # recipe if present (so e.g. in OpenEmbedded, openssl rather than nativesdk-openssl
179 # which would otherwise sort first).
180 if realfn[1] and realfn[0] in self.tinfoil.cooker.recipecaches[''].pkg_fn:
181 continue
182
183 if inherits:
184 matchcount = 0
185 recipe_inherits = self.tinfoil.cooker_data.inherits.get(preffile, [])
186 for cls in recipe_inherits:
187 if cls_re.match(cls):
188 continue
189 classname = os.path.splitext(os.path.basename(cls))[0]
190 if classname in global_inherit:
191 continue
192 elif classname in inherits:
193 matchcount += 1
194 if matchcount != len(inherits):
195 # No match - skip this recipe
196 continue
197
198 if preffile not in preffiles:
199 preflayer = self.get_file_layer(preffile)
200 multilayer = False
201 same_ver = True
202 provs = []
203 for prov in allproviders[p]:
204 provfile = bb.cache.virtualfn2realfn(prov[1])[0]
205 provlayer = self.get_file_layer(provfile)
206 provs.append((provfile, provlayer, prov[0]))
207 if provlayer != preflayer:
208 multilayer = True
209 if prov[0] != pref[0]:
210 same_ver = False
211
212 if (multilayer or not show_overlayed_only) and (same_ver or not show_same_ver_only):
213 if not items_listed:
214 logger.plain('=== %s ===' % title)
215 items_listed = True
216 print_item(preffile, p, self.version_str(pref[0][0], pref[0][1]), preflayer, True)
217 for (provfile, provlayer, provver) in provs:
218 if provfile != preffile:
219 print_item(provfile, p, self.version_str(provver[0], provver[1]), provlayer, False)
220 # Ensure we don't show two entries for BBCLASSEXTENDed recipes
221 preffiles.append(preffile)
222
223 return items_listed
224
225 def get_file_layer(self, filename):
226 layerdir = self.get_file_layerdir(filename)
227 if layerdir:
228 return self.get_layer_name(layerdir)
229 else:
230 return '?'
231
232 def get_file_layerdir(self, filename):
233 layer = bb.utils.get_file_layer(filename, self.tinfoil.config_data)
234 return self.bbfile_collections.get(layer, None)
235
236 def remove_layer_prefix(self, f):
237 """Remove the layer_dir prefix, e.g., f = /path/to/layer_dir/foo/blah, the
238 return value will be: layer_dir/foo/blah"""
239 f_layerdir = self.get_file_layerdir(f)
240 if not f_layerdir:
241 return f
242 prefix = os.path.join(os.path.dirname(f_layerdir), '')
243 return f[len(prefix):] if f.startswith(prefix) else f
244
245 def do_show_appends(self, args):
246 """list bbappend files and recipe files they apply to
247
248Lists recipes with the bbappends that apply to them as subitems.
249"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500250 if args.pnspec:
251 logger.plain('=== Matched appended recipes ===')
252 else:
253 logger.plain('=== Appended recipes ===')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600254
255 pnlist = list(self.tinfoil.cooker_data.pkg_pn.keys())
256 pnlist.sort()
257 appends = False
258 for pn in pnlist:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400259 if args.pnspec:
260 found=False
261 for pnm in args.pnspec:
262 if fnmatch.fnmatch(pn, pnm):
263 found=True
264 break
265 if not found:
266 continue
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500267
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600268 if self.show_appends_for_pn(pn):
269 appends = True
270
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500271 if not args.pnspec and self.show_appends_for_skipped():
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600272 appends = True
273
274 if not appends:
275 logger.plain('No append files found')
276
277 def show_appends_for_pn(self, pn):
278 filenames = self.tinfoil.cooker_data.pkg_pn[pn]
279
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500280 best = self.tinfoil.find_best_provider(pn)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600281 best_filename = os.path.basename(best[3])
282
283 return self.show_appends_output(filenames, best_filename)
284
285 def show_appends_for_skipped(self):
286 filenames = [os.path.basename(f)
287 for f in self.tinfoil.cooker.skiplist.keys()]
288 return self.show_appends_output(filenames, None, " (skipped)")
289
290 def show_appends_output(self, filenames, best_filename, name_suffix = ''):
291 appended, missing = self.get_appends_for_files(filenames)
292 if appended:
293 for basename, appends in appended:
294 logger.plain('%s%s:', basename, name_suffix)
295 for append in appends:
296 logger.plain(' %s', append)
297
298 if best_filename:
299 if best_filename in missing:
300 logger.warning('%s: missing append for preferred version',
301 best_filename)
302 return True
303 else:
304 return False
305
306 def get_appends_for_files(self, filenames):
307 appended, notappended = [], []
308 for filename in filenames:
309 _, cls, _ = bb.cache.virtualfn2realfn(filename)
310 if cls:
311 continue
312
313 basename = os.path.basename(filename)
314 appends = self.tinfoil.cooker.collection.get_file_appends(basename)
315 if appends:
316 appended.append((basename, list(appends)))
317 else:
318 notappended.append(basename)
319 return appended, notappended
320
321 def do_show_cross_depends(self, args):
322 """Show dependencies between recipes that cross layer boundaries.
323
324Figure out the dependencies between recipes that cross layer boundaries.
325
326NOTE: .bbappend files can impact the dependencies.
327"""
328 ignore_layers = (args.ignore or '').split(',')
329
330 pkg_fn = self.tinfoil.cooker_data.pkg_fn
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500331 bbpath = str(self.tinfoil.config_data.getVar('BBPATH'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600332 self.require_re = re.compile(r"require\s+(.+)")
333 self.include_re = re.compile(r"include\s+(.+)")
334 self.inherit_re = re.compile(r"inherit\s+(.+)")
335
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500336 global_inherit = (self.tinfoil.config_data.getVar('INHERIT') or "").split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600337
338 # The bb's DEPENDS and RDEPENDS
339 for f in pkg_fn:
340 f = bb.cache.virtualfn2realfn(f)[0]
341 # Get the layername that the file is in
342 layername = self.get_file_layer(f)
343
344 # The DEPENDS
345 deps = self.tinfoil.cooker_data.deps[f]
346 for pn in deps:
347 if pn in self.tinfoil.cooker_data.pkg_pn:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500348 best = self.tinfoil.find_best_provider(pn)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600349 self.check_cross_depends("DEPENDS", layername, f, best[3], args.filenames, ignore_layers)
350
351 # The RDPENDS
352 all_rdeps = self.tinfoil.cooker_data.rundeps[f].values()
353 # Remove the duplicated or null one.
354 sorted_rdeps = {}
355 # The all_rdeps is the list in list, so we need two for loops
356 for k1 in all_rdeps:
357 for k2 in k1:
358 sorted_rdeps[k2] = 1
359 all_rdeps = sorted_rdeps.keys()
360 for rdep in all_rdeps:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500361 all_p, best = self.tinfoil.get_runtime_providers(rdep)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600362 if all_p:
363 if f in all_p:
364 # The recipe provides this one itself, ignore
365 continue
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600366 self.check_cross_depends("RDEPENDS", layername, f, best, args.filenames, ignore_layers)
367
368 # The RRECOMMENDS
369 all_rrecs = self.tinfoil.cooker_data.runrecs[f].values()
370 # Remove the duplicated or null one.
371 sorted_rrecs = {}
372 # The all_rrecs is the list in list, so we need two for loops
373 for k1 in all_rrecs:
374 for k2 in k1:
375 sorted_rrecs[k2] = 1
376 all_rrecs = sorted_rrecs.keys()
377 for rrec in all_rrecs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500378 all_p, best = self.tinfoil.get_runtime_providers(rrec)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600379 if all_p:
380 if f in all_p:
381 # The recipe provides this one itself, ignore
382 continue
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600383 self.check_cross_depends("RRECOMMENDS", layername, f, best, args.filenames, ignore_layers)
384
385 # The inherit class
386 cls_re = re.compile('classes/')
387 if f in self.tinfoil.cooker_data.inherits:
388 inherits = self.tinfoil.cooker_data.inherits[f]
389 for cls in inherits:
390 # The inherits' format is [classes/cls, /path/to/classes/cls]
391 # ignore the classes/cls.
392 if not cls_re.match(cls):
393 classname = os.path.splitext(os.path.basename(cls))[0]
394 if classname in global_inherit:
395 continue
396 inherit_layername = self.get_file_layer(cls)
397 if inherit_layername != layername and not inherit_layername in ignore_layers:
398 if not args.filenames:
399 f_short = self.remove_layer_prefix(f)
400 cls = self.remove_layer_prefix(cls)
401 else:
402 f_short = f
403 logger.plain("%s inherits %s" % (f_short, cls))
404
405 # The 'require/include xxx' in the bb file
406 pv_re = re.compile(r"\${PV}")
407 with open(f, 'r') as fnfile:
408 line = fnfile.readline()
409 while line:
410 m, keyword = self.match_require_include(line)
411 # Found the 'require/include xxxx'
412 if m:
413 needed_file = m.group(1)
414 # Replace the ${PV} with the real PV
415 if pv_re.search(needed_file) and f in self.tinfoil.cooker_data.pkg_pepvpr:
416 pv = self.tinfoil.cooker_data.pkg_pepvpr[f][1]
417 needed_file = re.sub(r"\${PV}", pv, needed_file)
418 self.print_cross_files(bbpath, keyword, layername, f, needed_file, args.filenames, ignore_layers)
419 line = fnfile.readline()
420
421 # The "require/include xxx" in conf/machine/*.conf, .inc and .bbclass
422 conf_re = re.compile(".*/conf/machine/[^\/]*\.conf$")
423 inc_re = re.compile(".*\.inc$")
424 # The "inherit xxx" in .bbclass
425 bbclass_re = re.compile(".*\.bbclass$")
426 for layerdir in self.bblayers:
427 layername = self.get_layer_name(layerdir)
428 for dirpath, dirnames, filenames in os.walk(layerdir):
429 for name in filenames:
430 f = os.path.join(dirpath, name)
431 s = conf_re.match(f) or inc_re.match(f) or bbclass_re.match(f)
432 if s:
433 with open(f, 'r') as ffile:
434 line = ffile.readline()
435 while line:
436 m, keyword = self.match_require_include(line)
437 # Only bbclass has the "inherit xxx" here.
438 bbclass=""
439 if not m and f.endswith(".bbclass"):
440 m, keyword = self.match_inherit(line)
441 bbclass=".bbclass"
442 # Find a 'require/include xxxx'
443 if m:
444 self.print_cross_files(bbpath, keyword, layername, f, m.group(1) + bbclass, args.filenames, ignore_layers)
445 line = ffile.readline()
446
447 def print_cross_files(self, bbpath, keyword, layername, f, needed_filename, show_filenames, ignore_layers):
448 """Print the depends that crosses a layer boundary"""
449 needed_file = bb.utils.which(bbpath, needed_filename)
450 if needed_file:
451 # Which layer is this file from
452 needed_layername = self.get_file_layer(needed_file)
453 if needed_layername != layername and not needed_layername in ignore_layers:
454 if not show_filenames:
455 f = self.remove_layer_prefix(f)
456 needed_file = self.remove_layer_prefix(needed_file)
457 logger.plain("%s %s %s" %(f, keyword, needed_file))
458
459 def match_inherit(self, line):
460 """Match the inherit xxx line"""
461 return (self.inherit_re.match(line), "inherits")
462
463 def match_require_include(self, line):
464 """Match the require/include xxx line"""
465 m = self.require_re.match(line)
466 keyword = "requires"
467 if not m:
468 m = self.include_re.match(line)
469 keyword = "includes"
470 return (m, keyword)
471
472 def check_cross_depends(self, keyword, layername, f, needed_file, show_filenames, ignore_layers):
473 """Print the DEPENDS/RDEPENDS file that crosses a layer boundary"""
474 best_realfn = bb.cache.virtualfn2realfn(needed_file)[0]
475 needed_layername = self.get_file_layer(best_realfn)
476 if needed_layername != layername and not needed_layername in ignore_layers:
477 if not show_filenames:
478 f = self.remove_layer_prefix(f)
479 best_realfn = self.remove_layer_prefix(best_realfn)
480
481 logger.plain("%s %s %s" % (f, keyword, best_realfn))
482
483 def register_commands(self, sp):
484 self.add_command(sp, 'show-layers', self.do_show_layers, parserecipes=False)
485
486 parser_show_overlayed = self.add_command(sp, 'show-overlayed', self.do_show_overlayed)
487 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')
488 parser_show_overlayed.add_argument('-s', '--same-version', help='only list overlayed recipes where the version is the same', action='store_true')
489
490 parser_show_recipes = self.add_command(sp, 'show-recipes', self.do_show_recipes)
491 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')
492 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 -0400493 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='')
494 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 -0600495
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500496 parser_show_appends = self.add_command(sp, 'show-appends', self.do_show_appends)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400497 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 -0600498
499 parser_show_cross_depends = self.add_command(sp, 'show-cross-depends', self.do_show_cross_depends)
500 parser_show_cross_depends.add_argument('-f', '--filenames', help='show full file path', action='store_true')
501 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')