public inbox for openembedded-core@lists.openembedded.org
 help / color / mirror / Atom feed
From: Joshua Watt <jpewhacker@gmail.com>
To: openembedded-core@lists.openembedded.org
Cc: benjamin.robin@bootlin.com, ross.burton@arm.com,
	Joshua Watt <JPEWhacker@gmail.com>
Subject: [OE-core][PATCH v3 3/8] spdx3: Add recipe SPDX data
Date: Thu, 26 Feb 2026 10:33:04 -0700	[thread overview]
Message-ID: <20260226173930.2847872-4-JPEWhacker@gmail.com> (raw)
In-Reply-To: <20260226173930.2847872-1-JPEWhacker@gmail.com>

Adds a new package to the SPDX output that represents the recipe data
for a given recipe. Importantly, this data contains only things that can
be determined statically from only the recipe, so it doesn't require
fetching or building anything. This means that build time dependencies
and CVE information for recipes can be analyzed without needing to
actually do any builds.

Sadly, license data cannot be included because NO_GENERIC_LICENSE means
that actual license text might only be available after do_fetch

Signed-off-by: Joshua Watt <JPEWhacker@gmail.com>
---
 meta/classes-global/sstate.bbclass            |   4 +-
 .../create-spdx-image-3.0.bbclass             |   4 +-
 .../create-spdx-sdk-3.0.bbclass               |   4 +-
 meta/classes-recipe/kernel.bbclass            |   2 +-
 meta/classes-recipe/nospdx.bbclass            |   1 +
 meta/classes/create-spdx-2.2.bbclass          |  12 +-
 meta/classes/create-spdx-3.0.bbclass          |  50 ++-
 meta/classes/spdx-common.bbclass              |  15 +-
 meta/lib/oe/spdx30_tasks.py                   | 402 ++++++++++++------
 9 files changed, 343 insertions(+), 151 deletions(-)

diff --git a/meta/classes-global/sstate.bbclass b/meta/classes-global/sstate.bbclass
index 2fd29d7323..95c44f404e 100644
--- a/meta/classes-global/sstate.bbclass
+++ b/meta/classes-global/sstate.bbclass
@@ -954,7 +954,7 @@ def sstate_checkhashes(sq_data, d, siginfo=False, currentcount=0, summary=True,
             extrapath = d.getVar("NATIVELSBSTRING") + "/"
         else:
             extrapath = ""
-        
+
         tname = bb.runqueue.taskname_from_tid(task)[3:]
 
         if tname in ["fetch", "unpack", "patch", "populate_lic", "preconfigure"] and splithashfn[2]:
@@ -1116,7 +1116,7 @@ def setscene_depvalid(task, taskdependees, notneeded, d, log=None):
 
     logit("Considering setscene task: %s" % (str(taskdependees[task])), log)
 
-    directtasks = ["do_populate_lic", "do_deploy_source_date_epoch", "do_shared_workdir", "do_stash_locale", "do_gcc_stash_builddir", "do_create_spdx", "do_deploy_archives"]
+    directtasks = ["do_populate_lic", "do_deploy_source_date_epoch", "do_shared_workdir", "do_stash_locale", "do_gcc_stash_builddir", "do_create_spdx", "do_create_recipe_spdx", "do_deploy_archives"]
 
     def isNativeCross(x):
         return x.endswith("-native") or "-cross-" in x or "-crosssdk" in x or x.endswith("-cross")
diff --git a/meta/classes-recipe/create-spdx-image-3.0.bbclass b/meta/classes-recipe/create-spdx-image-3.0.bbclass
index 636ab14eb0..15a91e90e2 100644
--- a/meta/classes-recipe/create-spdx-image-3.0.bbclass
+++ b/meta/classes-recipe/create-spdx-image-3.0.bbclass
@@ -34,7 +34,7 @@ addtask do_create_rootfs_spdx after do_rootfs before do_image
 SSTATETASKS += "do_create_rootfs_spdx"
 do_create_rootfs_spdx[sstate-inputdirs] = "${SPDXROOTFSDEPLOY}"
 do_create_rootfs_spdx[sstate-outputdirs] = "${DEPLOY_DIR_SPDX}"
-do_create_rootfs_spdx[recrdeptask] += "do_create_spdx do_create_package_spdx"
+do_create_rootfs_spdx[recrdeptask] += "do_create_recipe_spdx do_create_spdx do_create_package_spdx"
 do_create_rootfs_spdx[cleandirs] += "${SPDXROOTFSDEPLOY}"
 do_create_rootfs_spdx[file-checksums] += "${SPDX3_DEP_FILES}"
 
@@ -76,7 +76,7 @@ do_create_image_sbom_spdx[sstate-inputdirs] = "${SPDXIMAGEDEPLOYDIR}"
 do_create_image_sbom_spdx[sstate-outputdirs] = "${DEPLOY_DIR_IMAGE}"
 do_create_image_sbom_spdx[stamp-extra-info] = "${MACHINE_ARCH}"
 do_create_image_sbom_spdx[cleandirs] = "${SPDXIMAGEDEPLOYDIR}"
-do_create_image_sbom_spdx[recrdeptask] += "do_create_spdx do_create_package_spdx"
+do_create_image_sbom_spdx[recrdeptask] += "do_create_recipe_spdx do_create_spdx do_create_package_spdx"
 do_create_image_sbom_spdx[file-checksums] += "${SPDX3_DEP_FILES}"
 
 python do_create_image_sbom_spdx_setscene() {
diff --git a/meta/classes-recipe/create-spdx-sdk-3.0.bbclass b/meta/classes-recipe/create-spdx-sdk-3.0.bbclass
index e5f220cdfa..a4b8ed3bf9 100644
--- a/meta/classes-recipe/create-spdx-sdk-3.0.bbclass
+++ b/meta/classes-recipe/create-spdx-sdk-3.0.bbclass
@@ -5,14 +5,14 @@
 #
 # SPDX SDK tasks
 
-do_populate_sdk[recrdeptask] += "do_create_spdx do_create_package_spdx"
+do_populate_sdk[recrdeptask] += "do_create_recipe_spdx do_create_spdx do_create_package_spdx"
 do_populate_sdk[cleandirs] += "${SPDXSDKWORK}"
 do_populate_sdk[postfuncs] += "sdk_create_sbom"
 do_populate_sdk[file-checksums] += "${SPDX3_DEP_FILES}"
 POPULATE_SDK_POST_HOST_COMMAND:append:task-populate-sdk = " sdk_host_create_spdx"
 POPULATE_SDK_POST_TARGET_COMMAND:append:task-populate-sdk = " sdk_target_create_spdx"
 
-do_populate_sdk_ext[recrdeptask] += "do_create_spdx do_create_package_spdx"
+do_populate_sdk_ext[recrdeptask] += "do_create_recipe_spdx do_create_spdx do_create_package_spdx"
 do_populate_sdk_ext[cleandirs] += "${SPDXSDKEXTWORK}"
 do_populate_sdk_ext[postfuncs] += "sdk_ext_create_sbom"
 do_populate_sdk_ext[file-checksums] += "${SPDX3_DEP_FILES}"
diff --git a/meta/classes-recipe/kernel.bbclass b/meta/classes-recipe/kernel.bbclass
index f989b31c47..3a2c20dec2 100644
--- a/meta/classes-recipe/kernel.bbclass
+++ b/meta/classes-recipe/kernel.bbclass
@@ -904,7 +904,7 @@ python do_create_kernel_config_spdx() {
             bb.error(f"Failed to parse kernel config file: {e}")
 
         build, build_objset = oe.sbom30.find_root_obj_in_jsonld(
-            d, "recipes", f"recipe-{pn}", oe.spdx30.build_Build
+            d, "builds", f"build-{pn}", oe.spdx30.build_Build
         )
 
         kernel_build = build_objset.add_root(
diff --git a/meta/classes-recipe/nospdx.bbclass b/meta/classes-recipe/nospdx.bbclass
index b20e28218b..90e14442ba 100644
--- a/meta/classes-recipe/nospdx.bbclass
+++ b/meta/classes-recipe/nospdx.bbclass
@@ -5,6 +5,7 @@
 #
 
 deltask do_collect_spdx_deps
+deltask do_create_recipe_spdx
 deltask do_create_spdx
 deltask do_create_spdx_runtime
 deltask do_create_package_spdx
diff --git a/meta/classes/create-spdx-2.2.bbclass b/meta/classes/create-spdx-2.2.bbclass
index 65d10d86db..f1ee0f9afd 100644
--- a/meta/classes/create-spdx-2.2.bbclass
+++ b/meta/classes/create-spdx-2.2.bbclass
@@ -399,6 +399,15 @@ def get_license_list_version(license_data, d):
     return ".".join(license_data["licenseListVersion"].split(".")[:2])
 
 
+# This task is added for compatibility with tasks shared with SPDX 3, but
+# doesn't do anything
+do_create_recipe_spdx() {
+    :
+}
+do_create_recipe_spdx[noexec] = "1"
+addtask do_create_recipe_spdx after do_collect_spdx_deps
+
+
 python do_create_spdx() {
     from datetime import datetime, timezone
     import oe.sbom
@@ -594,7 +603,7 @@ python do_create_spdx() {
 }
 do_create_spdx[vardepsexclude] += "BB_NUMBER_THREADS"
 # NOTE: depending on do_unpack is a hack that is necessary to get it's dependencies for archive the source
-addtask do_create_spdx after do_package do_packagedata do_unpack do_collect_spdx_deps before do_populate_sdk do_build do_rm_work
+addtask do_create_spdx after do_create_recipe_spdx do_package do_packagedata do_unpack do_collect_spdx_deps before do_populate_sdk do_build do_rm_work
 
 SSTATETASKS += "do_create_spdx"
 do_create_spdx[sstate-inputdirs] = "${SPDXDEPLOY}"
@@ -605,6 +614,7 @@ python do_create_spdx_setscene () {
 }
 addtask do_create_spdx_setscene
 
+do_create_spdx[deptask] += "do_create_spdx"
 do_create_spdx[dirs] = "${SPDXWORK}"
 do_create_spdx[cleandirs] = "${SPDXDEPLOY} ${SPDXWORK}"
 do_create_spdx[depends] += " \
diff --git a/meta/classes/create-spdx-3.0.bbclass b/meta/classes/create-spdx-3.0.bbclass
index d4575d61c4..c5f6462c5a 100644
--- a/meta/classes/create-spdx-3.0.bbclass
+++ b/meta/classes/create-spdx-3.0.bbclass
@@ -159,11 +159,18 @@ SPDX3_DEP_FILES = "\
     ${SPDX_LICENSES}:True \
     "
 
-python do_create_spdx() {
+python do_create_recipe_spdx() {
     import oe.spdx30_tasks
-    oe.spdx30_tasks.create_spdx(d)
+    oe.spdx30_tasks.create_recipe_spdx(d)
 }
-do_create_spdx[vardeps] += "\
+addtask do_create_recipe_spdx after do_collect_spdx_deps
+
+SSTATETASKS += "do_create_recipe_spdx"
+do_create_recipe_spdx[sstate-inputdirs] = "${SPDXRECIPEDEPLOY}"
+do_create_recipe_spdx[sstate-outputdirs] = "${DEPLOY_DIR_SPDX}"
+do_create_recipe_spdx[file-checksums] += "${SPDX3_DEP_FILES}"
+do_create_recipe_spdx[cleandirs] = "${SPDXRECIPEDEPLOY}"
+do_create_recipe_spdx[vardeps] += "\
     SPDX_INCLUDE_BITBAKE_PARENT_BUILD \
     SPDX_PACKAGE_ADDITIONAL_PURPOSE \
     SPDX_PROFILES \
@@ -171,7 +178,18 @@ do_create_spdx[vardeps] += "\
     SPDX_UUID_NAMESPACE \
     "
 
+python do_create_recipe_spdx_setscene () {
+    sstate_setscene(d)
+}
+addtask do_create_recipe_spdx_setscene
+
+python do_create_spdx() {
+    import oe.spdx30_tasks
+    oe.spdx30_tasks.create_spdx(d)
+}
 addtask do_create_spdx after \
+    do_unpack \
+    do_create_recipe_spdx \
     do_collect_spdx_deps \
     do_deploy_source_date_epoch \
     do_populate_sysroot do_package do_packagedata \
@@ -181,18 +199,25 @@ SSTATETASKS += "do_create_spdx"
 do_create_spdx[sstate-inputdirs] = "${SPDXDEPLOY}"
 do_create_spdx[sstate-outputdirs] = "${DEPLOY_DIR_SPDX}"
 do_create_spdx[file-checksums] += "${SPDX3_DEP_FILES}"
-
-python do_create_spdx_setscene () {
-    sstate_setscene(d)
-}
-addtask do_create_spdx_setscene
-
+do_create_spdx[deptask] += "do_create_spdx"
 do_create_spdx[dirs] = "${SPDXWORK}"
 do_create_spdx[cleandirs] = "${SPDXDEPLOY} ${SPDXWORK}"
 do_create_spdx[depends] += " \
     ${PATCHDEPENDENCY} \
     ${@create_spdx_source_deps(d)} \
 "
+do_create_spdx[vardeps] += "\
+    SPDX_INCLUDE_BITBAKE_PARENT_BUILD \
+    SPDX_PACKAGE_ADDITIONAL_PURPOSE \
+    SPDX_PROFILES \
+    SPDX_NAMESPACE_PREFIX \
+    SPDX_UUID_NAMESPACE \
+    "
+
+python do_create_spdx_setscene () {
+    sstate_setscene(d)
+}
+addtask do_create_spdx_setscene
 
 python do_create_package_spdx() {
     import oe.spdx30_tasks
@@ -205,16 +230,15 @@ SSTATETASKS += "do_create_package_spdx"
 do_create_package_spdx[sstate-inputdirs] = "${SPDXRUNTIMEDEPLOY}"
 do_create_package_spdx[sstate-outputdirs] = "${DEPLOY_DIR_SPDX}"
 do_create_package_spdx[file-checksums] += "${SPDX3_DEP_FILES}"
+do_create_package_spdx[dirs] = "${SPDXRUNTIMEDEPLOY}"
+do_create_package_spdx[cleandirs] = "${SPDXRUNTIMEDEPLOY}"
+do_create_package_spdx[rdeptask] = "do_create_spdx"
 
 python do_create_package_spdx_setscene () {
     sstate_setscene(d)
 }
 addtask do_create_package_spdx_setscene
 
-do_create_package_spdx[dirs] = "${SPDXRUNTIMEDEPLOY}"
-do_create_package_spdx[cleandirs] = "${SPDXRUNTIMEDEPLOY}"
-do_create_package_spdx[rdeptask] = "do_create_spdx"
-
 python spdx30_build_started_handler () {
     import oe.spdx30_tasks
     d = e.data.createCopy()
diff --git a/meta/classes/spdx-common.bbclass b/meta/classes/spdx-common.bbclass
index 3110230c9e..2804c27b0b 100644
--- a/meta/classes/spdx-common.bbclass
+++ b/meta/classes/spdx-common.bbclass
@@ -23,6 +23,7 @@ SPDXDEPS = "${SPDXDIR}/deps.json"
 SPDX_TOOL_NAME ??= "oe-spdx-creator"
 SPDX_TOOL_VERSION ??= "1.0"
 
+SPDXRECIPEDEPLOY = "${SPDXDIR}/recipe-deploy"
 SPDXRUNTIMEDEPLOY = "${SPDXDIR}/runtime-deploy"
 
 SPDX_INCLUDE_SOURCES ??= "0"
@@ -72,6 +73,8 @@ def create_spdx_source_deps(d):
         # ourselves
         if oe.spdx_common.has_task(d, "do_unpack"):
             deps.append("%s:do_unpack" % pn)
+        if oe.spdx_common.has_task(d, "do_patch"):
+            deps.append("%s:do_patch" % pn)
 
         if oe.spdx_common.is_work_shared_spdx(d) and \
            oe.spdx_common.process_sources(d):
@@ -97,8 +100,8 @@ python do_collect_spdx_deps() {
     # This task calculates the build time dependencies of the recipe, and is
     # required because while a task can deptask on itself, those dependencies
     # do not show up in BB_TASKDEPDATA. To work around that, this task does the
-    # deptask on do_create_spdx and writes out the dependencies it finds, then
-    # do_create_spdx reads in the found dependencies when writing the actual
+    # deptask on do_create_recipe_spdx and writes out the dependencies it finds, then
+    # downstream tasks read in the found dependencies when writing the actual
     # SPDX document
     import json
     import oe.spdx_common
@@ -106,15 +109,13 @@ python do_collect_spdx_deps() {
 
     spdx_deps_file = Path(d.getVar("SPDXDEPS"))
 
-    deps = oe.spdx_common.collect_direct_deps(d, "do_create_spdx")
+    deps = oe.spdx_common.collect_direct_deps(d, "do_create_recipe_spdx")
 
     with spdx_deps_file.open("w") as f:
         json.dump(deps, f)
 }
-# NOTE: depending on do_unpack is a hack that is necessary to get it's dependencies for archive the source
-addtask do_collect_spdx_deps after do_unpack
-do_collect_spdx_deps[depends] += "${PATCHDEPENDENCY}"
-do_collect_spdx_deps[deptask] = "do_create_spdx"
+addtask do_collect_spdx_deps
+do_collect_spdx_deps[deptask] = "do_create_recipe_spdx"
 do_collect_spdx_deps[dirs] = "${SPDXDIR}"
 
 oe.spdx_common.collect_direct_deps[vardepsexclude] += "BB_TASKDEPDATA"
diff --git a/meta/lib/oe/spdx30_tasks.py b/meta/lib/oe/spdx30_tasks.py
index 99f2892dfb..a8b4525e3d 100644
--- a/meta/lib/oe/spdx30_tasks.py
+++ b/meta/lib/oe/spdx30_tasks.py
@@ -32,7 +32,9 @@ def set_timestamp_now(d, o, prop):
         delattr(o, prop)
 
 
-def add_license_expression(d, objset, license_expression, license_data):
+def add_license_expression(
+    d, objset, license_expression, license_data, search_objsets=[]
+):
     simple_license_text = {}
     license_text_map = {}
     license_ref_idx = 0
@@ -44,14 +46,15 @@ def add_license_expression(d, objset, license_expression, license_data):
         if name in simple_license_text:
             return simple_license_text[name]
 
-        lic = objset.find_filter(
-            oe.spdx30.simplelicensing_SimpleLicensingText,
-            name=name,
-        )
+        for o in [objset] + search_objsets:
+            lic = o.find_filter(
+                oe.spdx30.simplelicensing_SimpleLicensingText,
+                name=name,
+            )
 
-        if lic is not None:
-            simple_license_text[name] = lic
-            return lic
+            if lic is not None:
+                simple_license_text[name] = lic
+                return lic
 
         lic = objset.add(
             oe.spdx30.simplelicensing_SimpleLicensingText(
@@ -178,7 +181,9 @@ def add_package_files(
 
             # Check if file is compiled
             if check_compiled_sources:
-                if not oe.spdx_common.is_compiled_source(filename, compiled_sources, types):
+                if not oe.spdx_common.is_compiled_source(
+                    filename, compiled_sources, types
+                ):
                     continue
 
             spdx_file = objset.new_file(
@@ -293,17 +298,16 @@ def get_package_sources_from_debug(
     return dep_source_files
 
 
-def collect_dep_objsets(d, build):
+def collect_dep_objsets(d, subdir, fn_prefix, obj_type, **attr_filter):
     deps = oe.spdx_common.get_spdx_deps(d)
 
     dep_objsets = []
-    dep_builds = set()
+    dep_objs = set()
 
-    dep_build_spdxids = set()
     for dep in deps:
         bb.debug(1, "Fetching SPDX for dependency %s" % (dep.pn))
-        dep_build, dep_objset = oe.sbom30.find_root_obj_in_jsonld(
-            d, "recipes", "recipe-" + dep.pn, oe.spdx30.build_Build
+        dep_obj, dep_objset = oe.sbom30.find_root_obj_in_jsonld(
+            d, subdir, fn_prefix + dep.pn, obj_type, **attr_filter
         )
         # If the dependency is part of the taskhash, return it to be linked
         # against. Otherwise, it cannot be linked against because this recipe
@@ -311,10 +315,10 @@ def collect_dep_objsets(d, build):
         if dep.in_taskhash:
             dep_objsets.append(dep_objset)
 
-        # The build _can_ be linked against (by alias)
-        dep_builds.add(dep_build)
+        # The object _can_ be linked against (by alias)
+        dep_objs.add(dep_obj)
 
-    return dep_objsets, dep_builds
+    return dep_objsets, dep_objs
 
 
 def index_sources_by_hash(sources, dest):
@@ -423,9 +427,7 @@ def add_download_files(d, objset):
             if fd.method.supports_checksum(fd):
                 # TODO Need something better than hard coding this
                 for checksum_id in ["sha256", "sha1"]:
-                    expected_checksum = getattr(
-                        fd, "%s_expected" % checksum_id, None
-                    )
+                    expected_checksum = getattr(fd, "%s_expected" % checksum_id, None)
                     if expected_checksum is None:
                         continue
 
@@ -462,50 +464,96 @@ def set_purposes(d, element, *var_names, force_purposes=[]):
     ]
 
 
-def create_spdx(d):
-    def set_var_field(var, obj, name, package=None):
-        val = None
-        if package:
-            val = d.getVar("%s:%s" % (var, package))
+def set_purls(spdx_package, purls):
+    if purls:
+        spdx_package.software_packageUrl = purls[0]
 
-        if not val:
-            val = d.getVar(var)
+    for p in sorted(set(purls)):
+        spdx_package.externalIdentifier.append(
+            oe.spdx30.ExternalIdentifier(
+                externalIdentifierType=oe.spdx30.ExternalIdentifierType.packageUrl,
+                identifier=p,
+            )
+        )
 
-        if val:
-            setattr(obj, name, val)
+
+def create_recipe_spdx(d):
+    deploydir = Path(d.getVar("SPDXRECIPEDEPLOY"))
+    deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX"))
+    pn = d.getVar("PN")
 
     license_data = oe.spdx_common.load_spdx_license_data(d)
 
-    deploydir = Path(d.getVar("SPDXDEPLOY"))
-    deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX"))
-    spdx_workdir = Path(d.getVar("SPDXWORK"))
-    include_sources = d.getVar("SPDX_INCLUDE_SOURCES") == "1"
-    pkg_arch = d.getVar("SSTATE_PKGARCH")
-    is_native = bb.data.inherits_class("native", d) or bb.data.inherits_class(
-        "cross", d
-    )
     include_vex = d.getVar("SPDX_INCLUDE_VEX")
     if not include_vex in ("none", "current", "all"):
         bb.fatal("SPDX_INCLUDE_VEX must be one of 'none', 'current', 'all'")
 
-    build_objset = oe.sbom30.ObjectSet.new_objset(d, "recipe-" + d.getVar("PN"))
+    recipe_objset = oe.sbom30.ObjectSet.new_objset(d, "static-" + pn)
 
-    build = build_objset.new_task_build("recipe", "recipe")
-    build_objset.set_element_alias(build)
+    recipe = recipe_objset.add_root(
+        oe.spdx30.software_Package(
+            _id=recipe_objset.new_spdxid("recipe", pn),
+            creationInfo=recipe_objset.doc.creationInfo,
+            name=d.getVar("PN"),
+            software_packageVersion=d.getVar("PV"),
+            software_primaryPurpose=oe.spdx30.software_SoftwarePurpose.specification,
+            software_sourceInfo=json.dumps(
+                {
+                    "FILENAME": os.path.basename(d.getVar("FILE")),
+                    "FILE_LAYERNAME": d.getVar("FILE_LAYERNAME"),
+                },
+                separators=(",", ":"),
+            ),
+        )
+    )
 
-    build_objset.doc.rootElement.append(build)
+    set_purls(recipe, (d.getVar("SPDX_PACKAGE_URLS") or "").split())
+
+    # TODO: This doesn't work before do_unpack because the license text has to
+    # be available for recipes with NO_GENERIC_LICENSE
+    # recipe_spdx_license = add_license_expression(
+    #    d,
+    #    recipe_objset,
+    #    d.getVar("LICENSE"),
+    #    license_data,
+    # )
+    # recipe_objset.new_relationship(
+    #    [recipe],
+    #    oe.spdx30.RelationshipType.hasDeclaredLicense,
+    #    [oe.sbom30.get_element_link_id(recipe_spdx_license)],
+    # )
+
+    if val := d.getVar("HOMEPAGE"):
+        recipe.software_homePage = val
+
+    if val := d.getVar("SUMMARY"):
+        recipe.summary = val
+
+    if val := d.getVar("DESCRIPTION"):
+        recipe.description = val
+
+    for cpe_id in oe.cve_check.get_cpe_ids(
+        d.getVar("CVE_PRODUCT"), d.getVar("CVE_VERSION")
+    ):
+        recipe.externalIdentifier.append(
+            oe.spdx30.ExternalIdentifier(
+                externalIdentifierType=oe.spdx30.ExternalIdentifierType.cpe23,
+                identifier=cpe_id,
+            )
+        )
 
-    build_objset.set_is_native(is_native)
+    dep_objsets, dep_recipes = collect_dep_objsets(
+        d, "static", "static-", oe.spdx30.software_Package
+    )
 
-    for var in (d.getVar("SPDX_CUSTOM_ANNOTATION_VARS") or "").split():
-        build_objset.new_annotation(
-            build,
-            "%s=%s" % (var, d.getVar(var)),
-            oe.spdx30.AnnotationType.other,
+    if dep_recipes:
+        recipe_objset.new_scoped_relationship(
+            [recipe],
+            oe.spdx30.RelationshipType.dependsOn,
+            oe.spdx30.LifecycleScopeType.build,
+            sorted(oe.sbom30.get_element_link_id(dep) for dep in dep_recipes),
         )
 
-    build_inputs = set()
-
     # Add CVEs
     cve_by_status = {}
     if include_vex != "none":
@@ -514,7 +562,7 @@ def create_spdx(d):
             decoded_status = {
                 "mapping": patched_cve["abbrev-status"],
                 "detail": patched_cve["status"],
-                "description": patched_cve.get("justification", None)
+                "description": patched_cve.get("justification", None),
             }
 
             # If this CVE is fixed upstream, skip it unless all CVEs are
@@ -531,8 +579,7 @@ def create_spdx(d):
                 bb.debug(1, "Skipping %s since it is already fixed upstream" % cve)
                 continue
 
-            spdx_cve = build_objset.new_cve_vuln(cve)
-            build_objset.set_element_alias(spdx_cve)
+            spdx_cve = recipe_objset.new_cve_vuln(cve)
 
             cve_by_status.setdefault(decoded_status["mapping"], {})[cve] = (
                 spdx_cve,
@@ -540,13 +587,118 @@ def create_spdx(d):
                 decoded_status["description"],
             )
 
+    all_cves = set()
+    for status, cves in cve_by_status.items():
+        for cve, items in cves.items():
+            spdx_cve, detail, description = items
+            spdx_cve_id = oe.sbom30.get_element_link_id(spdx_cve)
+
+            all_cves.add(spdx_cve)
+
+            if status == "Patched":
+                recipe_objset.new_vex_patched_relationship([spdx_cve_id], [recipe])
+            elif status == "Unpatched":
+                recipe_objset.new_vex_unpatched_relationship([spdx_cve_id], [recipe])
+            elif status == "Ignored":
+                spdx_vex = recipe_objset.new_vex_ignored_relationship(
+                    [spdx_cve_id],
+                    [recipe],
+                    impact_statement=description,
+                )
+
+                vex_just_type = d.getVarFlag("CVE_CHECK_VEX_JUSTIFICATION", detail)
+                if vex_just_type:
+                    if (
+                        vex_just_type
+                        not in oe.spdx30.security_VexJustificationType.NAMED_INDIVIDUALS
+                    ):
+                        bb.fatal(
+                            f"Unknown vex justification '{vex_just_type}', detail '{detail}', for ignored {cve}"
+                        )
+
+                    for v in spdx_vex:
+                        v.security_justificationType = (
+                            oe.spdx30.security_VexJustificationType.NAMED_INDIVIDUALS[
+                                vex_just_type
+                            ]
+                        )
+
+            elif status == "Unknown":
+                bb.note(f"Skipping {cve} with status 'Unknown'")
+            else:
+                bb.fatal(f"Unknown {cve} status '{status}'")
+
+    if all_cves:
+        recipe_objset.new_relationship(
+            [recipe],
+            oe.spdx30.RelationshipType.hasAssociatedVulnerability,
+            sorted(list(all_cves)),
+        )
+
+    oe.sbom30.write_recipe_jsonld_doc(d, recipe_objset, "static", deploydir)
+
+
+def load_recipe_spdx(d):
+
+    return oe.sbom30.find_root_obj_in_jsonld(
+        d,
+        "static",
+        "static-" + d.getVar("PN"),
+        oe.spdx30.software_Package,
+    )
+
+
+def create_spdx(d):
+    def set_var_field(var, obj, name, package=None):
+        val = None
+        if package:
+            val = d.getVar("%s:%s" % (var, package))
+
+        if not val:
+            val = d.getVar(var)
+
+        if val:
+            setattr(obj, name, val)
+
+    license_data = oe.spdx_common.load_spdx_license_data(d)
+
+    pn = d.getVar("PN")
+    deploydir = Path(d.getVar("SPDXDEPLOY"))
+    deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX"))
+    spdx_workdir = Path(d.getVar("SPDXWORK"))
+    include_sources = d.getVar("SPDX_INCLUDE_SOURCES") == "1"
+    pkg_arch = d.getVar("SSTATE_PKGARCH")
+    is_native = bb.data.inherits_class("native", d) or bb.data.inherits_class(
+        "cross", d
+    )
+
+    recipe, recipe_objset = load_recipe_spdx(d)
+
+    build_objset = oe.sbom30.ObjectSet.new_objset(d, "build-" + pn)
+
+    build = build_objset.new_task_build("recipe", "recipe")
+    build_objset.set_element_alias(build)
+
+    build_objset.doc.rootElement.append(build)
+
+    build_objset.set_is_native(is_native)
+
+    for var in (d.getVar("SPDX_CUSTOM_ANNOTATION_VARS") or "").split():
+        build_objset.new_annotation(
+            build,
+            "%s=%s" % (var, d.getVar(var)),
+            oe.spdx30.AnnotationType.other,
+        )
+
+    build_inputs = set()
+
     cpe_ids = oe.cve_check.get_cpe_ids(d.getVar("CVE_PRODUCT"), d.getVar("CVE_VERSION"))
 
     source_files = add_download_files(d, build_objset)
     build_inputs |= source_files
 
     recipe_spdx_license = add_license_expression(
-        d, build_objset, d.getVar("LICENSE"), license_data
+        d, build_objset, d.getVar("LICENSE"), license_data, [recipe_objset]
     )
     build_objset.new_relationship(
         source_files,
@@ -575,7 +727,10 @@ def create_spdx(d):
         build_inputs |= files
         index_sources_by_hash(files, dep_sources)
 
-    dep_objsets, dep_builds = collect_dep_objsets(d, build)
+    dep_objsets, dep_builds = collect_dep_objsets(
+        d, "builds", "build-", oe.spdx30.build_Build
+    )
+
     if dep_builds:
         build_objset.new_scoped_relationship(
             [build],
@@ -587,6 +742,22 @@ def create_spdx(d):
     debug_source_ids = set()
     source_hash_cache = {}
 
+    # Collect all VEX statements from the recipe
+    vex_statements = {}
+    for rel in recipe_objset.foreach_filter(
+        oe.spdx30.Relationship,
+        relationshipType=oe.spdx30.RelationshipType.hasAssociatedVulnerability,
+    ):
+        for cve in rel.to:
+            vex_statements[cve] = []
+
+    for cve in vex_statements.keys():
+        for rel in recipe_objset.foreach_filter(
+            oe.spdx30.security_VexVulnAssessmentRelationship,
+            from_=cve,
+        ):
+            vex_statements[cve].append(rel)
+
     # Write out the package SPDX data now. It is not complete as we cannot
     # write the runtime data, so write it to a staging area and a later task
     # will write out the final collection
@@ -645,16 +816,7 @@ def create_spdx(d):
                 or ""
             ).split()
 
-            if purls:
-                spdx_package.software_packageUrl = purls[0]
-
-            for p in sorted(set(purls)):
-                spdx_package.externalIdentifier.append(
-                    oe.spdx30.ExternalIdentifier(
-                        externalIdentifierType=oe.spdx30.ExternalIdentifierType.packageUrl,
-                        identifier=p,
-                    )
-                )
+            set_purls(spdx_package, purls)
 
             pkg_objset.new_scoped_relationship(
                 [oe.sbom30.get_element_link_id(build)],
@@ -663,6 +825,13 @@ def create_spdx(d):
                 [spdx_package],
             )
 
+            pkg_objset.new_scoped_relationship(
+                [oe.sbom30.get_element_link_id(recipe)],
+                oe.spdx30.RelationshipType.generates,
+                oe.spdx30.LifecycleScopeType.build,
+                [spdx_package],
+            )
+
             for cpe_id in cpe_ids:
                 spdx_package.externalIdentifier.append(
                     oe.spdx30.ExternalIdentifier(
@@ -696,7 +865,11 @@ def create_spdx(d):
             package_license = d.getVar("LICENSE:%s" % package)
             if package_license and package_license != d.getVar("LICENSE"):
                 package_spdx_license = add_license_expression(
-                    d, build_objset, package_license, license_data
+                    d,
+                    build_objset,
+                    package_license,
+                    license_data,
+                    [recipe_objset],
                 )
             else:
                 package_spdx_license = recipe_spdx_license
@@ -721,58 +894,41 @@ def create_spdx(d):
                     [oe.sbom30.get_element_link_id(concluded_spdx_license)],
                 )
 
-            # NOTE: CVE Elements live in the recipe collection
-            all_cves = set()
-            for status, cves in cve_by_status.items():
-                for cve, items in cves.items():
-                    spdx_cve, detail, description = items
-                    spdx_cve_id = oe.sbom30.get_element_link_id(spdx_cve)
-
-                    all_cves.add(spdx_cve_id)
+            # Copy CVEs from recipe
+            if vex_statements:
+                pkg_objset.new_relationship(
+                    [spdx_package],
+                    oe.spdx30.RelationshipType.hasAssociatedVulnerability,
+                    sorted(
+                        oe.sbom30.get_element_link_id(cve)
+                        for cve in vex_statements.keys()
+                    ),
+                )
 
-                    if status == "Patched":
+            for cve, vexes in vex_statements.items():
+                for vex in vexes:
+                    if vex.relationshipType == oe.spdx30.RelationshipType.fixedIn:
                         pkg_objset.new_vex_patched_relationship(
-                            [spdx_cve_id], [spdx_package]
+                            [oe.sbom30.get_element_link_id(cve)], [spdx_package]
                         )
-                    elif status == "Unpatched":
+                    elif vex.relationshipType == oe.spdx30.RelationshipType.affects:
                         pkg_objset.new_vex_unpatched_relationship(
-                            [spdx_cve_id], [spdx_package]
+                            [oe.sbom30.get_element_link_id(cve)], [spdx_package]
                         )
-                    elif status == "Ignored":
+                    elif (
+                        vex.relationshipType == oe.spdx30.RelationshipType.doesNotAffect
+                    ):
                         spdx_vex = pkg_objset.new_vex_ignored_relationship(
-                            [spdx_cve_id],
+                            [oe.sbom30.get_element_link_id(cve)],
                             [spdx_package],
-                            impact_statement=description,
+                            impact_statement=vex.security_impactStatement,
                         )
 
-                        vex_just_type = d.getVarFlag(
-                            "CVE_CHECK_VEX_JUSTIFICATION", detail
-                        )
-                        if vex_just_type:
-                            if (
-                                vex_just_type
-                                not in oe.spdx30.security_VexJustificationType.NAMED_INDIVIDUALS
-                            ):
-                                bb.fatal(
-                                    f"Unknown vex justification '{vex_just_type}', detail '{detail}', for ignored {cve}"
-                                )
-
+                        if vex.security_justificationType:
                             for v in spdx_vex:
-                                v.security_justificationType = oe.spdx30.security_VexJustificationType.NAMED_INDIVIDUALS[
-                                    vex_just_type
-                                ]
-
-                    elif status == "Unknown":
-                        bb.note(f"Skipping {cve} with status 'Unknown'")
-                    else:
-                        bb.fatal(f"Unknown {cve} status '{status}'")
-
-            if all_cves:
-                pkg_objset.new_relationship(
-                    [spdx_package],
-                    oe.spdx30.RelationshipType.hasAssociatedVulnerability,
-                    sorted(list(all_cves)),
-                )
+                                v.security_justificationType = (
+                                    vex.security_justificationType
+                                )
 
             bb.debug(1, "Adding package files to SPDX for package %s" % pkg_name)
             package_files = add_package_files(
@@ -851,14 +1007,15 @@ def create_spdx(d):
                 status = "enabled" if feature in enabled else "disabled"
                 build.build_parameter.append(
                     oe.spdx30.DictionaryEntry(
-                        key=f"PACKAGECONFIG:{feature}",
-                        value=status
+                        key=f"PACKAGECONFIG:{feature}", value=status
                     )
                 )
 
-            bb.note(f"Added PACKAGECONFIG entries: {len(enabled)} enabled, {len(disabled)} disabled")
+            bb.note(
+                f"Added PACKAGECONFIG entries: {len(enabled)} enabled, {len(disabled)} disabled"
+            )
 
-    oe.sbom30.write_recipe_jsonld_doc(d, build_objset, "recipes", deploydir)
+    oe.sbom30.write_recipe_jsonld_doc(d, build_objset, "builds", deploydir)
 
 
 def create_package_spdx(d):
@@ -1197,17 +1354,17 @@ def create_image_spdx(d):
             image_path = image_deploy_dir / image_filename
             if os.path.isdir(image_path):
                 a = add_package_files(
-                        d,
-                        objset,
-                        image_path,
-                        lambda file_counter: objset.new_spdxid(
-                            "imagefile", str(file_counter)
-                        ),
-                        lambda filepath: [],
-                        license_data=None,
-                        ignore_dirs=[],
-                        ignore_top_level_dirs=[],
-                        archive=None,
+                    d,
+                    objset,
+                    image_path,
+                    lambda file_counter: objset.new_spdxid(
+                        "imagefile", str(file_counter)
+                    ),
+                    lambda filepath: [],
+                    license_data=None,
+                    ignore_dirs=[],
+                    ignore_top_level_dirs=[],
+                    archive=None,
                 )
                 artifacts.extend(a)
             else:
@@ -1234,7 +1391,6 @@ def create_image_spdx(d):
 
                 set_timestamp_now(d, a, "builtTime")
 
-
         if artifacts:
             objset.new_scoped_relationship(
                 [image_build],
-- 
2.53.0



  parent reply	other threads:[~2026-02-26 17:39 UTC|newest]

Thread overview: 113+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-02-20 15:40 [OE-core][PATCH 0/9] Add SPDX 3 Recipe Information Joshua Watt
2026-02-20 15:40 ` [OE-core][PATCH 1/9] llvm-project-source: Use allarch.bbclass Joshua Watt
2026-02-20 15:40 ` [OE-core][PATCH 2/9] gcc-source: " Joshua Watt
2026-02-20 15:40 ` [OE-core][PATCH 3/9] spdx3: Add recipe SPDX data Joshua Watt
2026-02-22  7:59   ` Mathieu Dubois-Briand
2026-02-20 15:40 ` [OE-core][PATCH 4/9] spdx3: Add recipe SBoM task Joshua Watt
2026-02-20 15:40 ` [OE-core][PATCH 5/9] spdx3: Add is-native property Joshua Watt
2026-02-20 15:40 ` [OE-core][PATCH 6/9] spdx30: Include patch file information in VEX Joshua Watt
2026-02-20 15:40 ` [OE-core][PATCH 7/9] spdx: De-duplicate CreationInfo Joshua Watt
2026-02-20 15:40 ` [OE-core][PATCH 8/9] spdx: Ignore ASSUME_PROVIDED recipes Joshua Watt
2026-02-20 15:40 ` [OE-core][PATCH 9/9] spdx_common: Check for dependent task in task flags Joshua Watt
2026-02-24 23:00 ` [OE-core][PATCH v2 0/8] Add SPDX 3 Recipe Information Joshua Watt
2026-02-24 23:00   ` [OE-core][PATCH v2 1/8] llvm-project-source: Use allarch.bbclass Joshua Watt
2026-02-24 23:00   ` [OE-core][PATCH v2 2/8] gcc-source: " Joshua Watt
2026-02-24 23:00   ` [OE-core][PATCH v2 3/8] spdx3: Add recipe SPDX data Joshua Watt
2026-02-24 23:00   ` [OE-core][PATCH v2 4/8] spdx3: Add recipe SBoM task Joshua Watt
2026-02-24 23:00   ` [OE-core][PATCH v2 5/8] spdx3: Add is-native property Joshua Watt
2026-02-24 23:00   ` [OE-core][PATCH v2 6/8] spdx30: Include patch file information in VEX Joshua Watt
2026-02-24 23:00   ` [OE-core][PATCH v2 7/8] spdx: De-duplicate CreationInfo Joshua Watt
2026-02-24 23:00   ` [OE-core][PATCH v2 8/8] spdx_common: Check for dependent task in task flags Joshua Watt
2026-02-26 12:52   ` [OE-core][PATCH v2 0/8] Add SPDX 3 Recipe Information Mathieu Dubois-Briand
2026-02-26 14:27     ` Benjamin Robin
2026-02-26 15:09       ` Benjamin Robin
2026-02-26 15:41         ` Joshua Watt
2026-02-26 17:33   ` [OE-core][PATCH v3 " Joshua Watt
2026-02-26 17:33     ` [OE-core][PATCH v3 1/8] llvm-project-source: Use allarch.bbclass Joshua Watt
2026-02-26 17:33     ` [OE-core][PATCH v3 2/8] gcc-source: " Joshua Watt
2026-02-26 17:33     ` Joshua Watt [this message]
2026-02-26 17:33     ` [OE-core][PATCH v3 4/8] spdx3: Add recipe SBoM task Joshua Watt
2026-02-26 17:33     ` [OE-core][PATCH v3 5/8] spdx3: Add is-native property Joshua Watt
2026-02-26 17:33     ` [OE-core][PATCH v3 6/8] spdx30: Include patch file information in VEX Joshua Watt
2026-02-26 17:33     ` [OE-core][PATCH v3 7/8] spdx: De-duplicate CreationInfo Joshua Watt
2026-02-26 17:33     ` [OE-core][PATCH v3 8/8] spdx_common: Check for dependent task in task flags Joshua Watt
2026-02-27  7:32     ` [OE-core][PATCH v3 0/8] Add SPDX 3 Recipe Information Mathieu Dubois-Briand
2026-03-03  0:43     ` [OE-core][PATCH v4 0/9] " Joshua Watt
2026-03-03  0:43       ` [OE-core][PATCH v4 1/9] llvm-project-source: Use allarch.bbclass Joshua Watt
2026-03-03  0:43       ` [OE-core][PATCH v4 2/9] gcc-source: " Joshua Watt
2026-03-03  0:43       ` [OE-core][PATCH v4 3/9] spdx3: Add recipe SPDX data Joshua Watt
2026-03-03  0:43       ` [OE-core][PATCH v4 4/9] spdx3: Add recipe SBoM task Joshua Watt
2026-03-03  0:43       ` [OE-core][PATCH v4 5/9] spdx3: Add is-native property Joshua Watt
2026-03-03  0:43       ` [OE-core][PATCH v4 6/9] spdx30: Include patch file information in VEX Joshua Watt
2026-03-03  0:43       ` [OE-core][PATCH v4 7/9] spdx: De-duplicate CreationInfo Joshua Watt
2026-03-03  0:43       ` [OE-core][PATCH v4 8/9] spdx_common: Check for dependent task in task flags Joshua Watt
2026-03-03  0:43       ` [OE-core][PATCH v4 9/9] spdx30: Skip install package CVE information Joshua Watt
2026-03-03 10:17       ` [OE-core][PATCH v4 0/9] Add SPDX 3 Recipe Information Antonin Godard
2026-03-03 14:08       ` Mathieu Dubois-Briand
2026-03-04 16:44       ` [OE-core][PATCH v5 00/13] " Joshua Watt
2026-03-04 16:44         ` [OE-core][PATCH v5 01/13] llvm-project-source: Use allarch.bbclass Joshua Watt
2026-03-04 16:44         ` [OE-core][PATCH v5 02/13] gcc-source: " Joshua Watt
2026-03-04 16:44         ` [OE-core][PATCH v5 03/13] spdx3: Add recipe SPDX data Joshua Watt
2026-03-04 16:44         ` [OE-core][PATCH v5 04/13] spdx3: Add recipe SBoM task Joshua Watt
2026-03-04 16:44         ` [OE-core][PATCH v5 05/13] spdx3: Add is-native property Joshua Watt
2026-03-04 16:44         ` [OE-core][PATCH v5 06/13] spdx30: Include patch file information in VEX Joshua Watt
2026-03-04 16:44         ` [OE-core][PATCH v5 07/13] spdx: De-duplicate CreationInfo Joshua Watt
2026-03-04 16:44         ` [OE-core][PATCH v5 08/13] spdx_common: Check for dependent task in task flags Joshua Watt
2026-03-04 16:44         ` [OE-core][PATCH v5 09/13] spdx30: Skip install package CVE information Joshua Watt
2026-03-04 16:44         ` [OE-core][PATCH v5 10/13] dummy-sdk-package: Disable SPDX Joshua Watt
2026-03-04 16:44         ` [OE-core][PATCH v5 11/13] spdx: Remove fatal errors for missing providers Joshua Watt
2026-03-04 16:44         ` [OE-core][PATCH v5 12/13] spdx3: Use common variable for vardeps Joshua Watt
2026-03-04 16:44         ` [OE-core][PATCH v5 13/13] glibc-testsuite: Do not generate SPDX Joshua Watt
2026-03-05 19:59         ` [OE-core][PATCH v5 00/13] Add SPDX 3 Recipe Information Mathieu Dubois-Briand
2026-03-10 18:38         ` [OE-core][PATCH v6 00/15] " Joshua Watt
2026-03-10 18:38           ` [OE-core][PATCH v6 01/15] llvm-project-source: Use allarch.bbclass Joshua Watt
2026-03-10 18:38           ` [OE-core][PATCH v6 02/15] gcc-source: " Joshua Watt
2026-03-10 18:38           ` [OE-core][PATCH v6 03/15] spdx3: Add recipe SPDX data Joshua Watt
2026-03-12 11:43             ` Richard Purdie
2026-03-12 14:11               ` Joshua Watt
2026-03-12 17:50                 ` Richard Purdie
2026-03-10 18:38           ` [OE-core][PATCH v6 04/15] spdx3: Add recipe SBoM task Joshua Watt
2026-03-12 11:50             ` Richard Purdie
2026-03-12 14:12               ` Joshua Watt
2026-03-10 18:38           ` [OE-core][PATCH v6 05/15] spdx3: Add is-native property Joshua Watt
2026-03-10 18:38           ` [OE-core][PATCH v6 06/15] spdx30: Include patch file information in VEX Joshua Watt
2026-03-10 18:38           ` [OE-core][PATCH v6 07/15] spdx: De-duplicate CreationInfo Joshua Watt
2026-03-10 18:38           ` [OE-core][PATCH v6 08/15] spdx_common: Check for dependent task in task flags Joshua Watt
2026-03-10 18:38           ` [OE-core][PATCH v6 09/15] spdx30: Skip install package CVE information Joshua Watt
2026-03-12 11:55             ` Richard Purdie
2026-03-12 14:15               ` Joshua Watt
2026-03-12 15:52                 ` Richard Purdie
2026-03-12 16:11                   ` Joshua Watt
2026-03-10 18:38           ` [OE-core][PATCH v6 10/15] dummy-sdk-package: Disable SPDX Joshua Watt
2026-03-12 11:59             ` Richard Purdie
2026-03-12 14:24               ` Joshua Watt
2026-03-12 15:58                 ` Richard Purdie
2026-03-12 16:06                   ` Joshua Watt
2026-03-12 16:43                     ` Joshua Watt
2026-03-12 18:02                       ` Joshua Watt
2026-03-12 20:34                         ` Joshua Watt
2026-03-10 18:38           ` [OE-core][PATCH v6 11/15] spdx: Remove fatal errors for missing providers Joshua Watt
2026-03-10 18:38           ` [OE-core][PATCH v6 12/15] spdx3: Use common variable for vardeps Joshua Watt
2026-03-10 18:38           ` [OE-core][PATCH v6 13/15] glibc-testsuite: Do not generate SPDX Joshua Watt
2026-03-10 18:38           ` [OE-core][PATCH v6 14/15] spdx: Remove do_collect_spdx_deps task Joshua Watt
2026-03-11 13:55           ` [OE-core][PATCH v6 00/15] Add SPDX 3 Recipe Information Mathieu Dubois-Briand
2026-03-11 16:39             ` Joshua Watt
2026-03-11 19:33               ` Mathieu Dubois-Briand
2026-03-11 22:56                 ` Joshua Watt
2026-03-18 13:44           ` [OE-core][PATCH v7 00/12] " Joshua Watt
2026-03-18 13:44             ` [OE-core][PATCH v7 01/12] spdx3: Add recipe SPDX data Joshua Watt
2026-03-18 13:44             ` [OE-core][PATCH v7 02/12] spdx3: Add recipe SBoM task Joshua Watt
2026-03-18 13:44             ` [OE-core][PATCH v7 03/12] spdx3: Add is-native property Joshua Watt
2026-03-18 13:44             ` [OE-core][PATCH v7 04/12] spdx30: Include patch file information in VEX Joshua Watt
2026-03-18 13:44             ` [OE-core][PATCH v7 05/12] spdx: De-duplicate CreationInfo Joshua Watt
2026-03-18 13:44             ` [OE-core][PATCH v7 06/12] spdx_common: Check for dependent task in task flags Joshua Watt
2026-03-18 13:44             ` [OE-core][PATCH v7 07/12] spdx30: Remove package VEX Joshua Watt
2026-03-18 13:44             ` [OE-core][PATCH v7 08/12] spdx: Remove fatal errors for missing providers Joshua Watt
2026-03-18 13:44             ` [OE-core][PATCH v7 09/12] spdx3: Use common variable for vardeps Joshua Watt
2026-03-18 13:44             ` [OE-core][PATCH v7 10/12] glibc-testsuite: Do not generate SPDX Joshua Watt
2026-03-18 13:44             ` [OE-core][PATCH v7 11/12] spdx: Remove do_collect_spdx_deps task Joshua Watt
2026-03-18 13:49             ` [OE-core][PATCH v7 00/12] Add SPDX 3 Recipe Information Joshua Watt
2026-03-19  7:07               ` Mathieu Dubois-Briand
2026-03-19 12:02                 ` Mathieu Dubois-Briand
2026-03-19 21:55                 ` Joshua Watt
2026-03-19 22:14                   ` Richard Purdie

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260226173930.2847872-4-JPEWhacker@gmail.com \
    --to=jpewhacker@gmail.com \
    --cc=benjamin.robin@bootlin.com \
    --cc=openembedded-core@lists.openembedded.org \
    --cc=ross.burton@arm.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox