blob: 7db49c8e2ab73cfc3baf2d5dfa3aadeebfd1b2f5 [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
Brad Bishopa34c0302019-09-23 22:34:48 -040049 items_listed = self.list_recipes('Overlayed recipes', None, True, args.same_version, args.filenames, False, True, None, False, None)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060050
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:'
Brad Bishopa34c0302019-09-23 22:34:48 -0400115 self.list_recipes(title, args.pnspec, False, False, args.filenames, args.recipes_only, args.multiple, args.layer, args.bare, inheritlist)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600116
Brad Bishopa34c0302019-09-23 22:34:48 -0400117 def list_recipes(self, title, pnspec, show_overlayed_only, show_same_ver_only, show_filenames, show_recipes_only, show_multi_provider_only, selected_layer, bare, inherits):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600118 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):
Brad Bishopa34c0302019-09-23 22:34:48 -0400147 if not selected_layer or layer == selected_layer:
148 if not bare and f in skiplist:
149 skipped = ' (skipped)'
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600150 else:
Brad Bishopa34c0302019-09-23 22:34:48 -0400151 skipped = ''
152 if show_filenames:
153 if ispref:
154 logger.plain("%s%s", f, skipped)
155 else:
156 logger.plain(" %s%s", f, skipped)
157 elif show_recipes_only:
158 if pn not in show_unique_pn:
159 show_unique_pn.append(pn)
160 logger.plain("%s%s", pn, skipped)
161 else:
162 if ispref:
163 logger.plain("%s:", pn)
164 logger.plain(" %s %s%s", layer.ljust(20), ver, skipped)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600165
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500166 global_inherit = (self.tinfoil.config_data.getVar('INHERIT') or "").split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600167 cls_re = re.compile('classes/')
168
169 preffiles = []
Brad Bishopa34c0302019-09-23 22:34:48 -0400170 show_unique_pn = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600171 items_listed = False
172 for p in sorted(pkg_pn):
173 if pnspec:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400174 found=False
175 for pnm in pnspec:
176 if fnmatch.fnmatch(p, pnm):
177 found=True
178 break
179 if not found:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600180 continue
181
182 if len(allproviders[p]) > 1 or not show_multi_provider_only:
183 pref = preferred_versions[p]
184 realfn = bb.cache.virtualfn2realfn(pref[1])
185 preffile = realfn[0]
186
187 # We only display once per recipe, we should prefer non extended versions of the
188 # recipe if present (so e.g. in OpenEmbedded, openssl rather than nativesdk-openssl
189 # which would otherwise sort first).
190 if realfn[1] and realfn[0] in self.tinfoil.cooker.recipecaches[''].pkg_fn:
191 continue
192
193 if inherits:
194 matchcount = 0
195 recipe_inherits = self.tinfoil.cooker_data.inherits.get(preffile, [])
196 for cls in recipe_inherits:
197 if cls_re.match(cls):
198 continue
199 classname = os.path.splitext(os.path.basename(cls))[0]
200 if classname in global_inherit:
201 continue
202 elif classname in inherits:
203 matchcount += 1
204 if matchcount != len(inherits):
205 # No match - skip this recipe
206 continue
207
208 if preffile not in preffiles:
209 preflayer = self.get_file_layer(preffile)
210 multilayer = False
211 same_ver = True
212 provs = []
213 for prov in allproviders[p]:
214 provfile = bb.cache.virtualfn2realfn(prov[1])[0]
215 provlayer = self.get_file_layer(provfile)
216 provs.append((provfile, provlayer, prov[0]))
217 if provlayer != preflayer:
218 multilayer = True
219 if prov[0] != pref[0]:
220 same_ver = False
221
222 if (multilayer or not show_overlayed_only) and (same_ver or not show_same_ver_only):
223 if not items_listed:
224 logger.plain('=== %s ===' % title)
225 items_listed = True
226 print_item(preffile, p, self.version_str(pref[0][0], pref[0][1]), preflayer, True)
227 for (provfile, provlayer, provver) in provs:
228 if provfile != preffile:
229 print_item(provfile, p, self.version_str(provver[0], provver[1]), provlayer, False)
230 # Ensure we don't show two entries for BBCLASSEXTENDed recipes
231 preffiles.append(preffile)
232
233 return items_listed
234
235 def get_file_layer(self, filename):
236 layerdir = self.get_file_layerdir(filename)
237 if layerdir:
238 return self.get_layer_name(layerdir)
239 else:
240 return '?'
241
242 def get_file_layerdir(self, filename):
243 layer = bb.utils.get_file_layer(filename, self.tinfoil.config_data)
244 return self.bbfile_collections.get(layer, None)
245
246 def remove_layer_prefix(self, f):
247 """Remove the layer_dir prefix, e.g., f = /path/to/layer_dir/foo/blah, the
248 return value will be: layer_dir/foo/blah"""
249 f_layerdir = self.get_file_layerdir(f)
250 if not f_layerdir:
251 return f
252 prefix = os.path.join(os.path.dirname(f_layerdir), '')
253 return f[len(prefix):] if f.startswith(prefix) else f
254
255 def do_show_appends(self, args):
256 """list bbappend files and recipe files they apply to
257
258Lists recipes with the bbappends that apply to them as subitems.
259"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500260 if args.pnspec:
261 logger.plain('=== Matched appended recipes ===')
262 else:
263 logger.plain('=== Appended recipes ===')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600264
265 pnlist = list(self.tinfoil.cooker_data.pkg_pn.keys())
266 pnlist.sort()
267 appends = False
268 for pn in pnlist:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400269 if args.pnspec:
270 found=False
271 for pnm in args.pnspec:
272 if fnmatch.fnmatch(pn, pnm):
273 found=True
274 break
275 if not found:
276 continue
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500277
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600278 if self.show_appends_for_pn(pn):
279 appends = True
280
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500281 if not args.pnspec and self.show_appends_for_skipped():
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600282 appends = True
283
284 if not appends:
285 logger.plain('No append files found')
286
287 def show_appends_for_pn(self, pn):
288 filenames = self.tinfoil.cooker_data.pkg_pn[pn]
289
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500290 best = self.tinfoil.find_best_provider(pn)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600291 best_filename = os.path.basename(best[3])
292
293 return self.show_appends_output(filenames, best_filename)
294
295 def show_appends_for_skipped(self):
296 filenames = [os.path.basename(f)
297 for f in self.tinfoil.cooker.skiplist.keys()]
298 return self.show_appends_output(filenames, None, " (skipped)")
299
300 def show_appends_output(self, filenames, best_filename, name_suffix = ''):
301 appended, missing = self.get_appends_for_files(filenames)
302 if appended:
303 for basename, appends in appended:
304 logger.plain('%s%s:', basename, name_suffix)
305 for append in appends:
306 logger.plain(' %s', append)
307
308 if best_filename:
309 if best_filename in missing:
310 logger.warning('%s: missing append for preferred version',
311 best_filename)
312 return True
313 else:
314 return False
315
316 def get_appends_for_files(self, filenames):
317 appended, notappended = [], []
318 for filename in filenames:
319 _, cls, _ = bb.cache.virtualfn2realfn(filename)
320 if cls:
321 continue
322
323 basename = os.path.basename(filename)
324 appends = self.tinfoil.cooker.collection.get_file_appends(basename)
325 if appends:
326 appended.append((basename, list(appends)))
327 else:
328 notappended.append(basename)
329 return appended, notappended
330
331 def do_show_cross_depends(self, args):
332 """Show dependencies between recipes that cross layer boundaries.
333
334Figure out the dependencies between recipes that cross layer boundaries.
335
336NOTE: .bbappend files can impact the dependencies.
337"""
338 ignore_layers = (args.ignore or '').split(',')
339
340 pkg_fn = self.tinfoil.cooker_data.pkg_fn
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500341 bbpath = str(self.tinfoil.config_data.getVar('BBPATH'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600342 self.require_re = re.compile(r"require\s+(.+)")
343 self.include_re = re.compile(r"include\s+(.+)")
344 self.inherit_re = re.compile(r"inherit\s+(.+)")
345
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500346 global_inherit = (self.tinfoil.config_data.getVar('INHERIT') or "").split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600347
348 # The bb's DEPENDS and RDEPENDS
349 for f in pkg_fn:
350 f = bb.cache.virtualfn2realfn(f)[0]
351 # Get the layername that the file is in
352 layername = self.get_file_layer(f)
353
354 # The DEPENDS
355 deps = self.tinfoil.cooker_data.deps[f]
356 for pn in deps:
357 if pn in self.tinfoil.cooker_data.pkg_pn:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500358 best = self.tinfoil.find_best_provider(pn)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600359 self.check_cross_depends("DEPENDS", layername, f, best[3], args.filenames, ignore_layers)
360
361 # The RDPENDS
362 all_rdeps = self.tinfoil.cooker_data.rundeps[f].values()
363 # Remove the duplicated or null one.
364 sorted_rdeps = {}
365 # The all_rdeps is the list in list, so we need two for loops
366 for k1 in all_rdeps:
367 for k2 in k1:
368 sorted_rdeps[k2] = 1
369 all_rdeps = sorted_rdeps.keys()
370 for rdep in all_rdeps:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500371 all_p, best = self.tinfoil.get_runtime_providers(rdep)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600372 if all_p:
373 if f in all_p:
374 # The recipe provides this one itself, ignore
375 continue
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600376 self.check_cross_depends("RDEPENDS", layername, f, best, args.filenames, ignore_layers)
377
378 # The RRECOMMENDS
379 all_rrecs = self.tinfoil.cooker_data.runrecs[f].values()
380 # Remove the duplicated or null one.
381 sorted_rrecs = {}
382 # The all_rrecs is the list in list, so we need two for loops
383 for k1 in all_rrecs:
384 for k2 in k1:
385 sorted_rrecs[k2] = 1
386 all_rrecs = sorted_rrecs.keys()
387 for rrec in all_rrecs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500388 all_p, best = self.tinfoil.get_runtime_providers(rrec)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600389 if all_p:
390 if f in all_p:
391 # The recipe provides this one itself, ignore
392 continue
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600393 self.check_cross_depends("RRECOMMENDS", layername, f, best, args.filenames, ignore_layers)
394
395 # The inherit class
396 cls_re = re.compile('classes/')
397 if f in self.tinfoil.cooker_data.inherits:
398 inherits = self.tinfoil.cooker_data.inherits[f]
399 for cls in inherits:
400 # The inherits' format is [classes/cls, /path/to/classes/cls]
401 # ignore the classes/cls.
402 if not cls_re.match(cls):
403 classname = os.path.splitext(os.path.basename(cls))[0]
404 if classname in global_inherit:
405 continue
406 inherit_layername = self.get_file_layer(cls)
407 if inherit_layername != layername and not inherit_layername in ignore_layers:
408 if not args.filenames:
409 f_short = self.remove_layer_prefix(f)
410 cls = self.remove_layer_prefix(cls)
411 else:
412 f_short = f
413 logger.plain("%s inherits %s" % (f_short, cls))
414
415 # The 'require/include xxx' in the bb file
416 pv_re = re.compile(r"\${PV}")
417 with open(f, 'r') as fnfile:
418 line = fnfile.readline()
419 while line:
420 m, keyword = self.match_require_include(line)
421 # Found the 'require/include xxxx'
422 if m:
423 needed_file = m.group(1)
424 # Replace the ${PV} with the real PV
425 if pv_re.search(needed_file) and f in self.tinfoil.cooker_data.pkg_pepvpr:
426 pv = self.tinfoil.cooker_data.pkg_pepvpr[f][1]
427 needed_file = re.sub(r"\${PV}", pv, needed_file)
428 self.print_cross_files(bbpath, keyword, layername, f, needed_file, args.filenames, ignore_layers)
429 line = fnfile.readline()
430
431 # The "require/include xxx" in conf/machine/*.conf, .inc and .bbclass
432 conf_re = re.compile(".*/conf/machine/[^\/]*\.conf$")
433 inc_re = re.compile(".*\.inc$")
434 # The "inherit xxx" in .bbclass
435 bbclass_re = re.compile(".*\.bbclass$")
436 for layerdir in self.bblayers:
437 layername = self.get_layer_name(layerdir)
438 for dirpath, dirnames, filenames in os.walk(layerdir):
439 for name in filenames:
440 f = os.path.join(dirpath, name)
441 s = conf_re.match(f) or inc_re.match(f) or bbclass_re.match(f)
442 if s:
443 with open(f, 'r') as ffile:
444 line = ffile.readline()
445 while line:
446 m, keyword = self.match_require_include(line)
447 # Only bbclass has the "inherit xxx" here.
448 bbclass=""
449 if not m and f.endswith(".bbclass"):
450 m, keyword = self.match_inherit(line)
451 bbclass=".bbclass"
452 # Find a 'require/include xxxx'
453 if m:
454 self.print_cross_files(bbpath, keyword, layername, f, m.group(1) + bbclass, args.filenames, ignore_layers)
455 line = ffile.readline()
456
457 def print_cross_files(self, bbpath, keyword, layername, f, needed_filename, show_filenames, ignore_layers):
458 """Print the depends that crosses a layer boundary"""
459 needed_file = bb.utils.which(bbpath, needed_filename)
460 if needed_file:
461 # Which layer is this file from
462 needed_layername = self.get_file_layer(needed_file)
463 if needed_layername != layername and not needed_layername in ignore_layers:
464 if not show_filenames:
465 f = self.remove_layer_prefix(f)
466 needed_file = self.remove_layer_prefix(needed_file)
467 logger.plain("%s %s %s" %(f, keyword, needed_file))
468
469 def match_inherit(self, line):
470 """Match the inherit xxx line"""
471 return (self.inherit_re.match(line), "inherits")
472
473 def match_require_include(self, line):
474 """Match the require/include xxx line"""
475 m = self.require_re.match(line)
476 keyword = "requires"
477 if not m:
478 m = self.include_re.match(line)
479 keyword = "includes"
480 return (m, keyword)
481
482 def check_cross_depends(self, keyword, layername, f, needed_file, show_filenames, ignore_layers):
483 """Print the DEPENDS/RDEPENDS file that crosses a layer boundary"""
484 best_realfn = bb.cache.virtualfn2realfn(needed_file)[0]
485 needed_layername = self.get_file_layer(best_realfn)
486 if needed_layername != layername and not needed_layername in ignore_layers:
487 if not show_filenames:
488 f = self.remove_layer_prefix(f)
489 best_realfn = self.remove_layer_prefix(best_realfn)
490
491 logger.plain("%s %s %s" % (f, keyword, best_realfn))
492
493 def register_commands(self, sp):
494 self.add_command(sp, 'show-layers', self.do_show_layers, parserecipes=False)
495
496 parser_show_overlayed = self.add_command(sp, 'show-overlayed', self.do_show_overlayed)
497 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')
498 parser_show_overlayed.add_argument('-s', '--same-version', help='only list overlayed recipes where the version is the same', action='store_true')
499
500 parser_show_recipes = self.add_command(sp, 'show-recipes', self.do_show_recipes)
501 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')
Brad Bishopa34c0302019-09-23 22:34:48 -0400502 parser_show_recipes.add_argument('-r', '--recipes-only', help='instead of the default formatting, list recipes only', action='store_true')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600503 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 -0400504 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='')
Brad Bishopa34c0302019-09-23 22:34:48 -0400505 parser_show_recipes.add_argument('-l', '--layer', help='only list recipes from the selected layer', default='')
506 parser_show_recipes.add_argument('-b', '--bare', help='output just names without the "(skipped)" marker', action='store_true')
Brad Bishop316dfdd2018-06-25 12:45:53 -0400507 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 -0600508
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500509 parser_show_appends = self.add_command(sp, 'show-appends', self.do_show_appends)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400510 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 -0600511
512 parser_show_cross_depends = self.add_command(sp, 'show-cross-depends', self.do_show_cross_depends)
513 parser_show_cross_depends.add_argument('-f', '--filenames', help='show full file path', action='store_true')
514 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')