@@ -287,7 +287,12 @@ def check_command(error_msg, cmd, cwd=None):
raise RuntimeError(msg)
return output
-def get_signatures(builddir, failsafe=False, machine=None, extravars=None):
+def get_signatures(builddir, machine=None, extravars=None):
+ '''
+ Returns the world signatures, the tasks of each tune and None, or the
+ error when some recipes failed, with the signatures of the others
+ (None when none were written).
+ '''
import re
# some recipes needs to be excluded like meta-world-pkgdata
@@ -304,25 +309,20 @@ def get_signatures(builddir, failsafe=False, machine=None, extravars=None):
cmd += ' '
if machine:
cmd += 'MACHINE=%s ' % machine
- cmd += 'bitbake '
- if failsafe:
- cmd += '-k '
- cmd += '-S lockedsigs world'
+ cmd += 'bitbake -k -S lockedsigs world'
sigs_file = os.path.join(builddir, 'locked-sigs.inc')
if os.path.exists(sigs_file):
os.unlink(sigs_file)
+ error = None
try:
check_command('Generating signatures failed. This might be due to some parse error and/or general layer incompatibilities.',
cmd, builddir)
except RuntimeError as ex:
- if failsafe and os.path.exists(sigs_file):
- # Ignore the error here. Most likely some recipes active
- # in a world build lack some dependencies. There is a
- # separate test_machine_world_build which exposes the
- # failure.
- pass
- else:
- raise
+ # Most likely some recipes active in a world build lack some
+ # dependencies: the caller decides whether that is fatal.
+ error = str(ex)
+ if not os.path.exists(sigs_file):
+ return (None, None, error)
sig_regex = re.compile(r"^(?P<task>[^:]*:[^:]*):(?P<hash>[^:]*) .$")
assignment_regex = re.compile(r"^\s*(?P<var>\S+)\s*\+?=")
@@ -362,7 +362,7 @@ def get_signatures(builddir, failsafe=False, machine=None, extravars=None):
if not sigs:
raise RuntimeError('Can\'t load signatures from %s' % sigs_file)
- return (sigs, tune2tasks)
+ return (sigs, tune2tasks, error)
def get_depgraph(targets=['world'], failsafe=False):
'''
@@ -14,6 +14,10 @@ class BSPCheckLayer(OECheckLayerTestCase):
if self.tc.layer['type'] not in (LayerType.BSP, LayerType.CORE):
raise unittest.SkipTest("BSPCheckLayer: Layer %s isn't BSP one." %\
self.tc.layer['name'])
+ # test_machine_world and test_machine_signatures share one world
+ # dump per machine
+ self.machine_sigs = {machine: get_signatures(self.td['builddir'], machine=machine)
+ for machine in self.td['machines'] or []}
def test_bsp_defines_machines(self):
self.assertTrue(self.tc.layer['conf']['machines'],
@@ -45,10 +49,9 @@ class BSPCheckLayer(OECheckLayerTestCase):
msg = []
for machine in self.td['machines']:
# In contrast to test_machine_signatures() below, errors are fatal here.
- try:
- get_signatures(self.td['builddir'], failsafe=False, machine=machine)
- except RuntimeError as ex:
- msg.append(str(ex))
+ error = self.machine_sigs[machine][2]
+ if error:
+ msg.append(error)
if msg:
msg.insert(0, 'The following machines broke a world build:')
self.fail('\n'.join(msg))
@@ -80,7 +83,9 @@ class BSPCheckLayer(OECheckLayerTestCase):
tunes = {}
# Preserve ordering of machines as chosen by the user.
for machine in self.td['machines']:
- curr_sigs, tune2tasks = get_signatures(self.td['builddir'], failsafe=True, machine=machine)
+ curr_sigs, tune2tasks, error = self.machine_sigs[machine]
+ if curr_sigs is None:
+ raise RuntimeError(error)
# Invert the tune -> [tasks] mapping.
tasks2tune = {}
for tune, tasks in tune2tasks.items():
@@ -11,6 +11,11 @@ from checklayer import get_signatures, LayerType, check_command, compare_signatu
from checklayer.case import OECheckLayerTestCase
class CommonCheckLayer(OECheckLayerTestCase):
+ @classmethod
+ def setUpClass(cls):
+ # test_world and test_signatures share one world dump
+ cls.world_sigs, _, cls.world_error = get_signatures(cls.td['builddir'])
+
def test_readme(self):
"""
Test that the layer contains a valid README file and that is meets
@@ -98,7 +103,8 @@ class CommonCheckLayer(OECheckLayerTestCase):
are not actually buildable, so here we fail when "bitbake -S none world"
fails.
'''
- get_signatures(self.td['builddir'], failsafe=False)
+ if self.world_error:
+ raise RuntimeError(self.world_error)
def test_world_inherit_class(self):
'''
@@ -106,10 +112,9 @@ class CommonCheckLayer(OECheckLayerTestCase):
class, which can do additional per-recipe test cases.
'''
msg = []
- try:
- get_signatures(self.td['builddir'], failsafe=False, machine=None, extravars='BB_ENV_PASSTHROUGH_ADDITIONS="$BB_ENV_PASSTHROUGH_ADDITIONS INHERIT" INHERIT="yocto-check-layer"')
- except RuntimeError as ex:
- msg.append(str(ex))
+ _, _, error = get_signatures(self.td['builddir'], machine=None, extravars='BB_ENV_PASSTHROUGH_ADDITIONS="$BB_ENV_PASSTHROUGH_ADDITIONS INHERIT" INHERIT="yocto-check-layer"')
+ if error:
+ msg.append(error)
if msg:
msg.insert(0, 'Layer %s failed additional checks from yocto-check-layer.bbclass\nSee below log for specific recipe parsing errors:\n' % \
self.tc.layer['name'])
@@ -135,8 +140,9 @@ class CommonCheckLayer(OECheckLayerTestCase):
raise unittest.SkipTest("Not testing for signature changes in a software layer %s." \
% self.tc.layer['name'])
- curr_sigs, _ = get_signatures(self.td['builddir'], failsafe=True)
- msg = compare_signatures(self.td['sigs'], curr_sigs)
+ if self.world_sigs is None:
+ raise RuntimeError(self.world_error)
+ msg = compare_signatures(self.td['sigs'], self.world_sigs)
if msg is not None:
self.fail('Adding layer %s changed signatures.\n%s' % (self.tc.layer['name'], msg))
@@ -216,7 +216,9 @@ def main():
try:
# Only test_signatures, a common test, compares against them
if not args.run_tests or 'common' in [t.split('.')[0] for t in args.run_tests]:
- td['sigs'], td['tunetasks'] = get_signatures(td['builddir'])
+ td['sigs'], td['tunetasks'], error = get_signatures(td['builddir'])
+ if error:
+ raise RuntimeError(error)
except RuntimeError as e:
logger.info(str(e))
results[layer['name']] = None
test_signatures and test_world dump the same world signatures, with and without -k, and so do test_machine_signatures and test_machine_world for each machine; when the dump succeeds, the second only repeats a world parse. Make get_signatures() always run bitbake with -k and return the error along with the signatures, and dump once in setUpClass(): for the common tests, and per machine for the BSP ones. test_world and test_machine_world check the stored error, the signature tests use the stored signatures. Tested on meta-freescale with two machines: 5 world dumps instead of 8, same results; a recipe breaking world on one machine fails test_machine_world for that machine only, now listing all its errors. Suggested-by: Luciano Dittgen <luciano.dittgen@ossystems.com.br> Suggested-by: Paul Barker <paul@pbarker.dev> AI-Generated: Uses Claude Code (Claude Opus 5.5) Signed-off-by: Otavio Salvador <otavio@ossystems.com.br> --- Changes in v2: - Replace the cache in get_signatures(), keyed on bblayers.conf, MACHINE and the extra variables, with one dump in setUpClass(): once for the common tests and once per machine for the BSP tests (Paul Barker). - get_signatures() always runs with -k and returns the error, or None, with the signatures, instead of taking failsafe. A failed dump is no longer run a second time without -k. scripts/lib/checklayer/__init__.py | 28 +++++++++++++------------- scripts/lib/checklayer/cases/bsp.py | 15 +++++++++----- scripts/lib/checklayer/cases/common.py | 20 +++++++++++------- scripts/yocto-check-layer | 4 +++- 4 files changed, 40 insertions(+), 27 deletions(-)