diff options
Diffstat (limited to 'bitbake/bin/bitbake-layers')
-rwxr-xr-x | bitbake/bin/bitbake-layers | 720 |
1 files changed, 720 insertions, 0 deletions
diff --git a/bitbake/bin/bitbake-layers b/bitbake/bin/bitbake-layers new file mode 100755 index 0000000000..047583c497 --- /dev/null +++ b/bitbake/bin/bitbake-layers | |||
@@ -0,0 +1,720 @@ | |||
1 | #!/usr/bin/env python | ||
2 | |||
3 | # This script has subcommands which operate against your bitbake layers, either | ||
4 | # displaying useful information, or acting against them. | ||
5 | # See the help output for details on available commands. | ||
6 | |||
7 | # Copyright (C) 2011 Mentor Graphics Corporation | ||
8 | # Copyright (C) 2012 Intel Corporation | ||
9 | # | ||
10 | # This program is free software; you can redistribute it and/or modify | ||
11 | # it under the terms of the GNU General Public License version 2 as | ||
12 | # published by the Free Software Foundation. | ||
13 | # | ||
14 | # This program is distributed in the hope that it will be useful, | ||
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
17 | # GNU General Public License for more details. | ||
18 | # | ||
19 | # You should have received a copy of the GNU General Public License along | ||
20 | # with this program; if not, write to the Free Software Foundation, Inc., | ||
21 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
22 | |||
23 | import cmd | ||
24 | import logging | ||
25 | import os | ||
26 | import sys | ||
27 | import fnmatch | ||
28 | from collections import defaultdict | ||
29 | import re | ||
30 | |||
31 | bindir = os.path.dirname(__file__) | ||
32 | topdir = os.path.dirname(bindir) | ||
33 | sys.path[0:0] = [os.path.join(topdir, 'lib')] | ||
34 | |||
35 | import bb.cache | ||
36 | import bb.cooker | ||
37 | import bb.providers | ||
38 | import bb.utils | ||
39 | import bb.tinfoil | ||
40 | |||
41 | |||
42 | logger = logging.getLogger('BitBake') | ||
43 | |||
44 | |||
45 | def main(args): | ||
46 | cmds = Commands() | ||
47 | if args: | ||
48 | # Allow user to specify e.g. show-layers instead of show_layers | ||
49 | args = [args[0].replace('-', '_')] + args[1:] | ||
50 | cmds.onecmd(' '.join(args)) | ||
51 | else: | ||
52 | cmds.do_help('') | ||
53 | return cmds.returncode | ||
54 | |||
55 | |||
56 | class Commands(cmd.Cmd): | ||
57 | def __init__(self): | ||
58 | cmd.Cmd.__init__(self) | ||
59 | self.bbhandler = bb.tinfoil.Tinfoil() | ||
60 | self.returncode = 0 | ||
61 | self.bblayers = (self.bbhandler.config_data.getVar('BBLAYERS', True) or "").split() | ||
62 | |||
63 | def default(self, line): | ||
64 | """Handle unrecognised commands""" | ||
65 | sys.stderr.write("Unrecognised command or option\n") | ||
66 | self.do_help('') | ||
67 | |||
68 | def do_help(self, topic): | ||
69 | """display general help or help on a specified command""" | ||
70 | if topic: | ||
71 | sys.stdout.write('%s: ' % topic) | ||
72 | cmd.Cmd.do_help(self, topic.replace('-', '_')) | ||
73 | else: | ||
74 | sys.stdout.write("usage: bitbake-layers <command> [arguments]\n\n") | ||
75 | sys.stdout.write("Available commands:\n") | ||
76 | procnames = list(set(self.get_names())) | ||
77 | for procname in procnames: | ||
78 | if procname[:3] == 'do_': | ||
79 | sys.stdout.write(" %s\n" % procname[3:].replace('_', '-')) | ||
80 | doc = getattr(self, procname).__doc__ | ||
81 | if doc: | ||
82 | sys.stdout.write(" %s\n" % doc.splitlines()[0]) | ||
83 | |||
84 | def do_show_layers(self, args): | ||
85 | """show current configured layers""" | ||
86 | self.bbhandler.prepare(config_only = True) | ||
87 | logger.plain("%s %s %s" % ("layer".ljust(20), "path".ljust(40), "priority")) | ||
88 | logger.plain('=' * 74) | ||
89 | for layerdir in self.bblayers: | ||
90 | layername = self.get_layer_name(layerdir) | ||
91 | layerpri = 0 | ||
92 | for layer, _, regex, pri in self.bbhandler.cooker.recipecache.bbfile_config_priorities: | ||
93 | if regex.match(os.path.join(layerdir, 'test')): | ||
94 | layerpri = pri | ||
95 | break | ||
96 | |||
97 | logger.plain("%s %s %d" % (layername.ljust(20), layerdir.ljust(40), layerpri)) | ||
98 | |||
99 | |||
100 | def version_str(self, pe, pv, pr = None): | ||
101 | verstr = "%s" % pv | ||
102 | if pr: | ||
103 | verstr = "%s-%s" % (verstr, pr) | ||
104 | if pe: | ||
105 | verstr = "%s:%s" % (pe, verstr) | ||
106 | return verstr | ||
107 | |||
108 | |||
109 | def do_show_overlayed(self, args): | ||
110 | """list overlayed recipes (where the same recipe exists in another layer) | ||
111 | |||
112 | usage: show-overlayed [-f] [-s] | ||
113 | |||
114 | Lists the names of overlayed recipes and the available versions in each | ||
115 | layer, with the preferred version first. Note that skipped recipes that | ||
116 | are overlayed will also be listed, with a " (skipped)" suffix. | ||
117 | |||
118 | Options: | ||
119 | -f instead of the default formatting, list filenames of higher priority | ||
120 | recipes with the ones they overlay indented underneath | ||
121 | -s only list overlayed recipes where the version is the same | ||
122 | """ | ||
123 | self.bbhandler.prepare() | ||
124 | |||
125 | show_filenames = False | ||
126 | show_same_ver_only = False | ||
127 | for arg in args.split(): | ||
128 | if arg == '-f': | ||
129 | show_filenames = True | ||
130 | elif arg == '-s': | ||
131 | show_same_ver_only = True | ||
132 | else: | ||
133 | sys.stderr.write("show-overlayed: invalid option %s\n" % arg) | ||
134 | self.do_help('') | ||
135 | return | ||
136 | |||
137 | items_listed = self.list_recipes('Overlayed recipes', None, True, show_same_ver_only, show_filenames, True) | ||
138 | |||
139 | # Check for overlayed .bbclass files | ||
140 | classes = defaultdict(list) | ||
141 | for layerdir in self.bblayers: | ||
142 | classdir = os.path.join(layerdir, 'classes') | ||
143 | if os.path.exists(classdir): | ||
144 | for classfile in os.listdir(classdir): | ||
145 | if os.path.splitext(classfile)[1] == '.bbclass': | ||
146 | classes[classfile].append(classdir) | ||
147 | |||
148 | # Locating classes and other files is a bit more complicated than recipes - | ||
149 | # layer priority is not a factor; instead BitBake uses the first matching | ||
150 | # file in BBPATH, which is manipulated directly by each layer's | ||
151 | # conf/layer.conf in turn, thus the order of layers in bblayers.conf is a | ||
152 | # factor - however, each layer.conf is free to either prepend or append to | ||
153 | # BBPATH (or indeed do crazy stuff with it). Thus the order in BBPATH might | ||
154 | # not be exactly the order present in bblayers.conf either. | ||
155 | bbpath = str(self.bbhandler.config_data.getVar('BBPATH', True)) | ||
156 | overlayed_class_found = False | ||
157 | for (classfile, classdirs) in classes.items(): | ||
158 | if len(classdirs) > 1: | ||
159 | if not overlayed_class_found: | ||
160 | logger.plain('=== Overlayed classes ===') | ||
161 | overlayed_class_found = True | ||
162 | |||
163 | mainfile = bb.utils.which(bbpath, os.path.join('classes', classfile)) | ||
164 | if show_filenames: | ||
165 | logger.plain('%s' % mainfile) | ||
166 | else: | ||
167 | # We effectively have to guess the layer here | ||
168 | logger.plain('%s:' % classfile) | ||
169 | mainlayername = '?' | ||
170 | for layerdir in self.bblayers: | ||
171 | classdir = os.path.join(layerdir, 'classes') | ||
172 | if mainfile.startswith(classdir): | ||
173 | mainlayername = self.get_layer_name(layerdir) | ||
174 | logger.plain(' %s' % mainlayername) | ||
175 | for classdir in classdirs: | ||
176 | fullpath = os.path.join(classdir, classfile) | ||
177 | if fullpath != mainfile: | ||
178 | if show_filenames: | ||
179 | print(' %s' % fullpath) | ||
180 | else: | ||
181 | print(' %s' % self.get_layer_name(os.path.dirname(classdir))) | ||
182 | |||
183 | if overlayed_class_found: | ||
184 | items_listed = True; | ||
185 | |||
186 | if not items_listed: | ||
187 | logger.plain('No overlayed files found.') | ||
188 | |||
189 | |||
190 | def do_show_recipes(self, args): | ||
191 | """list available recipes, showing the layer they are provided by | ||
192 | |||
193 | usage: show-recipes [-f] [-m] [pnspec] | ||
194 | |||
195 | Lists the names of overlayed recipes and the available versions in each | ||
196 | layer, with the preferred version first. Optionally you may specify | ||
197 | pnspec to match a specified recipe name (supports wildcards). Note that | ||
198 | skipped recipes will also be listed, with a " (skipped)" suffix. | ||
199 | |||
200 | Options: | ||
201 | -f instead of the default formatting, list filenames of higher priority | ||
202 | recipes with other available recipes indented underneath | ||
203 | -m only list where multiple recipes (in the same layer or different | ||
204 | layers) exist for the same recipe name | ||
205 | """ | ||
206 | self.bbhandler.prepare() | ||
207 | |||
208 | show_filenames = False | ||
209 | show_multi_provider_only = False | ||
210 | pnspec = None | ||
211 | title = 'Available recipes:' | ||
212 | for arg in args.split(): | ||
213 | if arg == '-f': | ||
214 | show_filenames = True | ||
215 | elif arg == '-m': | ||
216 | show_multi_provider_only = True | ||
217 | elif not arg.startswith('-'): | ||
218 | pnspec = arg | ||
219 | title = 'Available recipes matching %s:' % pnspec | ||
220 | else: | ||
221 | sys.stderr.write("show-recipes: invalid option %s\n" % arg) | ||
222 | self.do_help('') | ||
223 | return | ||
224 | self.list_recipes(title, pnspec, False, False, show_filenames, show_multi_provider_only) | ||
225 | |||
226 | |||
227 | def list_recipes(self, title, pnspec, show_overlayed_only, show_same_ver_only, show_filenames, show_multi_provider_only): | ||
228 | pkg_pn = self.bbhandler.cooker.recipecache.pkg_pn | ||
229 | (latest_versions, preferred_versions) = bb.providers.findProviders(self.bbhandler.config_data, self.bbhandler.cooker.recipecache, pkg_pn) | ||
230 | allproviders = bb.providers.allProviders(self.bbhandler.cooker.recipecache) | ||
231 | |||
232 | # Ensure we list skipped recipes | ||
233 | # We are largely guessing about PN, PV and the preferred version here, | ||
234 | # but we have no choice since skipped recipes are not fully parsed | ||
235 | skiplist = self.bbhandler.cooker.skiplist.keys() | ||
236 | skiplist.sort( key=lambda fileitem: self.bbhandler.cooker.collection.calc_bbfile_priority(fileitem) ) | ||
237 | skiplist.reverse() | ||
238 | for fn in skiplist: | ||
239 | recipe_parts = os.path.splitext(os.path.basename(fn))[0].split('_') | ||
240 | p = recipe_parts[0] | ||
241 | if len(recipe_parts) > 1: | ||
242 | ver = (None, recipe_parts[1], None) | ||
243 | else: | ||
244 | ver = (None, 'unknown', None) | ||
245 | allproviders[p].append((ver, fn)) | ||
246 | if not p in pkg_pn: | ||
247 | pkg_pn[p] = 'dummy' | ||
248 | preferred_versions[p] = (ver, fn) | ||
249 | |||
250 | def print_item(f, pn, ver, layer, ispref): | ||
251 | if f in skiplist: | ||
252 | skipped = ' (skipped)' | ||
253 | else: | ||
254 | skipped = '' | ||
255 | if show_filenames: | ||
256 | if ispref: | ||
257 | logger.plain("%s%s", f, skipped) | ||
258 | else: | ||
259 | logger.plain(" %s%s", f, skipped) | ||
260 | else: | ||
261 | if ispref: | ||
262 | logger.plain("%s:", pn) | ||
263 | logger.plain(" %s %s%s", layer.ljust(20), ver, skipped) | ||
264 | |||
265 | preffiles = [] | ||
266 | items_listed = False | ||
267 | for p in sorted(pkg_pn): | ||
268 | if pnspec: | ||
269 | if not fnmatch.fnmatch(p, pnspec): | ||
270 | continue | ||
271 | |||
272 | if len(allproviders[p]) > 1 or not show_multi_provider_only: | ||
273 | pref = preferred_versions[p] | ||
274 | preffile = bb.cache.Cache.virtualfn2realfn(pref[1])[0] | ||
275 | if preffile not in preffiles: | ||
276 | preflayer = self.get_file_layer(preffile) | ||
277 | multilayer = False | ||
278 | same_ver = True | ||
279 | provs = [] | ||
280 | for prov in allproviders[p]: | ||
281 | provfile = bb.cache.Cache.virtualfn2realfn(prov[1])[0] | ||
282 | provlayer = self.get_file_layer(provfile) | ||
283 | provs.append((provfile, provlayer, prov[0])) | ||
284 | if provlayer != preflayer: | ||
285 | multilayer = True | ||
286 | if prov[0] != pref[0]: | ||
287 | same_ver = False | ||
288 | |||
289 | if (multilayer or not show_overlayed_only) and (same_ver or not show_same_ver_only): | ||
290 | if not items_listed: | ||
291 | logger.plain('=== %s ===' % title) | ||
292 | items_listed = True | ||
293 | print_item(preffile, p, self.version_str(pref[0][0], pref[0][1]), preflayer, True) | ||
294 | for (provfile, provlayer, provver) in provs: | ||
295 | if provfile != preffile: | ||
296 | print_item(provfile, p, self.version_str(provver[0], provver[1]), provlayer, False) | ||
297 | # Ensure we don't show two entries for BBCLASSEXTENDed recipes | ||
298 | preffiles.append(preffile) | ||
299 | |||
300 | return items_listed | ||
301 | |||
302 | |||
303 | def do_flatten(self, args): | ||
304 | """flattens layer configuration into a separate output directory. | ||
305 | |||
306 | usage: flatten [layer1 layer2 [layer3]...] <outputdir> | ||
307 | |||
308 | Takes the specified layers (or all layers in the current layer | ||
309 | configuration if none are specified) and builds a "flattened" directory | ||
310 | containing the contents of all layers, with any overlayed recipes removed | ||
311 | and bbappends appended to the corresponding recipes. Note that some manual | ||
312 | cleanup may still be necessary afterwards, in particular: | ||
313 | |||
314 | * where non-recipe files (such as patches) are overwritten (the flatten | ||
315 | command will show a warning for these) | ||
316 | * where anything beyond the normal layer setup has been added to | ||
317 | layer.conf (only the lowest priority number layer's layer.conf is used) | ||
318 | * overridden/appended items from bbappends will need to be tidied up | ||
319 | * when the flattened layers do not have the same directory structure (the | ||
320 | flatten command should show a warning when this will cause a problem) | ||
321 | |||
322 | Warning: if you flatten several layers where another layer is intended to | ||
323 | be used "inbetween" them (in layer priority order) such that recipes / | ||
324 | bbappends in the layers interact, and then attempt to use the new output | ||
325 | layer together with that other layer, you may no longer get the same | ||
326 | build results (as the layer priority order has effectively changed). | ||
327 | """ | ||
328 | arglist = args.split() | ||
329 | if len(arglist) < 1: | ||
330 | logger.error('Please specify an output directory') | ||
331 | self.do_help('flatten') | ||
332 | return | ||
333 | |||
334 | if len(arglist) == 2: | ||
335 | logger.error('If you specify layers to flatten you must specify at least two') | ||
336 | self.do_help('flatten') | ||
337 | return | ||
338 | |||
339 | outputdir = arglist[-1] | ||
340 | if os.path.exists(outputdir) and os.listdir(outputdir): | ||
341 | logger.error('Directory %s exists and is non-empty, please clear it out first' % outputdir) | ||
342 | return | ||
343 | |||
344 | self.bbhandler.prepare() | ||
345 | layers = self.bblayers | ||
346 | if len(arglist) > 2: | ||
347 | layernames = arglist[:-1] | ||
348 | found_layernames = [] | ||
349 | found_layerdirs = [] | ||
350 | for layerdir in layers: | ||
351 | layername = self.get_layer_name(layerdir) | ||
352 | if layername in layernames: | ||
353 | found_layerdirs.append(layerdir) | ||
354 | found_layernames.append(layername) | ||
355 | |||
356 | for layername in layernames: | ||
357 | if not layername in found_layernames: | ||
358 | logger.error('Unable to find layer %s in current configuration, please run "%s show-layers" to list configured layers' % (layername, os.path.basename(sys.argv[0]))) | ||
359 | return | ||
360 | layers = found_layerdirs | ||
361 | else: | ||
362 | layernames = [] | ||
363 | |||
364 | # Ensure a specified path matches our list of layers | ||
365 | def layer_path_match(path): | ||
366 | for layerdir in layers: | ||
367 | if path.startswith(os.path.join(layerdir, '')): | ||
368 | return layerdir | ||
369 | return None | ||
370 | |||
371 | appended_recipes = [] | ||
372 | for layer in layers: | ||
373 | overlayed = [] | ||
374 | for f in self.bbhandler.cooker.collection.overlayed.iterkeys(): | ||
375 | for of in self.bbhandler.cooker.collection.overlayed[f]: | ||
376 | if of.startswith(layer): | ||
377 | overlayed.append(of) | ||
378 | |||
379 | logger.plain('Copying files from %s...' % layer ) | ||
380 | for root, dirs, files in os.walk(layer): | ||
381 | for f1 in files: | ||
382 | f1full = os.sep.join([root, f1]) | ||
383 | if f1full in overlayed: | ||
384 | logger.plain(' Skipping overlayed file %s' % f1full ) | ||
385 | else: | ||
386 | ext = os.path.splitext(f1)[1] | ||
387 | if ext != '.bbappend': | ||
388 | fdest = f1full[len(layer):] | ||
389 | fdest = os.path.normpath(os.sep.join([outputdir,fdest])) | ||
390 | bb.utils.mkdirhier(os.path.dirname(fdest)) | ||
391 | if os.path.exists(fdest): | ||
392 | if f1 == 'layer.conf' and root.endswith('/conf'): | ||
393 | logger.plain(' Skipping layer config file %s' % f1full ) | ||
394 | continue | ||
395 | else: | ||
396 | logger.warn('Overwriting file %s', fdest) | ||
397 | bb.utils.copyfile(f1full, fdest) | ||
398 | if ext == '.bb': | ||
399 | if f1 in self.bbhandler.cooker.collection.appendlist: | ||
400 | appends = self.bbhandler.cooker.collection.appendlist[f1] | ||
401 | if appends: | ||
402 | logger.plain(' Applying appends to %s' % fdest ) | ||
403 | for appendname in appends: | ||
404 | if layer_path_match(appendname): | ||
405 | self.apply_append(appendname, fdest) | ||
406 | appended_recipes.append(f1) | ||
407 | |||
408 | # Take care of when some layers are excluded and yet we have included bbappends for those recipes | ||
409 | for recipename in self.bbhandler.cooker.collection.appendlist.iterkeys(): | ||
410 | if recipename not in appended_recipes: | ||
411 | appends = self.bbhandler.cooker.collection.appendlist[recipename] | ||
412 | first_append = None | ||
413 | for appendname in appends: | ||
414 | layer = layer_path_match(appendname) | ||
415 | if layer: | ||
416 | if first_append: | ||
417 | self.apply_append(appendname, first_append) | ||
418 | else: | ||
419 | fdest = appendname[len(layer):] | ||
420 | fdest = os.path.normpath(os.sep.join([outputdir,fdest])) | ||
421 | bb.utils.mkdirhier(os.path.dirname(fdest)) | ||
422 | bb.utils.copyfile(appendname, fdest) | ||
423 | first_append = fdest | ||
424 | |||
425 | # Get the regex for the first layer in our list (which is where the conf/layer.conf file will | ||
426 | # have come from) | ||
427 | first_regex = None | ||
428 | layerdir = layers[0] | ||
429 | for layername, pattern, regex, _ in self.bbhandler.cooker.recipecache.bbfile_config_priorities: | ||
430 | if regex.match(os.path.join(layerdir, 'test')): | ||
431 | first_regex = regex | ||
432 | break | ||
433 | |||
434 | if first_regex: | ||
435 | # Find the BBFILES entries that match (which will have come from this conf/layer.conf file) | ||
436 | bbfiles = str(self.bbhandler.config_data.getVar('BBFILES', True)).split() | ||
437 | bbfiles_layer = [] | ||
438 | for item in bbfiles: | ||
439 | if first_regex.match(item): | ||
440 | newpath = os.path.join(outputdir, item[len(layerdir)+1:]) | ||
441 | bbfiles_layer.append(newpath) | ||
442 | |||
443 | if bbfiles_layer: | ||
444 | # Check that all important layer files match BBFILES | ||
445 | for root, dirs, files in os.walk(outputdir): | ||
446 | for f1 in files: | ||
447 | ext = os.path.splitext(f1)[1] | ||
448 | if ext in ['.bb', '.bbappend']: | ||
449 | f1full = os.sep.join([root, f1]) | ||
450 | entry_found = False | ||
451 | for item in bbfiles_layer: | ||
452 | if fnmatch.fnmatch(f1full, item): | ||
453 | entry_found = True | ||
454 | break | ||
455 | if not entry_found: | ||
456 | logger.warning("File %s does not match the flattened layer's BBFILES setting, you may need to edit conf/layer.conf or move the file elsewhere" % f1full) | ||
457 | |||
458 | def get_file_layer(self, filename): | ||
459 | for layer, _, regex, _ in self.bbhandler.cooker.recipecache.bbfile_config_priorities: | ||
460 | if regex.match(filename): | ||
461 | for layerdir in self.bblayers: | ||
462 | if regex.match(os.path.join(layerdir, 'test')) and re.match(layerdir, filename): | ||
463 | return self.get_layer_name(layerdir) | ||
464 | return "?" | ||
465 | |||
466 | def get_file_layerdir(self, filename): | ||
467 | for layer, _, regex, _ in self.bbhandler.cooker.recipecache.bbfile_config_priorities: | ||
468 | if regex.match(filename): | ||
469 | for layerdir in self.bblayers: | ||
470 | if regex.match(os.path.join(layerdir, 'test')) and re.match(layerdir, filename): | ||
471 | return layerdir | ||
472 | return "?" | ||
473 | |||
474 | def remove_layer_prefix(self, f): | ||
475 | """Remove the layer_dir prefix, e.g., f = /path/to/layer_dir/foo/blah, the | ||
476 | return value will be: layer_dir/foo/blah""" | ||
477 | f_layerdir = self.get_file_layerdir(f) | ||
478 | prefix = os.path.join(os.path.dirname(f_layerdir), '') | ||
479 | return f[len(prefix):] if f.startswith(prefix) else f | ||
480 | |||
481 | def get_layer_name(self, layerdir): | ||
482 | return os.path.basename(layerdir.rstrip(os.sep)) | ||
483 | |||
484 | def apply_append(self, appendname, recipename): | ||
485 | appendfile = open(appendname, 'r') | ||
486 | recipefile = open(recipename, 'a') | ||
487 | recipefile.write('\n') | ||
488 | recipefile.write('##### bbappended from %s #####\n' % self.get_file_layer(appendname)) | ||
489 | recipefile.writelines(appendfile.readlines()) | ||
490 | recipefile.close() | ||
491 | appendfile.close() | ||
492 | |||
493 | def do_show_appends(self, args): | ||
494 | """list bbappend files and recipe files they apply to | ||
495 | |||
496 | usage: show-appends | ||
497 | |||
498 | Recipes are listed with the bbappends that apply to them as subitems. | ||
499 | """ | ||
500 | self.bbhandler.prepare() | ||
501 | if not self.bbhandler.cooker.collection.appendlist: | ||
502 | logger.plain('No append files found') | ||
503 | return | ||
504 | |||
505 | logger.plain('=== Appended recipes ===') | ||
506 | |||
507 | pnlist = list(self.bbhandler.cooker_data.pkg_pn.keys()) | ||
508 | pnlist.sort() | ||
509 | for pn in pnlist: | ||
510 | self.show_appends_for_pn(pn) | ||
511 | |||
512 | self.show_appends_for_skipped() | ||
513 | |||
514 | def show_appends_for_pn(self, pn): | ||
515 | filenames = self.bbhandler.cooker_data.pkg_pn[pn] | ||
516 | |||
517 | best = bb.providers.findBestProvider(pn, | ||
518 | self.bbhandler.config_data, | ||
519 | self.bbhandler.cooker_data, | ||
520 | self.bbhandler.cooker_data.pkg_pn) | ||
521 | best_filename = os.path.basename(best[3]) | ||
522 | |||
523 | self.show_appends_output(filenames, best_filename) | ||
524 | |||
525 | def show_appends_for_skipped(self): | ||
526 | filenames = [os.path.basename(f) | ||
527 | for f in self.bbhandler.cooker.skiplist.iterkeys()] | ||
528 | self.show_appends_output(filenames, None, " (skipped)") | ||
529 | |||
530 | def show_appends_output(self, filenames, best_filename, name_suffix = ''): | ||
531 | appended, missing = self.get_appends_for_files(filenames) | ||
532 | if appended: | ||
533 | for basename, appends in appended: | ||
534 | logger.plain('%s%s:', basename, name_suffix) | ||
535 | for append in appends: | ||
536 | logger.plain(' %s', append) | ||
537 | |||
538 | if best_filename: | ||
539 | if best_filename in missing: | ||
540 | logger.warn('%s: missing append for preferred version', | ||
541 | best_filename) | ||
542 | self.returncode |= 1 | ||
543 | |||
544 | |||
545 | def get_appends_for_files(self, filenames): | ||
546 | appended, notappended = [], [] | ||
547 | for filename in filenames: | ||
548 | _, cls = bb.cache.Cache.virtualfn2realfn(filename) | ||
549 | if cls: | ||
550 | continue | ||
551 | |||
552 | basename = os.path.basename(filename) | ||
553 | appends = self.bbhandler.cooker.collection.appendlist.get(basename) | ||
554 | if appends: | ||
555 | appended.append((basename, list(appends))) | ||
556 | else: | ||
557 | notappended.append(basename) | ||
558 | return appended, notappended | ||
559 | |||
560 | def do_show_cross_depends(self, args): | ||
561 | """figure out the dependency between recipes that crosses a layer boundary. | ||
562 | |||
563 | usage: show-cross-depends [-f] | ||
564 | |||
565 | Figure out the dependency between recipes that crosses a layer boundary. | ||
566 | |||
567 | Options: | ||
568 | -f show full file path | ||
569 | |||
570 | NOTE: | ||
571 | The .bbappend file can impact the dependency. | ||
572 | """ | ||
573 | self.bbhandler.prepare() | ||
574 | |||
575 | show_filenames = False | ||
576 | for arg in args.split(): | ||
577 | if arg == '-f': | ||
578 | show_filenames = True | ||
579 | else: | ||
580 | sys.stderr.write("show-cross-depends: invalid option %s\n" % arg) | ||
581 | self.do_help('') | ||
582 | return | ||
583 | |||
584 | pkg_fn = self.bbhandler.cooker_data.pkg_fn | ||
585 | bbpath = str(self.bbhandler.config_data.getVar('BBPATH', True)) | ||
586 | self.require_re = re.compile(r"require\s+(.+)") | ||
587 | self.include_re = re.compile(r"include\s+(.+)") | ||
588 | self.inherit_re = re.compile(r"inherit\s+(.+)") | ||
589 | |||
590 | # The bb's DEPENDS and RDEPENDS | ||
591 | for f in pkg_fn: | ||
592 | f = bb.cache.Cache.virtualfn2realfn(f)[0] | ||
593 | # Get the layername that the file is in | ||
594 | layername = self.get_file_layer(f) | ||
595 | |||
596 | # The DEPENDS | ||
597 | deps = self.bbhandler.cooker_data.deps[f] | ||
598 | for pn in deps: | ||
599 | if pn in self.bbhandler.cooker_data.pkg_pn: | ||
600 | best = bb.providers.findBestProvider(pn, | ||
601 | self.bbhandler.config_data, | ||
602 | self.bbhandler.cooker_data, | ||
603 | self.bbhandler.cooker_data.pkg_pn) | ||
604 | self.check_cross_depends("DEPENDS", layername, f, best[3], show_filenames) | ||
605 | |||
606 | # The RDPENDS | ||
607 | all_rdeps = self.bbhandler.cooker_data.rundeps[f].values() | ||
608 | # Remove the duplicated or null one. | ||
609 | sorted_rdeps = {} | ||
610 | # The all_rdeps is the list in list, so we need two for loops | ||
611 | for k1 in all_rdeps: | ||
612 | for k2 in k1: | ||
613 | sorted_rdeps[k2] = 1 | ||
614 | all_rdeps = sorted_rdeps.keys() | ||
615 | for rdep in all_rdeps: | ||
616 | all_p = bb.providers.getRuntimeProviders(self.bbhandler.cooker_data, rdep) | ||
617 | if all_p: | ||
618 | best = bb.providers.filterProvidersRunTime(all_p, rdep, | ||
619 | self.bbhandler.config_data, | ||
620 | self.bbhandler.cooker_data)[0][0] | ||
621 | self.check_cross_depends("RDEPENDS", layername, f, best, show_filenames) | ||
622 | |||
623 | # The inherit class | ||
624 | cls_re = re.compile('classes/') | ||
625 | if f in self.bbhandler.cooker_data.inherits: | ||
626 | inherits = self.bbhandler.cooker_data.inherits[f] | ||
627 | for cls in inherits: | ||
628 | # The inherits' format is [classes/cls, /path/to/classes/cls] | ||
629 | # ignore the classes/cls. | ||
630 | if not cls_re.match(cls): | ||
631 | inherit_layername = self.get_file_layer(cls) | ||
632 | if inherit_layername != layername: | ||
633 | if not show_filenames: | ||
634 | f_short = self.remove_layer_prefix(f) | ||
635 | cls = self.remove_layer_prefix(cls) | ||
636 | else: | ||
637 | f_short = f | ||
638 | logger.plain("%s inherits %s" % (f_short, cls)) | ||
639 | |||
640 | # The 'require/include xxx' in the bb file | ||
641 | pv_re = re.compile(r"\${PV}") | ||
642 | fnfile = open(f, 'r') | ||
643 | line = fnfile.readline() | ||
644 | while line: | ||
645 | m, keyword = self.match_require_include(line) | ||
646 | # Found the 'require/include xxxx' | ||
647 | if m: | ||
648 | needed_file = m.group(1) | ||
649 | # Replace the ${PV} with the real PV | ||
650 | if pv_re.search(needed_file) and f in self.bbhandler.cooker_data.pkg_pepvpr: | ||
651 | pv = self.bbhandler.cooker_data.pkg_pepvpr[f][1] | ||
652 | needed_file = re.sub(r"\${PV}", pv, needed_file) | ||
653 | self.print_cross_files(bbpath, keyword, layername, f, needed_file, show_filenames) | ||
654 | line = fnfile.readline() | ||
655 | fnfile.close() | ||
656 | |||
657 | # The "require/include xxx" in conf/machine/*.conf, .inc and .bbclass | ||
658 | conf_re = re.compile(".*/conf/machine/[^\/]*\.conf$") | ||
659 | inc_re = re.compile(".*\.inc$") | ||
660 | # The "inherit xxx" in .bbclass | ||
661 | bbclass_re = re.compile(".*\.bbclass$") | ||
662 | for layerdir in self.bblayers: | ||
663 | layername = self.get_layer_name(layerdir) | ||
664 | for dirpath, dirnames, filenames in os.walk(layerdir): | ||
665 | for name in filenames: | ||
666 | f = os.path.join(dirpath, name) | ||
667 | s = conf_re.match(f) or inc_re.match(f) or bbclass_re.match(f) | ||
668 | if s: | ||
669 | ffile = open(f, 'r') | ||
670 | line = ffile.readline() | ||
671 | while line: | ||
672 | m, keyword = self.match_require_include(line) | ||
673 | # Only bbclass has the "inherit xxx" here. | ||
674 | bbclass="" | ||
675 | if not m and f.endswith(".bbclass"): | ||
676 | m, keyword = self.match_inherit(line) | ||
677 | bbclass=".bbclass" | ||
678 | # Find a 'require/include xxxx' | ||
679 | if m: | ||
680 | self.print_cross_files(bbpath, keyword, layername, f, m.group(1) + bbclass, show_filenames) | ||
681 | line = ffile.readline() | ||
682 | ffile.close() | ||
683 | |||
684 | def print_cross_files(self, bbpath, keyword, layername, f, needed_filename, show_filenames): | ||
685 | """Print the depends that crosses a layer boundary""" | ||
686 | needed_file = bb.utils.which(bbpath, needed_filename) | ||
687 | if needed_file: | ||
688 | # Which layer is this file from | ||
689 | needed_layername = self.get_file_layer(needed_file) | ||
690 | if needed_layername != layername: | ||
691 | if not show_filenames: | ||
692 | f = self.remove_layer_prefix(f) | ||
693 | needed_file = self.remove_layer_prefix(needed_file) | ||
694 | logger.plain("%s %s %s" %(f, keyword, needed_file)) | ||
695 | def match_inherit(self, line): | ||
696 | """Match the inherit xxx line""" | ||
697 | return (self.inherit_re.match(line), "inherits") | ||
698 | |||
699 | def match_require_include(self, line): | ||
700 | """Match the require/include xxx line""" | ||
701 | m = self.require_re.match(line) | ||
702 | keyword = "requires" | ||
703 | if not m: | ||
704 | m = self.include_re.match(line) | ||
705 | keyword = "includes" | ||
706 | return (m, keyword) | ||
707 | |||
708 | def check_cross_depends(self, keyword, layername, f, needed_file, show_filenames): | ||
709 | """Print the DEPENDS/RDEPENDS file that crosses a layer boundary""" | ||
710 | best_realfn = bb.cache.Cache.virtualfn2realfn(needed_file)[0] | ||
711 | needed_layername = self.get_file_layer(best_realfn) | ||
712 | if needed_layername != layername: | ||
713 | if not show_filenames: | ||
714 | f = self.remove_layer_prefix(f) | ||
715 | best_realfn = self.remove_layer_prefix(best_realfn) | ||
716 | |||
717 | logger.plain("%s %s %s" % (f, keyword, best_realfn)) | ||
718 | |||
719 | if __name__ == '__main__': | ||
720 | sys.exit(main(sys.argv[1:]) or 0) | ||