diff options
author | ToshikazuOhiwa <toshikazu_ohiwa@mail.toyota.co.jp> | 2020-03-30 09:24:26 +0900 |
---|---|---|
committer | ToshikazuOhiwa <toshikazu_ohiwa@mail.toyota.co.jp> | 2020-03-30 09:24:26 +0900 |
commit | 5b80bfd7bffd4c20d80b7c70a7130529e9a755dd (patch) | |
tree | b4bb18dcd1487dbf1ea8127e5671b7bb2eded033 /external/poky/bitbake/lib/bblayers | |
parent | 706ad73eb02caf8532deaf5d38995bd258725cb8 (diff) |
agl-basesystem
Diffstat (limited to 'external/poky/bitbake/lib/bblayers')
-rw-r--r-- | external/poky/bitbake/lib/bblayers/__init__.py | 2 | ||||
-rw-r--r-- | external/poky/bitbake/lib/bblayers/action.py | 256 | ||||
-rw-r--r-- | external/poky/bitbake/lib/bblayers/common.py | 33 | ||||
-rw-r--r-- | external/poky/bitbake/lib/bblayers/layerindex.py | 208 | ||||
-rw-r--r-- | external/poky/bitbake/lib/bblayers/query.py | 501 |
5 files changed, 1000 insertions, 0 deletions
diff --git a/external/poky/bitbake/lib/bblayers/__init__.py b/external/poky/bitbake/lib/bblayers/__init__.py new file mode 100644 index 00000000..3ad9513f --- /dev/null +++ b/external/poky/bitbake/lib/bblayers/__init__.py @@ -0,0 +1,2 @@ +from pkgutil import extend_path +__path__ = extend_path(__path__, __name__) diff --git a/external/poky/bitbake/lib/bblayers/action.py b/external/poky/bitbake/lib/bblayers/action.py new file mode 100644 index 00000000..a3f658f9 --- /dev/null +++ b/external/poky/bitbake/lib/bblayers/action.py @@ -0,0 +1,256 @@ +import fnmatch +import logging +import os +import shutil +import sys +import tempfile + +import bb.utils + +from bblayers.common import LayerPlugin + +logger = logging.getLogger('bitbake-layers') + + +def plugin_init(plugins): + return ActionPlugin() + + +class ActionPlugin(LayerPlugin): + def do_add_layer(self, args): + """Add one or more layers to bblayers.conf.""" + layerdirs = [os.path.abspath(ldir) for ldir in args.layerdir] + + for layerdir in layerdirs: + if not os.path.exists(layerdir): + sys.stderr.write("Specified layer directory %s doesn't exist\n" % layerdir) + return 1 + + layer_conf = os.path.join(layerdir, 'conf', 'layer.conf') + if not os.path.exists(layer_conf): + sys.stderr.write("Specified layer directory %s doesn't contain a conf/layer.conf file\n" % layerdir) + return 1 + + bblayers_conf = os.path.join('conf', 'bblayers.conf') + if not os.path.exists(bblayers_conf): + sys.stderr.write("Unable to find bblayers.conf\n") + return 1 + + # Back up bblayers.conf to tempdir before we add layers + tempdir = tempfile.mkdtemp() + backup = tempdir + "/bblayers.conf.bak" + shutil.copy2(bblayers_conf, backup) + + try: + notadded, _ = bb.utils.edit_bblayers_conf(bblayers_conf, layerdirs, None) + if not (args.force or notadded): + try: + self.tinfoil.run_command('parseConfiguration') + except bb.tinfoil.TinfoilUIException: + # Restore the back up copy of bblayers.conf + shutil.copy2(backup, bblayers_conf) + bb.fatal("Parse failure with the specified layer added") + else: + for item in notadded: + sys.stderr.write("Specified layer %s is already in BBLAYERS\n" % item) + finally: + # Remove the back up copy of bblayers.conf + shutil.rmtree(tempdir) + + def do_remove_layer(self, args): + """Remove one or more layers from bblayers.conf.""" + bblayers_conf = os.path.join('conf', 'bblayers.conf') + if not os.path.exists(bblayers_conf): + sys.stderr.write("Unable to find bblayers.conf\n") + return 1 + + layerdirs = [] + for item in args.layerdir: + if item.startswith('*'): + layerdir = item + elif not '/' in item: + layerdir = '*/%s' % item + else: + layerdir = os.path.abspath(item) + layerdirs.append(layerdir) + (_, notremoved) = bb.utils.edit_bblayers_conf(bblayers_conf, None, layerdirs) + if notremoved: + for item in notremoved: + sys.stderr.write("No layers matching %s found in BBLAYERS\n" % item) + return 1 + + def do_flatten(self, args): + """flatten layer configuration into a separate output directory. + +Takes the specified layers (or all layers in the current layer +configuration if none are specified) and builds a "flattened" directory +containing the contents of all layers, with any overlayed recipes removed +and bbappends appended to the corresponding recipes. Note that some manual +cleanup may still be necessary afterwards, in particular: + +* where non-recipe files (such as patches) are overwritten (the flatten + command will show a warning for these) +* where anything beyond the normal layer setup has been added to + layer.conf (only the lowest priority number layer's layer.conf is used) +* overridden/appended items from bbappends will need to be tidied up +* when the flattened layers do not have the same directory structure (the + flatten command should show a warning when this will cause a problem) + +Warning: if you flatten several layers where another layer is intended to +be used "inbetween" them (in layer priority order) such that recipes / +bbappends in the layers interact, and then attempt to use the new output +layer together with that other layer, you may no longer get the same +build results (as the layer priority order has effectively changed). +""" + if len(args.layer) == 1: + logger.error('If you specify layers to flatten you must specify at least two') + return 1 + + outputdir = args.outputdir + if os.path.exists(outputdir) and os.listdir(outputdir): + logger.error('Directory %s exists and is non-empty, please clear it out first' % outputdir) + return 1 + + layers = self.bblayers + if len(args.layer) > 2: + layernames = args.layer + found_layernames = [] + found_layerdirs = [] + for layerdir in layers: + layername = self.get_layer_name(layerdir) + if layername in layernames: + found_layerdirs.append(layerdir) + found_layernames.append(layername) + + for layername in layernames: + if not layername in found_layernames: + 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]))) + return + layers = found_layerdirs + else: + layernames = [] + + # Ensure a specified path matches our list of layers + def layer_path_match(path): + for layerdir in layers: + if path.startswith(os.path.join(layerdir, '')): + return layerdir + return None + + applied_appends = [] + for layer in layers: + overlayed = [] + for f in self.tinfoil.cooker.collection.overlayed.keys(): + for of in self.tinfoil.cooker.collection.overlayed[f]: + if of.startswith(layer): + overlayed.append(of) + + logger.plain('Copying files from %s...' % layer ) + for root, dirs, files in os.walk(layer): + if '.git' in dirs: + dirs.remove('.git') + if '.hg' in dirs: + dirs.remove('.hg') + + for f1 in files: + f1full = os.sep.join([root, f1]) + if f1full in overlayed: + logger.plain(' Skipping overlayed file %s' % f1full ) + else: + ext = os.path.splitext(f1)[1] + if ext != '.bbappend': + fdest = f1full[len(layer):] + fdest = os.path.normpath(os.sep.join([outputdir,fdest])) + bb.utils.mkdirhier(os.path.dirname(fdest)) + if os.path.exists(fdest): + if f1 == 'layer.conf' and root.endswith('/conf'): + logger.plain(' Skipping layer config file %s' % f1full ) + continue + else: + logger.warning('Overwriting file %s', fdest) + bb.utils.copyfile(f1full, fdest) + if ext == '.bb': + for append in self.tinfoil.cooker.collection.get_file_appends(f1full): + if layer_path_match(append): + logger.plain(' Applying append %s to %s' % (append, fdest)) + self.apply_append(append, fdest) + applied_appends.append(append) + + # Take care of when some layers are excluded and yet we have included bbappends for those recipes + for b in self.tinfoil.cooker.collection.bbappends: + (recipename, appendname) = b + if appendname not in applied_appends: + first_append = None + layer = layer_path_match(appendname) + if layer: + if first_append: + self.apply_append(appendname, first_append) + else: + fdest = appendname[len(layer):] + fdest = os.path.normpath(os.sep.join([outputdir,fdest])) + bb.utils.mkdirhier(os.path.dirname(fdest)) + bb.utils.copyfile(appendname, fdest) + first_append = fdest + + # Get the regex for the first layer in our list (which is where the conf/layer.conf file will + # have come from) + first_regex = None + layerdir = layers[0] + for layername, pattern, regex, _ in self.tinfoil.cooker.bbfile_config_priorities: + if regex.match(os.path.join(layerdir, 'test')): + first_regex = regex + break + + if first_regex: + # Find the BBFILES entries that match (which will have come from this conf/layer.conf file) + bbfiles = str(self.tinfoil.config_data.getVar('BBFILES')).split() + bbfiles_layer = [] + for item in bbfiles: + if first_regex.match(item): + newpath = os.path.join(outputdir, item[len(layerdir)+1:]) + bbfiles_layer.append(newpath) + + if bbfiles_layer: + # Check that all important layer files match BBFILES + for root, dirs, files in os.walk(outputdir): + for f1 in files: + ext = os.path.splitext(f1)[1] + if ext in ['.bb', '.bbappend']: + f1full = os.sep.join([root, f1]) + entry_found = False + for item in bbfiles_layer: + if fnmatch.fnmatch(f1full, item): + entry_found = True + break + if not entry_found: + 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) + + def get_file_layer(self, filename): + layerdir = self.get_file_layerdir(filename) + if layerdir: + return self.get_layer_name(layerdir) + else: + return '?' + + def get_file_layerdir(self, filename): + layer = bb.utils.get_file_layer(filename, self.tinfoil.config_data) + return self.bbfile_collections.get(layer, None) + + def apply_append(self, appendname, recipename): + with open(appendname, 'r') as appendfile: + with open(recipename, 'a') as recipefile: + recipefile.write('\n') + recipefile.write('##### bbappended from %s #####\n' % self.get_file_layer(appendname)) + recipefile.writelines(appendfile.readlines()) + + def register_commands(self, sp): + parser_add_layer = self.add_command(sp, 'add-layer', self.do_add_layer, parserecipes=False) + parser_add_layer.add_argument('layerdir', nargs='+', help='Layer directory/directories to add') + + parser_remove_layer = self.add_command(sp, 'remove-layer', self.do_remove_layer, parserecipes=False) + parser_remove_layer.add_argument('layerdir', nargs='+', help='Layer directory/directories to remove (wildcards allowed, enclose in quotes to avoid shell expansion)') + parser_remove_layer.set_defaults(func=self.do_remove_layer) + + parser_flatten = self.add_command(sp, 'flatten', self.do_flatten) + parser_flatten.add_argument('layer', nargs='*', help='Optional layer(s) to flatten (otherwise all are flattened)') + parser_flatten.add_argument('outputdir', help='Output directory') diff --git a/external/poky/bitbake/lib/bblayers/common.py b/external/poky/bitbake/lib/bblayers/common.py new file mode 100644 index 00000000..98515ced --- /dev/null +++ b/external/poky/bitbake/lib/bblayers/common.py @@ -0,0 +1,33 @@ +import argparse +import logging +import os + +logger = logging.getLogger('bitbake-layers') + + +class LayerPlugin(): + def __init__(self): + self.tinfoil = None + self.bblayers = [] + + def tinfoil_init(self, tinfoil): + self.tinfoil = tinfoil + self.bblayers = (self.tinfoil.config_data.getVar('BBLAYERS') or "").split() + layerconfs = self.tinfoil.config_data.varhistory.get_variable_items_files('BBFILE_COLLECTIONS', self.tinfoil.config_data) + self.bbfile_collections = {layer: os.path.dirname(os.path.dirname(path)) for layer, path in layerconfs.items()} + + @staticmethod + def add_command(subparsers, cmdname, function, parserecipes=True, *args, **kwargs): + """Convert docstring for function to help.""" + docsplit = function.__doc__.splitlines() + help = docsplit[0] + if len(docsplit) > 1: + desc = '\n'.join(docsplit[1:]) + else: + desc = help + subparser = subparsers.add_parser(cmdname, *args, help=help, description=desc, formatter_class=argparse.RawTextHelpFormatter, **kwargs) + subparser.set_defaults(func=function, parserecipes=parserecipes) + return subparser + + def get_layer_name(self, layerdir): + return os.path.basename(layerdir.rstrip(os.sep)) diff --git a/external/poky/bitbake/lib/bblayers/layerindex.py b/external/poky/bitbake/lib/bblayers/layerindex.py new file mode 100644 index 00000000..b2ff2268 --- /dev/null +++ b/external/poky/bitbake/lib/bblayers/layerindex.py @@ -0,0 +1,208 @@ +import layerindexlib + +import argparse +import logging +import os +import subprocess + +from bblayers.action import ActionPlugin + +logger = logging.getLogger('bitbake-layers') + + +def plugin_init(plugins): + return LayerIndexPlugin() + + +class LayerIndexPlugin(ActionPlugin): + """Subcommands for interacting with the layer index. + + This class inherits ActionPlugin to get do_add_layer. + """ + + def get_fetch_layer(self, fetchdir, url, subdir, fetch_layer): + layername = self.get_layer_name(url) + if os.path.splitext(layername)[1] == '.git': + layername = os.path.splitext(layername)[0] + repodir = os.path.join(fetchdir, layername) + layerdir = os.path.join(repodir, subdir) + if not os.path.exists(repodir): + if fetch_layer: + result = subprocess.call(['git', 'clone', url, repodir]) + if result: + logger.error("Failed to download %s" % url) + return None, None, None + else: + return subdir, layername, layerdir + else: + logger.plain("Repository %s needs to be fetched" % url) + return subdir, layername, layerdir + elif os.path.exists(layerdir): + return subdir, layername, layerdir + else: + logger.error("%s is not in %s" % (url, subdir)) + return None, None, None + + def do_layerindex_fetch(self, args): + """Fetches a layer from a layer index along with its dependent layers, and adds them to conf/bblayers.conf. +""" + + def _construct_url(baseurls, branches): + urls = [] + for baseurl in baseurls: + if baseurl[-1] != '/': + baseurl += '/' + + if not baseurl.startswith('cooker'): + baseurl += "api/" + + if branches: + baseurl += ";branch=%s" % ','.join(branches) + + urls.append(baseurl) + + return urls + + + # Set the default... + if args.branch: + branches = [args.branch] + else: + branches = (self.tinfoil.config_data.getVar('LAYERSERIES_CORENAMES') or 'master').split() + logger.debug(1, 'Trying branches: %s' % branches) + + ignore_layers = [] + if args.ignore: + ignore_layers.extend(args.ignore.split(',')) + + # Load the cooker DB + cookerIndex = layerindexlib.LayerIndex(self.tinfoil.config_data) + cookerIndex.load_layerindex('cooker://', load='layerDependencies') + + # Fast path, check if we already have what has been requested! + (dependencies, invalidnames) = cookerIndex.find_dependencies(names=args.layername, ignores=ignore_layers) + if not args.show_only and not invalidnames: + logger.plain("You already have the requested layer(s): %s" % args.layername) + return 0 + + # The information to show is already in the cookerIndex + if invalidnames: + # General URL to use to access the layer index + # While there is ONE right now, we're expect users could enter several + apiurl = self.tinfoil.config_data.getVar('BBLAYERS_LAYERINDEX_URL').split() + if not apiurl: + logger.error("Cannot get BBLAYERS_LAYERINDEX_URL") + return 1 + + remoteIndex = layerindexlib.LayerIndex(self.tinfoil.config_data) + + for remoteurl in _construct_url(apiurl, branches): + logger.plain("Loading %s..." % remoteurl) + remoteIndex.load_layerindex(remoteurl) + + if remoteIndex.is_empty(): + logger.error("Remote layer index %s is empty for branches %s" % (apiurl, branches)) + return 1 + + lIndex = cookerIndex + remoteIndex + + (dependencies, invalidnames) = lIndex.find_dependencies(names=args.layername, ignores=ignore_layers) + + if invalidnames: + for invaluename in invalidnames: + logger.error('Layer "%s" not found in layer index' % invaluename) + return 1 + + logger.plain("%s %s %s" % ("Layer".ljust(49), "Git repository (branch)".ljust(54), "Subdirectory")) + logger.plain('=' * 125) + + for deplayerbranch in dependencies: + layerBranch = dependencies[deplayerbranch][0] + + # TODO: Determine display behavior + # This is the local content, uncomment to hide local + # layers from the display. + #if layerBranch.index.config['TYPE'] == 'cooker': + # continue + + layerDeps = dependencies[deplayerbranch][1:] + + requiredby = [] + recommendedby = [] + for dep in layerDeps: + if dep.required: + requiredby.append(dep.layer.name) + else: + recommendedby.append(dep.layer.name) + + logger.plain('%s %s %s' % (("%s:%s:%s" % + (layerBranch.index.config['DESCRIPTION'], + layerBranch.branch.name, + layerBranch.layer.name)).ljust(50), + ("%s (%s)" % (layerBranch.layer.vcs_url, + layerBranch.actual_branch)).ljust(55), + layerBranch.vcs_subdir + )) + if requiredby: + logger.plain(' required by: %s' % ' '.join(requiredby)) + if recommendedby: + logger.plain(' recommended by: %s' % ' '.join(recommendedby)) + + if dependencies: + fetchdir = self.tinfoil.config_data.getVar('BBLAYERS_FETCH_DIR') + if not fetchdir: + logger.error("Cannot get BBLAYERS_FETCH_DIR") + return 1 + if not os.path.exists(fetchdir): + os.makedirs(fetchdir) + addlayers = [] + + for deplayerbranch in dependencies: + layerBranch = dependencies[deplayerbranch][0] + + if layerBranch.index.config['TYPE'] == 'cooker': + # Anything loaded via cooker is already local, skip it + continue + + subdir, name, layerdir = self.get_fetch_layer(fetchdir, + layerBranch.layer.vcs_url, + layerBranch.vcs_subdir, + not args.show_only) + if not name: + # Error already shown + return 1 + addlayers.append((subdir, name, layerdir)) + if not args.show_only: + localargs = argparse.Namespace() + localargs.layerdir = [] + localargs.force = args.force + for subdir, name, layerdir in addlayers: + if os.path.exists(layerdir): + if subdir: + logger.plain("Adding layer \"%s\" (%s) to conf/bblayers.conf" % (subdir, layerdir)) + else: + logger.plain("Adding layer \"%s\" (%s) to conf/bblayers.conf" % (name, layerdir)) + localargs.layerdir.append(layerdir) + else: + break + + if localargs.layerdir: + self.do_add_layer(localargs) + + def do_layerindex_show_depends(self, args): + """Find layer dependencies from layer index. +""" + args.show_only = True + args.ignore = [] + self.do_layerindex_fetch(args) + + def register_commands(self, sp): + parser_layerindex_fetch = self.add_command(sp, 'layerindex-fetch', self.do_layerindex_fetch, parserecipes=False) + parser_layerindex_fetch.add_argument('-n', '--show-only', help='show dependencies and do nothing else', action='store_true') + parser_layerindex_fetch.add_argument('-b', '--branch', help='branch name to fetch') + parser_layerindex_fetch.add_argument('-i', '--ignore', help='assume the specified layers do not need to be fetched/added (separate multiple layers with commas, no spaces)', metavar='LAYER') + parser_layerindex_fetch.add_argument('layername', nargs='+', help='layer to fetch') + + parser_layerindex_show_depends = self.add_command(sp, 'layerindex-show-depends', self.do_layerindex_show_depends, parserecipes=False) + parser_layerindex_show_depends.add_argument('-b', '--branch', help='branch name to fetch') + parser_layerindex_show_depends.add_argument('layername', nargs='+', help='layer to query') diff --git a/external/poky/bitbake/lib/bblayers/query.py b/external/poky/bitbake/lib/bblayers/query.py new file mode 100644 index 00000000..9294dfa8 --- /dev/null +++ b/external/poky/bitbake/lib/bblayers/query.py @@ -0,0 +1,501 @@ +import collections +import fnmatch +import logging +import sys +import os +import re + +import bb.utils + +from bblayers.common import LayerPlugin + +logger = logging.getLogger('bitbake-layers') + + +def plugin_init(plugins): + return QueryPlugin() + + +class QueryPlugin(LayerPlugin): + def do_show_layers(self, args): + """show current configured layers.""" + logger.plain("%s %s %s" % ("layer".ljust(20), "path".ljust(40), "priority")) + logger.plain('=' * 74) + for layer, _, regex, pri in self.tinfoil.cooker.bbfile_config_priorities: + layerdir = self.bbfile_collections.get(layer, None) + layername = self.get_layer_name(layerdir) + logger.plain("%s %s %d" % (layername.ljust(20), layerdir.ljust(40), pri)) + + def version_str(self, pe, pv, pr = None): + verstr = "%s" % pv + if pr: + verstr = "%s-%s" % (verstr, pr) + if pe: + verstr = "%s:%s" % (pe, verstr) + return verstr + + def do_show_overlayed(self, args): + """list overlayed recipes (where the same recipe exists in another layer) + +Lists the names of overlayed recipes and the available versions in each +layer, with the preferred version first. Note that skipped recipes that +are overlayed will also be listed, with a " (skipped)" suffix. +""" + + items_listed = self.list_recipes('Overlayed recipes', None, True, args.same_version, args.filenames, True, None) + + # Check for overlayed .bbclass files + classes = collections.defaultdict(list) + for layerdir in self.bblayers: + classdir = os.path.join(layerdir, 'classes') + if os.path.exists(classdir): + for classfile in os.listdir(classdir): + if os.path.splitext(classfile)[1] == '.bbclass': + classes[classfile].append(classdir) + + # Locating classes and other files is a bit more complicated than recipes - + # layer priority is not a factor; instead BitBake uses the first matching + # file in BBPATH, which is manipulated directly by each layer's + # conf/layer.conf in turn, thus the order of layers in bblayers.conf is a + # factor - however, each layer.conf is free to either prepend or append to + # BBPATH (or indeed do crazy stuff with it). Thus the order in BBPATH might + # not be exactly the order present in bblayers.conf either. + bbpath = str(self.tinfoil.config_data.getVar('BBPATH')) + overlayed_class_found = False + for (classfile, classdirs) in classes.items(): + if len(classdirs) > 1: + if not overlayed_class_found: + logger.plain('=== Overlayed classes ===') + overlayed_class_found = True + + mainfile = bb.utils.which(bbpath, os.path.join('classes', classfile)) + if args.filenames: + logger.plain('%s' % mainfile) + else: + # We effectively have to guess the layer here + logger.plain('%s:' % classfile) + mainlayername = '?' + for layerdir in self.bblayers: + classdir = os.path.join(layerdir, 'classes') + if mainfile.startswith(classdir): + mainlayername = self.get_layer_name(layerdir) + logger.plain(' %s' % mainlayername) + for classdir in classdirs: + fullpath = os.path.join(classdir, classfile) + if fullpath != mainfile: + if args.filenames: + print(' %s' % fullpath) + else: + print(' %s' % self.get_layer_name(os.path.dirname(classdir))) + + if overlayed_class_found: + items_listed = True; + + if not items_listed: + logger.plain('No overlayed files found.') + + def do_show_recipes(self, args): + """list available recipes, showing the layer they are provided by + +Lists the names of recipes and the available versions in each +layer, with the preferred version first. Optionally you may specify +pnspec to match a specified recipe name (supports wildcards). Note that +skipped recipes will also be listed, with a " (skipped)" suffix. +""" + + inheritlist = args.inherits.split(',') if args.inherits else [] + if inheritlist or args.pnspec or args.multiple: + title = 'Matching recipes:' + else: + title = 'Available recipes:' + self.list_recipes(title, args.pnspec, False, False, args.filenames, args.multiple, inheritlist) + + def list_recipes(self, title, pnspec, show_overlayed_only, show_same_ver_only, show_filenames, show_multi_provider_only, inherits): + if inherits: + bbpath = str(self.tinfoil.config_data.getVar('BBPATH')) + for classname in inherits: + classfile = 'classes/%s.bbclass' % classname + if not bb.utils.which(bbpath, classfile, history=False): + logger.error('No class named %s found in BBPATH', classfile) + sys.exit(1) + + pkg_pn = self.tinfoil.cooker.recipecaches[''].pkg_pn + (latest_versions, preferred_versions) = self.tinfoil.find_providers() + allproviders = self.tinfoil.get_all_providers() + + # Ensure we list skipped recipes + # We are largely guessing about PN, PV and the preferred version here, + # but we have no choice since skipped recipes are not fully parsed + skiplist = list(self.tinfoil.cooker.skiplist.keys()) + for fn in skiplist: + recipe_parts = os.path.splitext(os.path.basename(fn))[0].split('_') + p = recipe_parts[0] + if len(recipe_parts) > 1: + ver = (None, recipe_parts[1], None) + else: + ver = (None, 'unknown', None) + allproviders[p].append((ver, fn)) + if not p in pkg_pn: + pkg_pn[p] = 'dummy' + preferred_versions[p] = (ver, fn) + + def print_item(f, pn, ver, layer, ispref): + if f in skiplist: + skipped = ' (skipped)' + else: + skipped = '' + if show_filenames: + if ispref: + logger.plain("%s%s", f, skipped) + else: + logger.plain(" %s%s", f, skipped) + else: + if ispref: + logger.plain("%s:", pn) + logger.plain(" %s %s%s", layer.ljust(20), ver, skipped) + + global_inherit = (self.tinfoil.config_data.getVar('INHERIT') or "").split() + cls_re = re.compile('classes/') + + preffiles = [] + items_listed = False + for p in sorted(pkg_pn): + if pnspec: + found=False + for pnm in pnspec: + if fnmatch.fnmatch(p, pnm): + found=True + break + if not found: + continue + + if len(allproviders[p]) > 1 or not show_multi_provider_only: + pref = preferred_versions[p] + realfn = bb.cache.virtualfn2realfn(pref[1]) + preffile = realfn[0] + + # We only display once per recipe, we should prefer non extended versions of the + # recipe if present (so e.g. in OpenEmbedded, openssl rather than nativesdk-openssl + # which would otherwise sort first). + if realfn[1] and realfn[0] in self.tinfoil.cooker.recipecaches[''].pkg_fn: + continue + + if inherits: + matchcount = 0 + recipe_inherits = self.tinfoil.cooker_data.inherits.get(preffile, []) + for cls in recipe_inherits: + if cls_re.match(cls): + continue + classname = os.path.splitext(os.path.basename(cls))[0] + if classname in global_inherit: + continue + elif classname in inherits: + matchcount += 1 + if matchcount != len(inherits): + # No match - skip this recipe + continue + + if preffile not in preffiles: + preflayer = self.get_file_layer(preffile) + multilayer = False + same_ver = True + provs = [] + for prov in allproviders[p]: + provfile = bb.cache.virtualfn2realfn(prov[1])[0] + provlayer = self.get_file_layer(provfile) + provs.append((provfile, provlayer, prov[0])) + if provlayer != preflayer: + multilayer = True + if prov[0] != pref[0]: + same_ver = False + + if (multilayer or not show_overlayed_only) and (same_ver or not show_same_ver_only): + if not items_listed: + logger.plain('=== %s ===' % title) + items_listed = True + print_item(preffile, p, self.version_str(pref[0][0], pref[0][1]), preflayer, True) + for (provfile, provlayer, provver) in provs: + if provfile != preffile: + print_item(provfile, p, self.version_str(provver[0], provver[1]), provlayer, False) + # Ensure we don't show two entries for BBCLASSEXTENDed recipes + preffiles.append(preffile) + + return items_listed + + def get_file_layer(self, filename): + layerdir = self.get_file_layerdir(filename) + if layerdir: + return self.get_layer_name(layerdir) + else: + return '?' + + def get_file_layerdir(self, filename): + layer = bb.utils.get_file_layer(filename, self.tinfoil.config_data) + return self.bbfile_collections.get(layer, None) + + def remove_layer_prefix(self, f): + """Remove the layer_dir prefix, e.g., f = /path/to/layer_dir/foo/blah, the + return value will be: layer_dir/foo/blah""" + f_layerdir = self.get_file_layerdir(f) + if not f_layerdir: + return f + prefix = os.path.join(os.path.dirname(f_layerdir), '') + return f[len(prefix):] if f.startswith(prefix) else f + + def do_show_appends(self, args): + """list bbappend files and recipe files they apply to + +Lists recipes with the bbappends that apply to them as subitems. +""" + if args.pnspec: + logger.plain('=== Matched appended recipes ===') + else: + logger.plain('=== Appended recipes ===') + + pnlist = list(self.tinfoil.cooker_data.pkg_pn.keys()) + pnlist.sort() + appends = False + for pn in pnlist: + if args.pnspec: + found=False + for pnm in args.pnspec: + if fnmatch.fnmatch(pn, pnm): + found=True + break + if not found: + continue + + if self.show_appends_for_pn(pn): + appends = True + + if not args.pnspec and self.show_appends_for_skipped(): + appends = True + + if not appends: + logger.plain('No append files found') + + def show_appends_for_pn(self, pn): + filenames = self.tinfoil.cooker_data.pkg_pn[pn] + + best = self.tinfoil.find_best_provider(pn) + best_filename = os.path.basename(best[3]) + + return self.show_appends_output(filenames, best_filename) + + def show_appends_for_skipped(self): + filenames = [os.path.basename(f) + for f in self.tinfoil.cooker.skiplist.keys()] + return self.show_appends_output(filenames, None, " (skipped)") + + def show_appends_output(self, filenames, best_filename, name_suffix = ''): + appended, missing = self.get_appends_for_files(filenames) + if appended: + for basename, appends in appended: + logger.plain('%s%s:', basename, name_suffix) + for append in appends: + logger.plain(' %s', append) + + if best_filename: + if best_filename in missing: + logger.warning('%s: missing append for preferred version', + best_filename) + return True + else: + return False + + def get_appends_for_files(self, filenames): + appended, notappended = [], [] + for filename in filenames: + _, cls, _ = bb.cache.virtualfn2realfn(filename) + if cls: + continue + + basename = os.path.basename(filename) + appends = self.tinfoil.cooker.collection.get_file_appends(basename) + if appends: + appended.append((basename, list(appends))) + else: + notappended.append(basename) + return appended, notappended + + def do_show_cross_depends(self, args): + """Show dependencies between recipes that cross layer boundaries. + +Figure out the dependencies between recipes that cross layer boundaries. + +NOTE: .bbappend files can impact the dependencies. +""" + ignore_layers = (args.ignore or '').split(',') + + pkg_fn = self.tinfoil.cooker_data.pkg_fn + bbpath = str(self.tinfoil.config_data.getVar('BBPATH')) + self.require_re = re.compile(r"require\s+(.+)") + self.include_re = re.compile(r"include\s+(.+)") + self.inherit_re = re.compile(r"inherit\s+(.+)") + + global_inherit = (self.tinfoil.config_data.getVar('INHERIT') or "").split() + + # The bb's DEPENDS and RDEPENDS + for f in pkg_fn: + f = bb.cache.virtualfn2realfn(f)[0] + # Get the layername that the file is in + layername = self.get_file_layer(f) + + # The DEPENDS + deps = self.tinfoil.cooker_data.deps[f] + for pn in deps: + if pn in self.tinfoil.cooker_data.pkg_pn: + best = self.tinfoil.find_best_provider(pn) + self.check_cross_depends("DEPENDS", layername, f, best[3], args.filenames, ignore_layers) + + # The RDPENDS + all_rdeps = self.tinfoil.cooker_data.rundeps[f].values() + # Remove the duplicated or null one. + sorted_rdeps = {} + # The all_rdeps is the list in list, so we need two for loops + for k1 in all_rdeps: + for k2 in k1: + sorted_rdeps[k2] = 1 + all_rdeps = sorted_rdeps.keys() + for rdep in all_rdeps: + all_p, best = self.tinfoil.get_runtime_providers(rdep) + if all_p: + if f in all_p: + # The recipe provides this one itself, ignore + continue + self.check_cross_depends("RDEPENDS", layername, f, best, args.filenames, ignore_layers) + + # The RRECOMMENDS + all_rrecs = self.tinfoil.cooker_data.runrecs[f].values() + # Remove the duplicated or null one. + sorted_rrecs = {} + # The all_rrecs is the list in list, so we need two for loops + for k1 in all_rrecs: + for k2 in k1: + sorted_rrecs[k2] = 1 + all_rrecs = sorted_rrecs.keys() + for rrec in all_rrecs: + all_p, best = self.tinfoil.get_runtime_providers(rrec) + if all_p: + if f in all_p: + # The recipe provides this one itself, ignore + continue + self.check_cross_depends("RRECOMMENDS", layername, f, best, args.filenames, ignore_layers) + + # The inherit class + cls_re = re.compile('classes/') + if f in self.tinfoil.cooker_data.inherits: + inherits = self.tinfoil.cooker_data.inherits[f] + for cls in inherits: + # The inherits' format is [classes/cls, /path/to/classes/cls] + # ignore the classes/cls. + if not cls_re.match(cls): + classname = os.path.splitext(os.path.basename(cls))[0] + if classname in global_inherit: + continue + inherit_layername = self.get_file_layer(cls) + if inherit_layername != layername and not inherit_layername in ignore_layers: + if not args.filenames: + f_short = self.remove_layer_prefix(f) + cls = self.remove_layer_prefix(cls) + else: + f_short = f + logger.plain("%s inherits %s" % (f_short, cls)) + + # The 'require/include xxx' in the bb file + pv_re = re.compile(r"\${PV}") + with open(f, 'r') as fnfile: + line = fnfile.readline() + while line: + m, keyword = self.match_require_include(line) + # Found the 'require/include xxxx' + if m: + needed_file = m.group(1) + # Replace the ${PV} with the real PV + if pv_re.search(needed_file) and f in self.tinfoil.cooker_data.pkg_pepvpr: + pv = self.tinfoil.cooker_data.pkg_pepvpr[f][1] + needed_file = re.sub(r"\${PV}", pv, needed_file) + self.print_cross_files(bbpath, keyword, layername, f, needed_file, args.filenames, ignore_layers) + line = fnfile.readline() + + # The "require/include xxx" in conf/machine/*.conf, .inc and .bbclass + conf_re = re.compile(".*/conf/machine/[^\/]*\.conf$") + inc_re = re.compile(".*\.inc$") + # The "inherit xxx" in .bbclass + bbclass_re = re.compile(".*\.bbclass$") + for layerdir in self.bblayers: + layername = self.get_layer_name(layerdir) + for dirpath, dirnames, filenames in os.walk(layerdir): + for name in filenames: + f = os.path.join(dirpath, name) + s = conf_re.match(f) or inc_re.match(f) or bbclass_re.match(f) + if s: + with open(f, 'r') as ffile: + line = ffile.readline() + while line: + m, keyword = self.match_require_include(line) + # Only bbclass has the "inherit xxx" here. + bbclass="" + if not m and f.endswith(".bbclass"): + m, keyword = self.match_inherit(line) + bbclass=".bbclass" + # Find a 'require/include xxxx' + if m: + self.print_cross_files(bbpath, keyword, layername, f, m.group(1) + bbclass, args.filenames, ignore_layers) + line = ffile.readline() + + def print_cross_files(self, bbpath, keyword, layername, f, needed_filename, show_filenames, ignore_layers): + """Print the depends that crosses a layer boundary""" + needed_file = bb.utils.which(bbpath, needed_filename) + if needed_file: + # Which layer is this file from + needed_layername = self.get_file_layer(needed_file) + if needed_layername != layername and not needed_layername in ignore_layers: + if not show_filenames: + f = self.remove_layer_prefix(f) + needed_file = self.remove_layer_prefix(needed_file) + logger.plain("%s %s %s" %(f, keyword, needed_file)) + + def match_inherit(self, line): + """Match the inherit xxx line""" + return (self.inherit_re.match(line), "inherits") + + def match_require_include(self, line): + """Match the require/include xxx line""" + m = self.require_re.match(line) + keyword = "requires" + if not m: + m = self.include_re.match(line) + keyword = "includes" + return (m, keyword) + + def check_cross_depends(self, keyword, layername, f, needed_file, show_filenames, ignore_layers): + """Print the DEPENDS/RDEPENDS file that crosses a layer boundary""" + best_realfn = bb.cache.virtualfn2realfn(needed_file)[0] + needed_layername = self.get_file_layer(best_realfn) + if needed_layername != layername and not needed_layername in ignore_layers: + if not show_filenames: + f = self.remove_layer_prefix(f) + best_realfn = self.remove_layer_prefix(best_realfn) + + logger.plain("%s %s %s" % (f, keyword, best_realfn)) + + def register_commands(self, sp): + self.add_command(sp, 'show-layers', self.do_show_layers, parserecipes=False) + + parser_show_overlayed = self.add_command(sp, 'show-overlayed', self.do_show_overlayed) + 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') + parser_show_overlayed.add_argument('-s', '--same-version', help='only list overlayed recipes where the version is the same', action='store_true') + + parser_show_recipes = self.add_command(sp, 'show-recipes', self.do_show_recipes) + 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') + 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') + 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='') + parser_show_recipes.add_argument('pnspec', nargs='*', help='optional recipe name specification (wildcards allowed, enclose in quotes to avoid shell expansion)') + + parser_show_appends = self.add_command(sp, 'show-appends', self.do_show_appends) + parser_show_appends.add_argument('pnspec', nargs='*', help='optional recipe name specification (wildcards allowed, enclose in quotes to avoid shell expansion)') + + parser_show_cross_depends = self.add_command(sp, 'show-cross-depends', self.do_show_cross_depends) + parser_show_cross_depends.add_argument('-f', '--filenames', help='show full file path', action='store_true') + 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') |