From: Stefano Tondo <stondo@gmail.com>
To: openembedded-core@lists.openembedded.org
Cc: stefano.tondo.ext@siemens.com, adrian.freihofer@siemens.com,
Peter.Marko@siemens.com, jpewhacker@gmail.com,
Ross.Burton@arm.com
Subject: [PATCH 11/14] spdx30: Add rootfs version and dependency scope classification
Date: Sat, 21 Feb 2026 05:24:15 +0100 [thread overview]
Message-ID: <20260221042418.317535-12-stondo@gmail.com> (raw)
In-Reply-To: <20260221042418.317535-1-stondo@gmail.com>
From: Stefano Tondo <stefano.tondo.ext@siemens.com>
- Add software_packageVersion to rootfs component using DISTRO_VERSION
Fixes SBOM validation tools reporting missing version on root elements
- Add get_dependencies_by_scope() using Yocto's native DEPENDS/RDEPENDS
mechanism to classify dependencies by lifecycle scope:
- runtime: packages in RDEPENDS (from package manifest PKGDATA)
- build: packages in DEPENDS but not in RDEPENDS
- test: explicitly marked via SPDX_FORCE_TEST_SCOPE
This universal approach works for all ecosystems (C/C++, Rust, Go,
npm, Python, etc.) because Yocto's packaging system already separates
build and runtime dependencies.
- Read runtime dependencies from package manifests to capture
auto-detected shared library dependencies (e.g., libc6, libssl3)
- Fall back to recipe-level RDEPENDS if manifest unavailable
Signed-off-by: Stefano Tondo <stefano.tondo.ext@siemens.com>
---
meta/lib/oe/spdx30_tasks.py | 79 ++++++++++++++++++++++++++++++++++++-
1 file changed, 78 insertions(+), 1 deletion(-)
diff --git a/meta/lib/oe/spdx30_tasks.py b/meta/lib/oe/spdx30_tasks.py
index 12b8e68fbe..b028238304 100644
--- a/meta/lib/oe/spdx30_tasks.py
+++ b/meta/lib/oe/spdx30_tasks.py
@@ -1224,7 +1224,59 @@ def create_package_spdx(d):
common_objset.doc.creationInfo
)
+ def get_dependencies_by_scope(d, package):
+ """Classify dependencies by LifecycleScopeType using DEPENDS/RDEPENDS.
+
+ Reads runtime deps from package manifests (PKGDATA) to capture both
+ explicit RDEPENDS and auto-detected shared library dependencies.
+ Returns dict with 'runtime', 'build', and 'test' sets.
+ """
+ pn = d.getVar('PN')
+
+ all_build = set((d.getVar('DEPENDS') or '').split())
+
+ runtime = set()
+
+ try:
+ pkg_data = oe.packagedata.read_subpkgdata_dict(package, d)
+ rdepends_str = pkg_data.get('RDEPENDS', '')
+ rrecommends_str = pkg_data.get('RRECOMMENDS', '')
+
+ for dep in rdepends_str.split():
+ if dep and not dep.startswith('(') and not dep.endswith(')'):
+ runtime.add(dep)
+
+ for dep in rrecommends_str.split():
+ if dep and not dep.startswith('(') and not dep.endswith(')'):
+ runtime.add(dep)
+
+ bb.debug(2, f"Package {package}: runtime deps from manifest: {runtime}")
+ except Exception as e:
+ bb.warn(f"Could not read package manifest for {package}: {e}")
+ runtime.update((d.getVar('RDEPENDS:' + package) or '').split())
+ runtime.update((d.getVar('RRECOMMENDS:' + package) or '').split())
+
+ non_runtime = all_build - runtime
+
+ force_build = set((d.getVar('SPDX_FORCE_BUILD_SCOPE') or '').split())
+ force_test = set((d.getVar('SPDX_FORCE_TEST_SCOPE') or '').split())
+ force_runtime = set((d.getVar('SPDX_FORCE_RUNTIME_SCOPE') or '').split())
+
+ runtime = (runtime | force_runtime) - force_build - force_test
+ build = (non_runtime | force_build) - force_runtime - force_test
+ test = force_test
+
+ return {
+ 'runtime': runtime,
+ 'build': build,
+ 'test': test
+ }
+
runtime_spdx_deps = set()
+ build_spdx_deps = set()
+ test_spdx_deps = set()
+
+ deps_by_scope = get_dependencies_by_scope(d, package)
deps = bb.utils.explode_dep_versions2(localdata.getVar("RDEPENDS") or "")
seen_deps = set()
@@ -1256,7 +1308,15 @@ def create_package_spdx(d):
)
dep_package_cache[dep] = dep_spdx_package
- runtime_spdx_deps.add(dep_spdx_package)
+ # Determine scope based on universal classification
+ if dep in deps_by_scope['runtime'] or dep_pkg in deps_by_scope['runtime']:
+ runtime_spdx_deps.add(dep_spdx_package)
+ elif dep in deps_by_scope['test'] or dep_pkg in deps_by_scope['test']:
+ test_spdx_deps.add(dep_spdx_package)
+ else:
+ # If it's in RDEPENDS but not classified as runtime or test,
+ # treat as runtime (this shouldn't happen normally)
+ runtime_spdx_deps.add(dep_spdx_package)
seen_deps.add(dep)
if runtime_spdx_deps:
@@ -1267,6 +1327,22 @@ def create_package_spdx(d):
[oe.sbom30.get_element_link_id(dep) for dep in runtime_spdx_deps],
)
+ if build_spdx_deps:
+ pkg_objset.new_scoped_relationship(
+ [spdx_package],
+ oe.spdx30.RelationshipType.dependsOn,
+ oe.spdx30.LifecycleScopeType.build,
+ [oe.sbom30.get_element_link_id(dep) for dep in build_spdx_deps],
+ )
+
+ if test_spdx_deps:
+ pkg_objset.new_scoped_relationship(
+ [spdx_package],
+ oe.spdx30.RelationshipType.dependsOn,
+ oe.spdx30.LifecycleScopeType.test,
+ [oe.sbom30.get_element_link_id(dep) for dep in test_spdx_deps],
+ )
+
oe.sbom30.write_recipe_jsonld_doc(d, pkg_objset, "packages", deploydir)
oe.sbom30.write_recipe_jsonld_doc(d, common_objset, "common-package", deploydir)
@@ -1427,6 +1503,7 @@ def create_rootfs_spdx(d):
_id=objset.new_spdxid("rootfs", image_basename),
creationInfo=objset.doc.creationInfo,
name=image_basename,
+ software_packageVersion=d.getVar("DISTRO_VERSION") or "1.0",
software_primaryPurpose=oe.spdx30.software_SoftwarePurpose.archive,
)
)
--
2.53.0
next prev parent reply other threads:[~2026-02-21 4:24 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-21 4:24 [PATCH 00/14] spdx30: SBOM enrichment for PURL, metadata, and compliance Stefano Tondo
2026-02-21 4:24 ` [PATCH 01/14] spdx30: Add configurable file filtering support Stefano Tondo
2026-02-21 4:24 ` [PATCH 02/14] spdx30: Add supplier support for image and SDK SBOMs Stefano Tondo
2026-02-21 4:24 ` [PATCH 03/14] spdx30: Add ecosystem-specific PURL generation Stefano Tondo
2026-02-21 4:24 ` [PATCH 04/14] spdx30: Add version extraction from SRCREV for Git source components Stefano Tondo
2026-02-21 4:24 ` [PATCH 05/14] spdx30: Add SPDX_GIT_PURL_MAPPINGS for Git hosting Stefano Tondo
2026-02-21 4:24 ` [PATCH 06/14] sbom30: Fix object deduplication to preserve complete data Stefano Tondo
2026-02-21 4:24 ` [PATCH 07/14] spdx30: Enrich source downloads with external refs and PURLs Stefano Tondo
2026-02-21 4:24 ` [PATCH 08/14] spdx30: Include recipe base PURL in package external identifiers Stefano Tondo
2026-02-21 4:24 ` [PATCH 09/14] spdx30: Add image root metadata package with describes relationship Stefano Tondo
2026-02-21 4:24 ` [PATCH 10/14] spdx30_tasks: Fix non-deterministic BUILDNAME in image package version Stefano Tondo
2026-02-21 4:24 ` Stefano Tondo [this message]
2026-02-21 4:24 ` [PATCH 12/14] oeqa/selftest: Add test for download_location defensive handling Stefano Tondo
2026-02-21 4:24 ` [PATCH 13/14] spdx.py: Add test for version extraction patterns Stefano Tondo
2026-02-21 4:24 ` [PATCH 14/14] cve_check: Escape special characters in CPE 2.3 formatted strings Stefano Tondo
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=20260221042418.317535-12-stondo@gmail.com \
--to=stondo@gmail.com \
--cc=Peter.Marko@siemens.com \
--cc=Ross.Burton@arm.com \
--cc=adrian.freihofer@siemens.com \
--cc=jpewhacker@gmail.com \
--cc=openembedded-core@lists.openembedded.org \
--cc=stefano.tondo.ext@siemens.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.