1eb8dc403SDave Cobbley# Yocto Project layer check tool 2eb8dc403SDave Cobbley# 3eb8dc403SDave Cobbley# Copyright (C) 2017 Intel Corporation 4c342db35SBrad Bishop# 5c342db35SBrad Bishop# SPDX-License-Identifier: MIT 6c342db35SBrad Bishop# 7eb8dc403SDave Cobbley 8eb8dc403SDave Cobbleyimport os 9eb8dc403SDave Cobbleyimport re 10eb8dc403SDave Cobbleyimport subprocess 11eb8dc403SDave Cobbleyfrom enum import Enum 12eb8dc403SDave Cobbley 13eb8dc403SDave Cobbleyimport bb.tinfoil 14eb8dc403SDave Cobbley 15eb8dc403SDave Cobbleyclass LayerType(Enum): 16eb8dc403SDave Cobbley BSP = 0 17eb8dc403SDave Cobbley DISTRO = 1 18eb8dc403SDave Cobbley SOFTWARE = 2 19517393d9SAndrew Geissler CORE = 3 20eb8dc403SDave Cobbley ERROR_NO_LAYER_CONF = 98 21eb8dc403SDave Cobbley ERROR_BSP_DISTRO = 99 22eb8dc403SDave Cobbley 23eb8dc403SDave Cobbleydef _get_configurations(path): 24eb8dc403SDave Cobbley configs = [] 25eb8dc403SDave Cobbley 26eb8dc403SDave Cobbley for f in os.listdir(path): 27eb8dc403SDave Cobbley file_path = os.path.join(path, f) 28eb8dc403SDave Cobbley if os.path.isfile(file_path) and f.endswith('.conf'): 29eb8dc403SDave Cobbley configs.append(f[:-5]) # strip .conf 30eb8dc403SDave Cobbley return configs 31eb8dc403SDave Cobbley 32eb8dc403SDave Cobbleydef _get_layer_collections(layer_path, lconf=None, data=None): 33eb8dc403SDave Cobbley import bb.parse 34eb8dc403SDave Cobbley import bb.data 35eb8dc403SDave Cobbley 36eb8dc403SDave Cobbley if lconf is None: 37eb8dc403SDave Cobbley lconf = os.path.join(layer_path, 'conf', 'layer.conf') 38eb8dc403SDave Cobbley 39eb8dc403SDave Cobbley if data is None: 40eb8dc403SDave Cobbley ldata = bb.data.init() 41eb8dc403SDave Cobbley bb.parse.init_parser(ldata) 42eb8dc403SDave Cobbley else: 43eb8dc403SDave Cobbley ldata = data.createCopy() 44eb8dc403SDave Cobbley 45eb8dc403SDave Cobbley ldata.setVar('LAYERDIR', layer_path) 46eb8dc403SDave Cobbley try: 47517393d9SAndrew Geissler ldata = bb.parse.handle(lconf, ldata, include=True, baseconfig=True) 48eb8dc403SDave Cobbley except: 49eb8dc403SDave Cobbley raise RuntimeError("Parsing of layer.conf from layer: %s failed" % layer_path) 50eb8dc403SDave Cobbley ldata.expandVarref('LAYERDIR') 51eb8dc403SDave Cobbley 52eb8dc403SDave Cobbley collections = (ldata.getVar('BBFILE_COLLECTIONS') or '').split() 53eb8dc403SDave Cobbley if not collections: 54eb8dc403SDave Cobbley name = os.path.basename(layer_path) 55eb8dc403SDave Cobbley collections = [name] 56eb8dc403SDave Cobbley 57eb8dc403SDave Cobbley collections = {c: {} for c in collections} 58eb8dc403SDave Cobbley for name in collections: 59eb8dc403SDave Cobbley priority = ldata.getVar('BBFILE_PRIORITY_%s' % name) 60eb8dc403SDave Cobbley pattern = ldata.getVar('BBFILE_PATTERN_%s' % name) 61eb8dc403SDave Cobbley depends = ldata.getVar('LAYERDEPENDS_%s' % name) 62eb8dc403SDave Cobbley compat = ldata.getVar('LAYERSERIES_COMPAT_%s' % name) 63475cb72dSAndrew Geissler try: 64475cb72dSAndrew Geissler depDict = bb.utils.explode_dep_versions2(depends or "") 65475cb72dSAndrew Geissler except bb.utils.VersionStringException as vse: 66475cb72dSAndrew Geissler bb.fatal('Error parsing LAYERDEPENDS_%s: %s' % (name, str(vse))) 67475cb72dSAndrew Geissler 68eb8dc403SDave Cobbley collections[name]['priority'] = priority 69eb8dc403SDave Cobbley collections[name]['pattern'] = pattern 70475cb72dSAndrew Geissler collections[name]['depends'] = ' '.join(depDict.keys()) 71eb8dc403SDave Cobbley collections[name]['compat'] = compat 72eb8dc403SDave Cobbley 73eb8dc403SDave Cobbley return collections 74eb8dc403SDave Cobbley 75eb8dc403SDave Cobbleydef _detect_layer(layer_path): 76eb8dc403SDave Cobbley """ 77eb8dc403SDave Cobbley Scans layer directory to detect what type of layer 78eb8dc403SDave Cobbley is BSP, Distro or Software. 79eb8dc403SDave Cobbley 80eb8dc403SDave Cobbley Returns a dictionary with layer name, type and path. 81eb8dc403SDave Cobbley """ 82eb8dc403SDave Cobbley 83eb8dc403SDave Cobbley layer = {} 84eb8dc403SDave Cobbley layer_name = os.path.basename(layer_path) 85eb8dc403SDave Cobbley 86eb8dc403SDave Cobbley layer['name'] = layer_name 87eb8dc403SDave Cobbley layer['path'] = layer_path 88eb8dc403SDave Cobbley layer['conf'] = {} 89eb8dc403SDave Cobbley 90eb8dc403SDave Cobbley if not os.path.isfile(os.path.join(layer_path, 'conf', 'layer.conf')): 91eb8dc403SDave Cobbley layer['type'] = LayerType.ERROR_NO_LAYER_CONF 92eb8dc403SDave Cobbley return layer 93eb8dc403SDave Cobbley 94eb8dc403SDave Cobbley machine_conf = os.path.join(layer_path, 'conf', 'machine') 95eb8dc403SDave Cobbley distro_conf = os.path.join(layer_path, 'conf', 'distro') 96eb8dc403SDave Cobbley 97eb8dc403SDave Cobbley is_bsp = False 98eb8dc403SDave Cobbley is_distro = False 99eb8dc403SDave Cobbley 100eb8dc403SDave Cobbley if os.path.isdir(machine_conf): 101eb8dc403SDave Cobbley machines = _get_configurations(machine_conf) 102eb8dc403SDave Cobbley if machines: 103eb8dc403SDave Cobbley is_bsp = True 104eb8dc403SDave Cobbley 105eb8dc403SDave Cobbley if os.path.isdir(distro_conf): 106eb8dc403SDave Cobbley distros = _get_configurations(distro_conf) 107eb8dc403SDave Cobbley if distros: 108eb8dc403SDave Cobbley is_distro = True 109eb8dc403SDave Cobbley 110517393d9SAndrew Geissler layer['collections'] = _get_layer_collections(layer['path']) 111517393d9SAndrew Geissler 112517393d9SAndrew Geissler if layer_name == "meta" and "core" in layer['collections']: 113517393d9SAndrew Geissler layer['type'] = LayerType.CORE 114517393d9SAndrew Geissler layer['conf']['machines'] = machines 115517393d9SAndrew Geissler layer['conf']['distros'] = distros 116517393d9SAndrew Geissler elif is_bsp and is_distro: 117eb8dc403SDave Cobbley layer['type'] = LayerType.ERROR_BSP_DISTRO 118eb8dc403SDave Cobbley elif is_bsp: 119eb8dc403SDave Cobbley layer['type'] = LayerType.BSP 120eb8dc403SDave Cobbley layer['conf']['machines'] = machines 121eb8dc403SDave Cobbley elif is_distro: 122eb8dc403SDave Cobbley layer['type'] = LayerType.DISTRO 123eb8dc403SDave Cobbley layer['conf']['distros'] = distros 124eb8dc403SDave Cobbley else: 125eb8dc403SDave Cobbley layer['type'] = LayerType.SOFTWARE 126eb8dc403SDave Cobbley 127eb8dc403SDave Cobbley return layer 128eb8dc403SDave Cobbley 129eb8dc403SDave Cobbleydef detect_layers(layer_directories, no_auto): 130eb8dc403SDave Cobbley layers = [] 131eb8dc403SDave Cobbley 132eb8dc403SDave Cobbley for directory in layer_directories: 133eb8dc403SDave Cobbley directory = os.path.realpath(directory) 134eb8dc403SDave Cobbley if directory[-1] == '/': 135eb8dc403SDave Cobbley directory = directory[0:-1] 136eb8dc403SDave Cobbley 137eb8dc403SDave Cobbley if no_auto: 138eb8dc403SDave Cobbley conf_dir = os.path.join(directory, 'conf') 139eb8dc403SDave Cobbley if os.path.isdir(conf_dir): 140eb8dc403SDave Cobbley layer = _detect_layer(directory) 141eb8dc403SDave Cobbley if layer: 142eb8dc403SDave Cobbley layers.append(layer) 143eb8dc403SDave Cobbley else: 144eb8dc403SDave Cobbley for root, dirs, files in os.walk(directory): 145eb8dc403SDave Cobbley dir_name = os.path.basename(root) 146eb8dc403SDave Cobbley conf_dir = os.path.join(root, 'conf') 147eb8dc403SDave Cobbley if os.path.isdir(conf_dir): 148eb8dc403SDave Cobbley layer = _detect_layer(root) 149eb8dc403SDave Cobbley if layer: 150eb8dc403SDave Cobbley layers.append(layer) 151eb8dc403SDave Cobbley 152eb8dc403SDave Cobbley return layers 153eb8dc403SDave Cobbley 154213cb269SPatrick Williamsdef _find_layer(depend, layers): 155eb8dc403SDave Cobbley for layer in layers: 1561e34c2d0SAndrew Geissler if 'collections' not in layer: 1571e34c2d0SAndrew Geissler continue 1581e34c2d0SAndrew Geissler 159eb8dc403SDave Cobbley for collection in layer['collections']: 160eb8dc403SDave Cobbley if depend == collection: 161eb8dc403SDave Cobbley return layer 162eb8dc403SDave Cobbley return None 163eb8dc403SDave Cobbley 1647e0e3c0cSAndrew Geisslerdef sanity_check_layers(layers, logger): 1657e0e3c0cSAndrew Geissler """ 1667e0e3c0cSAndrew Geissler Check that we didn't find duplicate collection names, as the layer that will 1677e0e3c0cSAndrew Geissler be used is non-deterministic. The precise check is duplicate collections 1687e0e3c0cSAndrew Geissler with different patterns, as the same pattern being repeated won't cause 1697e0e3c0cSAndrew Geissler problems. 1707e0e3c0cSAndrew Geissler """ 1717e0e3c0cSAndrew Geissler import collections 1727e0e3c0cSAndrew Geissler 1737e0e3c0cSAndrew Geissler passed = True 1747e0e3c0cSAndrew Geissler seen = collections.defaultdict(set) 1757e0e3c0cSAndrew Geissler for layer in layers: 1767e0e3c0cSAndrew Geissler for name, data in layer.get("collections", {}).items(): 1777e0e3c0cSAndrew Geissler seen[name].add(data["pattern"]) 1787e0e3c0cSAndrew Geissler 1797e0e3c0cSAndrew Geissler for name, patterns in seen.items(): 1807e0e3c0cSAndrew Geissler if len(patterns) > 1: 1817e0e3c0cSAndrew Geissler passed = False 1827e0e3c0cSAndrew Geissler logger.error("Collection %s found multiple times: %s" % (name, ", ".join(patterns))) 1837e0e3c0cSAndrew Geissler return passed 1847e0e3c0cSAndrew Geissler 185213cb269SPatrick Williamsdef get_layer_dependencies(layer, layers, logger): 186eb8dc403SDave Cobbley def recurse_dependencies(depends, layer, layers, logger, ret = []): 187eb8dc403SDave Cobbley logger.debug('Processing dependencies %s for layer %s.' % \ 188eb8dc403SDave Cobbley (depends, layer['name'])) 189eb8dc403SDave Cobbley 190eb8dc403SDave Cobbley for depend in depends.split(): 191eb8dc403SDave Cobbley # core (oe-core) is suppose to be provided 192eb8dc403SDave Cobbley if depend == 'core': 193eb8dc403SDave Cobbley continue 194eb8dc403SDave Cobbley 195213cb269SPatrick Williams layer_depend = _find_layer(depend, layers) 196eb8dc403SDave Cobbley if not layer_depend: 197eb8dc403SDave Cobbley logger.error('Layer %s depends on %s and isn\'t found.' % \ 198eb8dc403SDave Cobbley (layer['name'], depend)) 199eb8dc403SDave Cobbley ret = None 200eb8dc403SDave Cobbley continue 201eb8dc403SDave Cobbley 202eb8dc403SDave Cobbley # We keep processing, even if ret is None, this allows us to report 203eb8dc403SDave Cobbley # multiple errors at once 204eb8dc403SDave Cobbley if ret is not None and layer_depend not in ret: 205eb8dc403SDave Cobbley ret.append(layer_depend) 206004d4995SBrad Bishop else: 207004d4995SBrad Bishop # we might have processed this dependency already, in which case 208004d4995SBrad Bishop # we should not do it again (avoid recursive loop) 209004d4995SBrad Bishop continue 210eb8dc403SDave Cobbley 211eb8dc403SDave Cobbley # Recursively process... 212eb8dc403SDave Cobbley if 'collections' not in layer_depend: 213eb8dc403SDave Cobbley continue 214eb8dc403SDave Cobbley 215eb8dc403SDave Cobbley for collection in layer_depend['collections']: 216eb8dc403SDave Cobbley collect_deps = layer_depend['collections'][collection]['depends'] 217eb8dc403SDave Cobbley if not collect_deps: 218eb8dc403SDave Cobbley continue 219eb8dc403SDave Cobbley ret = recurse_dependencies(collect_deps, layer_depend, layers, logger, ret) 220eb8dc403SDave Cobbley 221eb8dc403SDave Cobbley return ret 222eb8dc403SDave Cobbley 223eb8dc403SDave Cobbley layer_depends = [] 224eb8dc403SDave Cobbley for collection in layer['collections']: 225eb8dc403SDave Cobbley depends = layer['collections'][collection]['depends'] 226eb8dc403SDave Cobbley if not depends: 227eb8dc403SDave Cobbley continue 228eb8dc403SDave Cobbley 229eb8dc403SDave Cobbley layer_depends = recurse_dependencies(depends, layer, layers, logger, layer_depends) 230eb8dc403SDave Cobbley 231eb8dc403SDave Cobbley # Note: [] (empty) is allowed, None is not! 232213cb269SPatrick Williams return layer_depends 233213cb269SPatrick Williams 234213cb269SPatrick Williamsdef add_layer_dependencies(bblayersconf, layer, layers, logger): 235213cb269SPatrick Williams 236213cb269SPatrick Williams layer_depends = get_layer_dependencies(layer, layers, logger) 237eb8dc403SDave Cobbley if layer_depends is None: 238eb8dc403SDave Cobbley return False 239eb8dc403SDave Cobbley else: 24099467dabSAndrew Geissler add_layers(bblayersconf, layer_depends, logger) 24199467dabSAndrew Geissler 24299467dabSAndrew Geissler return True 24399467dabSAndrew Geissler 24499467dabSAndrew Geisslerdef add_layers(bblayersconf, layers, logger): 245eb8dc403SDave Cobbley # Don't add a layer that is already present. 246eb8dc403SDave Cobbley added = set() 247eb8dc403SDave Cobbley output = check_command('Getting existing layers failed.', 'bitbake-layers show-layers').decode('utf-8') 248eb8dc403SDave Cobbley for layer, path, pri in re.findall(r'^(\S+) +([^\n]*?) +(\d+)$', output, re.MULTILINE): 249eb8dc403SDave Cobbley added.add(path) 250eb8dc403SDave Cobbley 25199467dabSAndrew Geissler with open(bblayersconf, 'a+') as f: 25299467dabSAndrew Geissler for layer in layers: 25399467dabSAndrew Geissler logger.info('Adding layer %s' % layer['name']) 25499467dabSAndrew Geissler name = layer['name'] 25599467dabSAndrew Geissler path = layer['path'] 256eb8dc403SDave Cobbley if path in added: 25799467dabSAndrew Geissler logger.info('%s is already in %s' % (name, bblayersconf)) 258eb8dc403SDave Cobbley else: 259eb8dc403SDave Cobbley added.add(path) 260eb8dc403SDave Cobbley f.write("\nBBLAYERS += \"%s\"\n" % path) 261eb8dc403SDave Cobbley return True 262eb8dc403SDave Cobbley 263635e0e46SAndrew Geisslerdef check_bblayers(bblayersconf, layer_path, logger): 264635e0e46SAndrew Geissler ''' 265635e0e46SAndrew Geissler If layer_path found in BBLAYERS return True 266635e0e46SAndrew Geissler ''' 267635e0e46SAndrew Geissler import bb.parse 268635e0e46SAndrew Geissler import bb.data 269635e0e46SAndrew Geissler 270635e0e46SAndrew Geissler ldata = bb.parse.handle(bblayersconf, bb.data.init(), include=True) 271635e0e46SAndrew Geissler for bblayer in (ldata.getVar('BBLAYERS') or '').split(): 272635e0e46SAndrew Geissler if os.path.normpath(bblayer) == os.path.normpath(layer_path): 273635e0e46SAndrew Geissler return True 274635e0e46SAndrew Geissler 275635e0e46SAndrew Geissler return False 276635e0e46SAndrew Geissler 27799467dabSAndrew Geisslerdef check_command(error_msg, cmd, cwd=None): 278eb8dc403SDave Cobbley ''' 279eb8dc403SDave Cobbley Run a command under a shell, capture stdout and stderr in a single stream, 280eb8dc403SDave Cobbley throw an error when command returns non-zero exit code. Returns the output. 281eb8dc403SDave Cobbley ''' 282eb8dc403SDave Cobbley 28399467dabSAndrew Geissler p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=cwd) 284eb8dc403SDave Cobbley output, _ = p.communicate() 285eb8dc403SDave Cobbley if p.returncode: 286eb8dc403SDave Cobbley msg = "%s\nCommand: %s\nOutput:\n%s" % (error_msg, cmd, output.decode('utf-8')) 287eb8dc403SDave Cobbley raise RuntimeError(msg) 288eb8dc403SDave Cobbley return output 289eb8dc403SDave Cobbley 2907e0e3c0cSAndrew Geisslerdef get_signatures(builddir, failsafe=False, machine=None, extravars=None): 291eb8dc403SDave Cobbley import re 292eb8dc403SDave Cobbley 293eb8dc403SDave Cobbley # some recipes needs to be excluded like meta-world-pkgdata 294eb8dc403SDave Cobbley # because a layer can add recipes to a world build so signature 295eb8dc403SDave Cobbley # will be change 296eb8dc403SDave Cobbley exclude_recipes = ('meta-world-pkgdata',) 297eb8dc403SDave Cobbley 298eb8dc403SDave Cobbley sigs = {} 299eb8dc403SDave Cobbley tune2tasks = {} 300eb8dc403SDave Cobbley 3017e0e3c0cSAndrew Geissler cmd = 'BB_ENV_PASSTHROUGH_ADDITIONS="$BB_ENV_PASSTHROUGH_ADDITIONS BB_SIGNATURE_HANDLER" BB_SIGNATURE_HANDLER="OEBasicHash" ' 3027e0e3c0cSAndrew Geissler if extravars: 3037e0e3c0cSAndrew Geissler cmd += extravars 3047e0e3c0cSAndrew Geissler cmd += ' ' 305eb8dc403SDave Cobbley if machine: 306eb8dc403SDave Cobbley cmd += 'MACHINE=%s ' % machine 307eb8dc403SDave Cobbley cmd += 'bitbake ' 308eb8dc403SDave Cobbley if failsafe: 309eb8dc403SDave Cobbley cmd += '-k ' 310ac13d5f3SPatrick Williams cmd += '-S lockedsigs world' 311eb8dc403SDave Cobbley sigs_file = os.path.join(builddir, 'locked-sigs.inc') 312eb8dc403SDave Cobbley if os.path.exists(sigs_file): 313eb8dc403SDave Cobbley os.unlink(sigs_file) 314eb8dc403SDave Cobbley try: 315eb8dc403SDave Cobbley check_command('Generating signatures failed. This might be due to some parse error and/or general layer incompatibilities.', 31699467dabSAndrew Geissler cmd, builddir) 317eb8dc403SDave Cobbley except RuntimeError as ex: 318eb8dc403SDave Cobbley if failsafe and os.path.exists(sigs_file): 319eb8dc403SDave Cobbley # Ignore the error here. Most likely some recipes active 320eb8dc403SDave Cobbley # in a world build lack some dependencies. There is a 321eb8dc403SDave Cobbley # separate test_machine_world_build which exposes the 322eb8dc403SDave Cobbley # failure. 323eb8dc403SDave Cobbley pass 324eb8dc403SDave Cobbley else: 325eb8dc403SDave Cobbley raise 326eb8dc403SDave Cobbley 32773bd93f1SPatrick Williams sig_regex = re.compile(r"^(?P<task>.*:.*):(?P<hash>.*) .$") 32873bd93f1SPatrick Williams tune_regex = re.compile(r"(^|\s)SIGGEN_LOCKEDSIGS_t-(?P<tune>\S*)\s*=\s*") 329eb8dc403SDave Cobbley current_tune = None 330eb8dc403SDave Cobbley with open(sigs_file, 'r') as f: 331eb8dc403SDave Cobbley for line in f.readlines(): 332eb8dc403SDave Cobbley line = line.strip() 333eb8dc403SDave Cobbley t = tune_regex.search(line) 334eb8dc403SDave Cobbley if t: 335eb8dc403SDave Cobbley current_tune = t.group('tune') 336eb8dc403SDave Cobbley s = sig_regex.match(line) 337eb8dc403SDave Cobbley if s: 338eb8dc403SDave Cobbley exclude = False 339eb8dc403SDave Cobbley for er in exclude_recipes: 340eb8dc403SDave Cobbley (recipe, task) = s.group('task').split(':') 341eb8dc403SDave Cobbley if er == recipe: 342eb8dc403SDave Cobbley exclude = True 343eb8dc403SDave Cobbley break 344eb8dc403SDave Cobbley if exclude: 345eb8dc403SDave Cobbley continue 346eb8dc403SDave Cobbley 347eb8dc403SDave Cobbley sigs[s.group('task')] = s.group('hash') 348eb8dc403SDave Cobbley tune2tasks.setdefault(current_tune, []).append(s.group('task')) 349eb8dc403SDave Cobbley 350eb8dc403SDave Cobbley if not sigs: 351eb8dc403SDave Cobbley raise RuntimeError('Can\'t load signatures from %s' % sigs_file) 352eb8dc403SDave Cobbley 353eb8dc403SDave Cobbley return (sigs, tune2tasks) 354eb8dc403SDave Cobbley 355eb8dc403SDave Cobbleydef get_depgraph(targets=['world'], failsafe=False): 356eb8dc403SDave Cobbley ''' 357eb8dc403SDave Cobbley Returns the dependency graph for the given target(s). 358eb8dc403SDave Cobbley The dependency graph is taken directly from DepTreeEvent. 359eb8dc403SDave Cobbley ''' 360eb8dc403SDave Cobbley depgraph = None 361eb8dc403SDave Cobbley with bb.tinfoil.Tinfoil() as tinfoil: 362eb8dc403SDave Cobbley tinfoil.prepare(config_only=False) 363eb8dc403SDave Cobbley tinfoil.set_event_mask(['bb.event.NoProvider', 'bb.event.DepTreeGenerated', 'bb.command.CommandCompleted']) 364eb8dc403SDave Cobbley if not tinfoil.run_command('generateDepTreeEvent', targets, 'do_build'): 365eb8dc403SDave Cobbley raise RuntimeError('starting generateDepTreeEvent failed') 366eb8dc403SDave Cobbley while True: 367eb8dc403SDave Cobbley event = tinfoil.wait_event(timeout=1000) 368eb8dc403SDave Cobbley if event: 369eb8dc403SDave Cobbley if isinstance(event, bb.command.CommandFailed): 370eb8dc403SDave Cobbley raise RuntimeError('Generating dependency information failed: %s' % event.error) 371eb8dc403SDave Cobbley elif isinstance(event, bb.command.CommandCompleted): 372eb8dc403SDave Cobbley break 373eb8dc403SDave Cobbley elif isinstance(event, bb.event.NoProvider): 374eb8dc403SDave Cobbley if failsafe: 375eb8dc403SDave Cobbley # The event is informational, we will get information about the 376eb8dc403SDave Cobbley # remaining dependencies eventually and thus can ignore this 377eb8dc403SDave Cobbley # here like we do in get_signatures(), if desired. 378eb8dc403SDave Cobbley continue 379eb8dc403SDave Cobbley if event._reasons: 380eb8dc403SDave Cobbley raise RuntimeError('Nothing provides %s: %s' % (event._item, event._reasons)) 381eb8dc403SDave Cobbley else: 382eb8dc403SDave Cobbley raise RuntimeError('Nothing provides %s.' % (event._item)) 383eb8dc403SDave Cobbley elif isinstance(event, bb.event.DepTreeGenerated): 384eb8dc403SDave Cobbley depgraph = event._depgraph 385eb8dc403SDave Cobbley 386eb8dc403SDave Cobbley if depgraph is None: 387eb8dc403SDave Cobbley raise RuntimeError('Could not retrieve the depgraph.') 388eb8dc403SDave Cobbley return depgraph 389eb8dc403SDave Cobbley 390eb8dc403SDave Cobbleydef compare_signatures(old_sigs, curr_sigs): 391eb8dc403SDave Cobbley ''' 392eb8dc403SDave Cobbley Compares the result of two get_signatures() calls. Returns None if no 393eb8dc403SDave Cobbley problems found, otherwise a string that can be used as additional 394eb8dc403SDave Cobbley explanation in self.fail(). 395eb8dc403SDave Cobbley ''' 396eb8dc403SDave Cobbley # task -> (old signature, new signature) 397eb8dc403SDave Cobbley sig_diff = {} 398eb8dc403SDave Cobbley for task in old_sigs: 399eb8dc403SDave Cobbley if task in curr_sigs and \ 400eb8dc403SDave Cobbley old_sigs[task] != curr_sigs[task]: 401eb8dc403SDave Cobbley sig_diff[task] = (old_sigs[task], curr_sigs[task]) 402eb8dc403SDave Cobbley 403eb8dc403SDave Cobbley if not sig_diff: 404eb8dc403SDave Cobbley return None 405eb8dc403SDave Cobbley 406eb8dc403SDave Cobbley # Beware, depgraph uses task=<pn>.<taskname> whereas get_signatures() 407eb8dc403SDave Cobbley # uses <pn>:<taskname>. Need to convert sometimes. The output follows 408eb8dc403SDave Cobbley # the convention from get_signatures() because that seems closer to 409eb8dc403SDave Cobbley # normal bitbake output. 410eb8dc403SDave Cobbley def sig2graph(task): 411eb8dc403SDave Cobbley pn, taskname = task.rsplit(':', 1) 412eb8dc403SDave Cobbley return pn + '.' + taskname 413eb8dc403SDave Cobbley def graph2sig(task): 414eb8dc403SDave Cobbley pn, taskname = task.rsplit('.', 1) 415eb8dc403SDave Cobbley return pn + ':' + taskname 416eb8dc403SDave Cobbley depgraph = get_depgraph(failsafe=True) 417eb8dc403SDave Cobbley depends = depgraph['tdepends'] 418eb8dc403SDave Cobbley 419eb8dc403SDave Cobbley # If a task A has a changed signature, but none of its 420eb8dc403SDave Cobbley # dependencies, then we need to report it because it is 421eb8dc403SDave Cobbley # the one which introduces a change. Any task depending on 422eb8dc403SDave Cobbley # A (directly or indirectly) will also have a changed 423eb8dc403SDave Cobbley # signature, but we don't need to report it. It might have 424eb8dc403SDave Cobbley # its own changes, which will become apparent once the 425eb8dc403SDave Cobbley # issues that we do report are fixed and the test gets run 426eb8dc403SDave Cobbley # again. 427eb8dc403SDave Cobbley sig_diff_filtered = [] 428eb8dc403SDave Cobbley for task, (old_sig, new_sig) in sig_diff.items(): 429eb8dc403SDave Cobbley deps_tainted = False 430eb8dc403SDave Cobbley for dep in depends.get(sig2graph(task), ()): 431eb8dc403SDave Cobbley if graph2sig(dep) in sig_diff: 432eb8dc403SDave Cobbley deps_tainted = True 433eb8dc403SDave Cobbley break 434eb8dc403SDave Cobbley if not deps_tainted: 435eb8dc403SDave Cobbley sig_diff_filtered.append((task, old_sig, new_sig)) 436eb8dc403SDave Cobbley 437eb8dc403SDave Cobbley msg = [] 438eb8dc403SDave Cobbley msg.append('%d signatures changed, initial differences (first hash before, second after):' % 439eb8dc403SDave Cobbley len(sig_diff)) 440eb8dc403SDave Cobbley for diff in sorted(sig_diff_filtered): 441eb8dc403SDave Cobbley recipe, taskname = diff[0].rsplit(':', 1) 442eb8dc403SDave Cobbley cmd = 'bitbake-diffsigs --task %s %s --signature %s %s' % \ 443eb8dc403SDave Cobbley (recipe, taskname, diff[1], diff[2]) 444eb8dc403SDave Cobbley msg.append(' %s: %s -> %s' % diff) 445eb8dc403SDave Cobbley msg.append(' %s' % cmd) 446eb8dc403SDave Cobbley try: 447eb8dc403SDave Cobbley output = check_command('Determining signature difference failed.', 448eb8dc403SDave Cobbley cmd).decode('utf-8') 449eb8dc403SDave Cobbley except RuntimeError as error: 450eb8dc403SDave Cobbley output = str(error) 451eb8dc403SDave Cobbley if output: 452eb8dc403SDave Cobbley msg.extend([' ' + line for line in output.splitlines()]) 453eb8dc403SDave Cobbley msg.append('') 454eb8dc403SDave Cobbley return '\n'.join(msg) 455*8460358cSPatrick Williams 456*8460358cSPatrick Williams 457*8460358cSPatrick Williamsdef get_git_toplevel(directory): 458*8460358cSPatrick Williams """ 459*8460358cSPatrick Williams Try and find the top of the git repository that directory might be in. 460*8460358cSPatrick Williams Returns the top-level directory, or None. 461*8460358cSPatrick Williams """ 462*8460358cSPatrick Williams cmd = ["git", "-C", directory, "rev-parse", "--show-toplevel"] 463*8460358cSPatrick Williams try: 464*8460358cSPatrick Williams return subprocess.check_output(cmd, text=True).strip() 465*8460358cSPatrick Williams except: 466*8460358cSPatrick Williams return None 467