From: Stefano Tondo <[email protected]> - 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 <[email protected]> --- 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
-=-=-=-=-=-=-=-=-=-=-=- Links: You receive all messages sent to this group. View/Reply Online (#231591): https://lists.openembedded.org/g/openembedded-core/message/231591 Mute This Topic: https://lists.openembedded.org/mt/117922743/21656 Group Owner: [email protected] Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [[email protected]] -=-=-=-=-=-=-=-=-=-=-=-
