aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/bb-determine-layers
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/bb-determine-layers')
-rwxr-xr-xscripts/bb-determine-layers550
1 files changed, 0 insertions, 550 deletions
diff --git a/scripts/bb-determine-layers b/scripts/bb-determine-layers
deleted file mode 100755
index 83583474..00000000
--- a/scripts/bb-determine-layers
+++ /dev/null
@@ -1,550 +0,0 @@
-#!/usr/bin/env python3
-
-# ---------------------------------------------------------------------------------------------------------------------
-# SPDX-License-Identifier: MIT
-# ---------------------------------------------------------------------------------------------------------------------
-
-import argparse
-import collections
-import collections.abc
-import contextlib
-import glob
-import os
-import signal
-import sys
-
-
-glob_default = './*/:./*/*/:{scriptdir}/../../*/:{scriptdir}/../../*/*/'
-layers_default = 'core'
-override_default = ''
-
-
-class Terminate(BaseException):
- """Exception raised if we receive a SIGTERM"""
- pass
-
-
-def sigterm_exception(signum, stackframe):
- """Raise Terminate exception if we receive a SIGTERM"""
- raise Terminate()
-
-
-def setup_command_import(command, relpath='../lib'):
- """Set up sys.path based on the location of a binary in the PATH """
- PATH = os.environ['PATH'].split(':')
- cmd_paths = [os.path.join(path, relpath)
- for path in PATH if os.path.exists(os.path.join(path, command))]
- if not cmd_paths:
- raise ImportError("Unable to locate bb, please ensure PATH is set correctly.")
-
- sys.path[0:0] = cmd_paths
-
-
-def find(dir, dirfilter=None, **walkoptions):
- """ Given a directory, recurse into that directory,
- returning all files as absolute paths. """
-
- for root, dirs, files in os.walk(dir, **walkoptions):
- if dirfilter is not None:
- for d in dirs:
- if not dirfilter(d):
- dirs.remove(d)
-
- for file in files:
- yield os.path.join(root, file)
-
-
-class StatusDisplay(object):
- """Show the user what we're doing, and whether we succeed"""
-
- def __init__(self, message, output=None):
- self.message = message
- if output is None:
- output = sys.stderr
- self.output = output
- self.finished = False
-
- def __enter__(self):
- self.output.write('{}..'.format(self.message))
- self.output.flush()
- return self
-
- def __exit__(self, exc_type, exc_value, traceback):
- if self.finished:
- return
-
- if not exc_type:
- self.set_status('done')
- elif isinstance(exc_value, KeyboardInterrupt):
- self.set_status('interrupted')
- elif isinstance(exc_value, Terminate):
- self.set_status('terminated')
- else:
- self.set_status('failed')
-
- def set_status(self, finishmessage):
- if self.finished:
- raise Exception("Status is already finished")
- self.output.write('.' + finishmessage + '\n')
- self.finished = True
-
-
-status = StatusDisplay
-
-
-class DictArgument(object):
- def __init__(self, listsep=None, valuesep='=', keytype=None, valuetype=None):
- self.listsep = listsep
- self.valuesep = valuesep
- self.keytype = keytype
- self.valuetype = valuetype
-
- def __call__(self, string):
- d = {}
- for o in string.split(self.listsep):
- try:
- k, v = o.rsplit(self.valuesep, 1)
- except ValueError:
- raise argparse.ArgumentTypeError('invalid entry `{0}`'.format(o))
-
- if self.keytype is not None:
- try:
- k = self.keytype(k)
- except ValueError:
- raise argparse.ArgumentTypeError('invalid key `{0}` for type `{1}`'.format(k, self.keytype.__name__))
-
- if self.valuetype is not None:
- try:
- v = self.valuetype(v)
- except ValueError:
- raise argparse.ArgumentTypeError('invalid value `{0}` for type `{1}`'.format(v, self.valuetype.__name__))
-
- d[k] = v
- return d
-
- def __repr__(self):
- return 'DictArgument(listsep={0}, valuesep={1}, keytype={2}, valuetype={3})'.format(repr(self.listsep), repr(self.valuesep), repr(self.keytype), repr(self.valuetype))
-
-
-def process_arguments(cmdline_args):
- parser = argparse.ArgumentParser(description='Determine the bitbake layers to use for a given machine, with sanity checks')
- parser.add_argument('-l', '--layers', default=layers_default,
- help='base layers to include. space separated. default: `{0}`'.format(layers_default))
- parser.add_argument('-o', '--optional-layers', default='',
- help='optional layers to include. space separated. These layers are included if they and their dependencies are available, but are silently left out otherwise.')
- parser.add_argument('-e', '--extra-layers', default='',
- help='extra layers to include. space separated. These layers are expected to exist, but are only included if their dependencies are already included in the configuration.')
- parser.add_argument('-x', '--excluded-layers', default='',
- help='explicit layers to excluded. space separated.')
- parser.add_argument('-O', '--override-layer-priorities', default=override_default,
- type=DictArgument(valuetype=int),
- help='override the priority of one or more layers. space separated list of layername=priority values. default: `{0}`'.format(override_default))
- parser.add_argument('-g', '--globs', default=glob_default,
- help='wildcard patterns to locate layers. colon separated. default: `{0}`'.format(glob_default))
- parser.add_argument('-p', '--paths',
- help='paths to search recursively to find layers. colon separated. equivalent to the "find" command', default='')
- parser.add_argument('-m', '--machine', help='add layer(s) providing the specified MACHINE')
- parser.add_argument('-d', '--distro', help='add layer(s) providing the specified DISTRO')
- parser.add_argument('-s', '--sdkmachine', help='add layer(s) providing the specified SDKMACHINE')
- parser.add_argument('-v', '--verbose', help='increase verbosity', action='store_true')
-
- scriptdir = os.path.dirname(__file__)
- args = parser.parse_args(cmdline_args)
-
- args.layers = args.layers.split()
- args.optional_layers = args.optional_layers.split()
- args.extra_layers = args.extra_layers.split()
- args.excluded_layers = args.excluded_layers.split()
-
- new_globs = []
- for pattern in args.globs.split(':'):
- pattern = os.path.abspath(pattern.format(scriptdir=scriptdir))
- new_globs.append(pattern)
- args.globs = new_globs
-
- new_paths = []
- if args.paths:
- for path in args.paths.split(':'):
- path = os.path.abspath(path.format(scriptdir=scriptdir))
- new_paths.append(path)
- args.paths = new_paths
-
- return args
-
-
-class LayerError(Exception):
- pass
-
-
-class Layer(object):
- def __init__(self, path, confpath, name, version, priority, pattern, depends, recommends):
- self.path = os.path.realpath(path)
- self.confpath = os.path.realpath(confpath)
- self.name = name
- self.version = version
- self.priority = priority
- self.depends = depends
- self.recommends = recommends
- self.missingdeps = set()
- self.pattern = pattern
-
- def __repr__(self):
- return '{0.__class__.__name__}({0.path!r}, {0.confpath!r},' \
- '{0.name!r}, {0.version!r}, {0.priority!r}, {0.pattern!r}, {0.depends!r}, {0.recommends!r})'.format(self)
-
- def __hash__(self):
- return hash(repr(self))
-
- @staticmethod
- def from_layerpath(layer_path, data=None):
- layers = []
-
- if data is None:
- data = bb.data.init()
- bb.parse.init_parser(data)
-
- lconf = os.path.join(layer_path, 'conf', 'layer.conf')
- ldata = data.createCopy()
- ldata.setVar('LAYERDIR', layer_path)
- try:
- ldata = bb.parse.handle(lconf, ldata, include=True)
- except BaseException as exc:
- raise LayerError(exc)
- ldata.expandVarref('LAYERDIR')
-
- bbfile_collections = (ldata.getVar('BBFILE_COLLECTIONS', True) or '').split()
- if not bbfile_collections:
- name = os.path.basename(layer_path)
- l = Layer(layer_path, lconf, name, 0, 0, None, {})
- layers.append(l)
-
- for name in bbfile_collections:
- pattern = ldata.getVar('BBFILE_PATTERN_%s' % name, True)
- priority = ldata.getVar('BBFILE_PRIORITY_%s' % name, True) or 0
- depends = ldata.getVar('LAYERDEPENDS_%s' % name, True) or ''
- version = ldata.getVar('LAYERVERSION_%s' % name, True)
- depends = bb.utils.explode_dep_versions2(depends)
- recommends = ldata.getVar('LAYERRECOMMENDS_%s' % name, True) or ''
- recommends = bb.utils.explode_dep_versions2(recommends)
-
- l = Layer(layer_path, lconf, name, version, int(priority), pattern, depends, recommends)
- layers.append(l)
-
- if layers:
- return layers
-
-
-class Layers(collections.abc.MutableSet):
- def __init__(self, iterable=None):
- """A collection of layers. Maintains lookup mappings by path and name"""
- self.layers = set()
- self.duplicates = collections.defaultdict(set)
- self.by_path = {}
- self.by_name = {}
-
- if iterable is not None:
- self |= iterable
-
- def __contains__(self, layer):
- return layer in self.layers
-
- def __iter__(self):
- return iter(self.layers)
-
- def __len__(self):
- return len(self.layers)
-
- def __repr__(self):
- return '{0.__class__.__name__}({0.layers!r})'.format(self)
-
- def __hash__(self):
- return hash(repr(self))
-
- def add(self, layer):
- if layer in self:
- return
-
- if layer.path in self.by_path:
- return
-
- if layer.name in self.by_name:
- self.duplicates[layer.name].add(self.by_name[layer.name])
- self.duplicates[layer.name].add(layer)
- return
-
- self.layers.add(layer)
- self.by_path[layer.path] = layer
- self.by_name[layer.name] = layer
-
- def add_from_path(self, layerpath, data=None):
- layerpath = os.path.realpath(layerpath)
- if layerpath in self.by_path:
- return
-
- self |= Layer.from_layerpath(layerpath, data)
-
- def discard(self, layer):
- self.layers.discard(layer)
- del self.by_path[layer.path]
- del self.by_name[layer.name]
-
- def clear(self):
- self.layers.clear()
- self.by_path.clear()
- self.by_name.clear()
-
- def priority_sorted(self):
- return sorted(self.layers, key=lambda l: (l.priority, l.name), reverse=True)
-
- def which(self, path, all=True):
- layers = []
- for layer in self.priority_sorted():
- if os.path.exists(os.path.join(layer.path, path)):
- layers.append(layer)
- return layers
-
- def get_by_name_recursive(self, name, include_recs=True, indirect_recs=False):
- """Get a layer by name and its dependencies, recursively.
-
- Returns a Layers() collection, and a list of missing dependencies."""
-
- missing = set()
- found_layers = Layers()
-
- if not include_recs:
- indirect_recs = False
-
- if name in self.duplicates:
- raise DuplicateLayers(name, self.duplicates[name])
-
- try:
- layer = self.by_name[name]
- except KeyError:
- missing.add(name)
- else:
- found_layers.add(layer)
- for dep, oplist in layer.depends.items():
- try:
- dep_found, dep_missing = self.get_by_name_recursive(dep, indirect_recs, indirect_recs)
- except KeyError:
- missing.add(dep)
- continue
-
- for _dep in dep_found:
- for opstr in oplist:
- op, depver = opstr.split()
- if _dep.version:
- try:
- res = bb.utils.vercmp_string_op(_dep.version, depver, op)
- except bb.utils.VersionStringException as vse:
- missing.add(dep)
- else:
- if not res:
- missing.add(dep)
-
- if dep not in missing:
- found_layers |= dep_found
- missing |= dep_missing
-
- if include_recs:
- for rec, oplist in layer.recommends.items():
- found = True
-
- try:
- rec_found, _ = self.get_by_name_recursive(rec, indirect_recs, indirect_recs)
- except KeyError:
- continue
-
- for _rec in rec_found:
- for opstr in oplist:
- op, recver = opstr.split()
- if _rec.version:
- try:
- res = bb.utils.vercmp_string_op(_rec.version, recver, op)
- except bb.utils.VersionStringException as vse:
- found = False
- else:
- if not res:
- found = False
-
- if found and rec not in missing:
- found_layers |= rec_found
-
- return found_layers, missing
-
-
-class DuplicateLayers(Exception):
- def __init__(self, name, layers):
- self.name = name
- self.layers = layers
-
- def __str__(self):
- return 'Duplicate layers found for %s:\n%s' % (self.name, ''.join(' %s\n' % l.path for l in self.layers))
-
-
-class LayerStatus(StatusDisplay):
- """Show status of work whose results are a set of layers."""
-
- def __init__(self, message, verbose=False, output=None):
- super(LayerStatus, self).__init__(message, output)
- self.verbose = verbose
-
- def set_layer_status(self, layers):
- layer_names = set(l.name for l in layers)
- if self.verbose:
- self.set_status('')
- for f in sorted(l.path for l in layers):
- self.output.write(' %s\n' % f)
- else:
- self.set_status(','.join(sorted(layer_names)))
-
-
-def get_layerpaths_bitbake(globs=None, paths=None):
- bitbake_path = None
- layer_paths = set()
-
- for pattern in globs:
- for glob_path in glob.glob(pattern):
- layerconf_path = os.path.join(glob_path, 'conf', 'layer.conf')
- if os.path.exists(layerconf_path):
- layer_paths.add(os.path.realpath(glob_path))
- elif not bitbake_path and os.path.exists(os.path.join(glob_path, 'bin', 'bitbake')):
- bitbake_path = os.path.join(glob_path, 'bin')
- if not os.path.exists(os.path.join(bitbake_path, '..', 'lib', 'bb')):
- bitbake_path = None
-
- for path in paths:
- for subpath in find(path, dirfilter=lambda d: d != 'lib'):
- if subpath.endswith('/conf/layer.conf'):
- layer_paths.add(os.path.realpath(os.path.dirname(os.path.dirname(subpath))))
- elif not bitbake_path and subpath.endswith('/bin/bitbake'):
- bitbake_path = os.path.dirname(subpath)
- if not os.path.exists(os.path.join(bitbake_path, '..', 'lib', 'bb')):
- bitbake_path = None
-
- return layer_paths, bitbake_path
-
-
-
-def get_layers_for(all_layers, value, varname, cfgpath, verbose=False):
- with LayerStatus("Determining layers to include for {0} '{1}'".format(varname, value), verbose=verbose) as s:
- layers = all_layers.which('conf/{0}/{1}.conf'.format(cfgpath, value))
- if not layers:
- sys.exit("No layer found for {0} `{1}`".format(varname, value))
-
- found_layers = Layers()
- for name in set(l.name for l in layers):
- layers, missing_layers = all_layers.get_by_name_recursive(name)
- found_layers |= layers
- if name in missing_layers:
- sys.exit('Layer `{0}` was not found'.format(name))
- elif missing_layers:
- sys.exit("Layer `{0}` was found, but its dependencies are missing: `{1}`".format(name, ', '.join(missing_layers)))
- s.set_layer_status(found_layers)
- return found_layers
-
-
-def determine_layers(cmdline_opts):
- args = process_arguments(cmdline_opts)
- args.layers.append('core')
-
- with status('Locating layers and bitbake'):
- layer_paths, bitbake_path = get_layerpaths_bitbake(args.globs, args.paths)
- if bitbake_path is not None:
- os.environ['PATH'] += ':' + bitbake_path
-
- setup_command_import('bitbake')
- try:
- import bb
- except (ImportError, RuntimeError) as exc:
- sys.exit("Unable to import 'bb' python package: %s" % exc)
-
- import bb.parse
- import bb.data
-
- data = bb.data.init()
- bb.parse.init_parser(data)
-
- all_layers = Layers()
- with status('Parsing layer configuration files'):
- for layer_path in layer_paths:
- try:
- all_layers.add_from_path(layer_path, data)
- except LayerError as exc:
- sys.stderr.write('Warning: error parsing layer at {}\n'.format(layer_path))
-
- for layer in list(all_layers):
- if layer.name in args.excluded_layers:
- all_layers.discard(layer)
- else:
- priority_override = args.override_layer_priorities.get(layer.name)
- if priority_override is not None:
- layer.priority = priority_override
-
- configured_layers = Layers()
- with LayerStatus('Checking for base layers', verbose=args.verbose) as s:
- found = set()
- for name in args.layers:
- if name in all_layers.by_name:
- layers, missing_layers = all_layers.get_by_name_recursive(name)
- if missing_layers:
- sys.exit("Layer `{0}` was found, but its dependencies are missing: `{1}`".format(name, ', '.join(missing_layers)))
- else:
- found.update(layers)
-
- s.set_layer_status(found)
- configured_layers |= found
-
- if args.distro and args.distro != 'nodistro':
- configured_layers |= get_layers_for(all_layers, args.distro, 'DISTRO', 'distro', args.verbose)
-
- if args.machine:
- configured_layers |= get_layers_for(all_layers, args.machine, 'MACHINE', 'machine', args.verbose)
-
- if args.sdkmachine:
- configured_layers |= get_layers_for(all_layers, args.sdkmachine, 'SDKMACHINE', 'machine-sdk', args.verbose)
-
- if args.optional_layers:
- with LayerStatus('Checking for optional layers', verbose=args.verbose) as s:
- found_optional = set()
- for name in args.optional_layers:
- if name in all_layers.by_name:
- layers, missing_layers = all_layers.get_by_name_recursive(name)
- if missing_layers:
- sys.stderr.write("Warning: optional layer `{0}` was found, but its dependencies are missing: `{1}`".format(name, ', '.join(missing_layers)))
- else:
- found_optional.update(layers)
-
- found_optional.difference_update(configured_layers)
- s.set_layer_status(found_optional)
- configured_layers |= found_optional
-
- if args.extra_layers:
- with LayerStatus('Checking for extra layers', verbose=args.verbose) as s:
- found_extra = set()
- for name in args.extra_layers:
- if name in all_layers.by_name:
- layers, missing_layers = all_layers.get_by_name_recursive(name)
- if not missing_layers:
- found_extra.update(layers)
-
- found_extra.difference_update(configured_layers)
- s.set_layer_status(found_extra)
- configured_layers |= found_extra
-
- for layer in configured_layers.priority_sorted():
- print(layer.path)
-
-
-if __name__ == '__main__':
- signal.signal(signal.SIGTERM, sigterm_exception)
- try:
- sys.exit(determine_layers(sys.argv[1:]) or 0)
- except DuplicateLayers as exc:
- sys.exit(str(exc))
- except KeyboardInterrupt:
- signal.signal(signal.SIGINT, signal.SIG_DFL)
- os.kill(os.getpid(), signal.SIGINT)
- except Terminate:
- signal.signal(signal.SIGTERM, signal.SIG_DFL)
- os.kill(os.getpid(), signal.SIGTERM)