diff --git a/meta/lib/oe/spdx30_tasks.py b/meta/lib/oe/spdx30_tasks.py index b43d626df3..0e9c6faf5c 100644 --- a/meta/lib/oe/spdx30_tasks.py +++ b/meta/lib/oe/spdx30_tasks.py @@ -517,9 +517,7 @@ def create_spdx(d): build_objset.set_is_native(is_native) for var in (d.getVar("SPDX_CUSTOM_ANNOTATION_VARS") or "").split(): - new_annotation( - d, - build_objset, + build_objset.new_annotation( build, "%s=%s" % (var, d.getVar(var)), oe.spdx30.AnnotationType.other, diff --git a/meta/lib/oeqa/selftest/cases/spdx.py b/meta/lib/oeqa/selftest/cases/spdx.py index 3373988ca4..5f75b07725 100644 --- a/meta/lib/oeqa/selftest/cases/spdx.py +++ b/meta/lib/oeqa/selftest/cases/spdx.py @@ -343,3 +343,77 @@ class SPDX30Check(SPDX3CheckBase, OESelftestTestCase): value, ["enabled", "disabled"], f"Unexpected PACKAGECONFIG value '{value}' for {key}" ) + + def test_custom_annotation_vars(self): + """ + Test that SPDX_CUSTOM_ANNOTATION_VARS properly creates annotations + without runtime errors. This is a regression test for the bug where + new_annotation() was called as a standalone function instead of as + a method on build_objset, causing a NameError. + + The test verifies: + 1. The build completes successfully (no NameError) + 2. Each configured annotation variable appears exactly once + 3. The annotation values match the configured variables + + We check for exact equality (not >=) to prevent regressions where + one annotation might appear multiple times while another is missing. + """ + ANNOTATION_VAR1 = "TestAnnotation1" + ANNOTATION_VAR2 = "TestAnnotation2" + + # This will fail with NameError if new_annotation() is called incorrectly + objset = self.check_recipe_spdx( + "base-files", + "{DEPLOY_DIR_SPDX}/{MACHINE_ARCH}/recipes/recipe-base-files.spdx.json", + extraconf=textwrap.dedent( + f"""\ + ANNOTATION1 = "{ANNOTATION_VAR1}" + ANNOTATION2 = "{ANNOTATION_VAR2}" + SPDX_CUSTOM_ANNOTATION_VARS = "ANNOTATION1 ANNOTATION2" + """ + ), + ) + + # If we got here, the build succeeded (no NameError) + # Now verify the annotations were actually created + + # Find the build element + build = None + for o in objset.foreach_type(oe.spdx30.build_Build): + build = o + break + + self.assertIsNotNone(build, "Unable to find Build element") + + # Find annotation objects that reference our build + found_annotations = [] + for obj in objset.objects: # <-- Remove parentheses + if isinstance(obj, oe.spdx30.Annotation): + if hasattr(obj, "subject") and build._id == obj.subject._id: + found_annotations.append(obj) + + # Check each annotation separately to ensure exactly one occurrence of each + annotation1_count = 0 + annotation2_count = 0 + + for annotation in found_annotations: + if hasattr(annotation, "statement"): + if f"ANNOTATION1={ANNOTATION_VAR1}" in annotation.statement: + annotation1_count += 1 + self.logger.info(f"Found ANNOTATION1: {annotation.statement}") + if f"ANNOTATION2={ANNOTATION_VAR2}" in annotation.statement: + annotation2_count += 1 + self.logger.info(f"Found ANNOTATION2: {annotation.statement}") + + # Each annotation should appear exactly once + self.assertEqual( + annotation1_count, + 1, + f"Expected exactly 1 occurrence of ANNOTATION1, found {annotation1_count}", + ) + self.assertEqual( + annotation2_count, + 1, + f"Expected exactly 1 occurrence of ANNOTATION2, found {annotation2_count}", + )