summaryrefslogtreecommitdiffstats
path: root/meta/classes/create-spdx-2.2.bbclass
diff options
context:
space:
mode:
Diffstat (limited to 'meta/classes/create-spdx-2.2.bbclass')
-rw-r--r--meta/classes/create-spdx-2.2.bbclass367
1 files changed, 134 insertions, 233 deletions
diff --git a/meta/classes/create-spdx-2.2.bbclass b/meta/classes/create-spdx-2.2.bbclass
index f0513af083..3ebf92b5e1 100644
--- a/meta/classes/create-spdx-2.2.bbclass
+++ b/meta/classes/create-spdx-2.2.bbclass
@@ -4,60 +4,15 @@
# SPDX-License-Identifier: GPL-2.0-only
#
-DEPLOY_DIR_SPDX ??= "${DEPLOY_DIR}/spdx/${MACHINE}"
+inherit spdx-common
-# The product name that the CVE database uses. Defaults to BPN, but may need to
-# be overriden per recipe (for example tiff.bb sets CVE_PRODUCT=libtiff).
-CVE_PRODUCT ??= "${BPN}"
-CVE_VERSION ??= "${PV}"
+SPDX_VERSION = "2.2"
-SPDXDIR ??= "${WORKDIR}/spdx"
-SPDXDEPLOY = "${SPDXDIR}/deploy"
-SPDXWORK = "${SPDXDIR}/work"
-
-SPDX_TOOL_NAME ??= "oe-spdx-creator"
-SPDX_TOOL_VERSION ??= "1.0"
-
-SPDXRUNTIMEDEPLOY = "${SPDXDIR}/runtime-deploy"
-
-SPDX_INCLUDE_SOURCES ??= "0"
-SPDX_ARCHIVE_SOURCES ??= "0"
-SPDX_ARCHIVE_PACKAGED ??= "0"
-
-SPDX_UUID_NAMESPACE ??= "sbom.openembedded.org"
-SPDX_NAMESPACE_PREFIX ??= "http://spdx.org/spdxdoc"
-SPDX_PRETTY ??= "0"
-
-SPDX_LICENSES ??= "${COREBASE}/meta/files/spdx-licenses.json"
-
-SPDX_ORG ??= "OpenEmbedded ()"
-SPDX_SUPPLIER ??= "Organization: ${SPDX_ORG}"
-SPDX_SUPPLIER[doc] = "The SPDX PackageSupplier field for SPDX packages created from \
- this recipe. For SPDX documents create using this class during the build, this \
- is the contact information for the person or organization who is doing the \
- build."
-
-def extract_licenses(filename):
- import re
-
- lic_regex = re.compile(rb'^\W*SPDX-License-Identifier:\s*([ \w\d.()+-]+?)(?:\s+\W*)?$', re.MULTILINE)
-
- try:
- with open(filename, 'rb') as f:
- size = min(15000, os.stat(filename).st_size)
- txt = f.read(size)
- licenses = re.findall(lic_regex, txt)
- if licenses:
- ascii_licenses = [lic.decode('ascii') for lic in licenses]
- return ascii_licenses
- except Exception as e:
- bb.warn(f"Exception reading {filename}: {e}")
- return None
-
-def get_doc_namespace(d, doc):
+def get_namespace(d, name):
import uuid
namespace_uuid = uuid.uuid5(uuid.NAMESPACE_DNS, d.getVar("SPDX_UUID_NAMESPACE"))
- return "%s/%s-%s" % (d.getVar("SPDX_NAMESPACE_PREFIX"), doc.name, str(uuid.uuid5(namespace_uuid, doc.name)))
+ return "%s/%s-%s" % (d.getVar("SPDX_NAMESPACE_PREFIX"), name, str(uuid.uuid5(namespace_uuid, name)))
+
def create_annotation(d, comment):
from datetime import datetime, timezone
@@ -75,26 +30,6 @@ def recipe_spdx_is_native(d, recipe):
a.annotator == "Tool: %s - %s" % (d.getVar("SPDX_TOOL_NAME"), d.getVar("SPDX_TOOL_VERSION")) and
a.comment == "isNative" for a in recipe.annotations)
-def is_work_shared_spdx(d):
- return bb.data.inherits_class('kernel', d) or ('work-shared' in d.getVar('WORKDIR'))
-
-def get_json_indent(d):
- if d.getVar("SPDX_PRETTY") == "1":
- return 2
- return None
-
-python() {
- import json
- if d.getVar("SPDX_LICENSE_DATA"):
- return
-
- with open(d.getVar("SPDX_LICENSES"), "r") as f:
- data = json.load(f)
- # Transform the license array to a dictionary
- data["licenses"] = {l["licenseId"]: l for l in data["licenses"]}
- d.setVar("SPDX_LICENSE_DATA", data)
-}
-
def convert_license_to_spdx(lic, document, d, existing={}):
from pathlib import Path
import oe.spdx
@@ -133,7 +68,7 @@ def convert_license_to_spdx(lic, document, d, existing={}):
with open(filename, errors="replace") as f:
extracted_info.extractedText = f.read()
else:
- bb.error("Cannot find any text for license %s" % name)
+ bb.fatal("Cannot find any text for license %s" % name)
extracted[name] = extracted_info
document.hasExtractedLicensingInfos.append(extracted_info)
@@ -163,38 +98,10 @@ def convert_license_to_spdx(lic, document, d, existing={}):
return spdx_license
- lic_split = lic.replace("(", " ( ").replace(")", " ) ").split()
+ lic_split = lic.replace("(", " ( ").replace(")", " ) ").replace("|", " | ").replace("&", " & ").split()
return ' '.join(convert(l) for l in lic_split)
-def process_sources(d):
- pn = d.getVar('PN')
- assume_provided = (d.getVar("ASSUME_PROVIDED") or "").split()
- if pn in assume_provided:
- for p in d.getVar("PROVIDES").split():
- if p != pn:
- pn = p
- break
-
- # glibc-locale: do_fetch, do_unpack and do_patch tasks have been deleted,
- # so avoid archiving source here.
- if pn.startswith('glibc-locale'):
- return False
- if d.getVar('PN') == "libtool-cross":
- return False
- if d.getVar('PN') == "libgcc-initial":
- return False
- if d.getVar('PN') == "shadow-sysroot":
- return False
-
- # We just archive gcc-source for all the gcc related recipes
- if d.getVar('BPN') in ['gcc', 'libgcc']:
- bb.debug(1, 'spdx: There is bug in scan of %s is, do nothing' % pn)
- return False
-
- return True
-
-
def add_package_files(d, doc, spdx_pkg, topdir, get_spdxid, get_types, *, archive=None, ignore_dirs=[], ignore_top_level_dirs=[]):
from pathlib import Path
import oe.spdx
@@ -297,7 +204,8 @@ def add_package_sources_from_debug(d, package_doc, spdx_package, package, packag
if file_path.lstrip("/") == pkg_file.fileName.lstrip("/"):
break
else:
- bb.fatal("No package file found for %s" % str(file_path))
+ bb.fatal("No package file found for %s in %s; SPDX found: %s" % (str(file_path), package,
+ " ".join(p.fileName for p in package_files)))
continue
for debugsrc in file_data["debugsrc"]:
@@ -333,21 +241,32 @@ def add_package_sources_from_debug(d, package_doc, spdx_package, package, packag
package_doc.add_relationship(pkg_file, "GENERATED_FROM", ref_id, comment=debugsrc)
+add_package_sources_from_debug[vardepsexclude] += "STAGING_KERNEL_DIR"
+
def collect_dep_recipes(d, doc, spdx_recipe):
+ import json
from pathlib import Path
import oe.sbom
import oe.spdx
deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX"))
+ package_archs = d.getVar("SSTATE_ARCHS").split()
+ package_archs.reverse()
dep_recipes = []
- taskdepdata = d.getVar("BB_TASKDEPDATA", False)
- deps = sorted(set(
- dep[0] for dep in taskdepdata.values() if
- dep[1] == "do_create_spdx" and dep[0] != d.getVar("PN")
- ))
- for dep_pn in deps:
- dep_recipe_path = deploy_dir_spdx / "recipes" / ("recipe-%s.spdx.json" % dep_pn)
+
+ deps = get_spdx_deps(d)
+
+ for dep_pn, dep_hashfn, in_taskhash in deps:
+ # If this dependency is not calculated in the taskhash skip it.
+ # Otherwise, it can result in broken links since this task won't
+ # rebuild and see the new SPDX ID if the dependency changes
+ if not in_taskhash:
+ continue
+
+ dep_recipe_path = oe.sbom.doc_find_by_hashfn(deploy_dir_spdx, package_archs, "recipe-" + dep_pn, dep_hashfn)
+ if not dep_recipe_path:
+ bb.fatal("Cannot find any SPDX file for recipe %s, %s" % (dep_pn, dep_hashfn))
spdx_dep_doc, spdx_dep_sha1 = oe.sbom.read_doc(dep_recipe_path)
@@ -376,8 +295,7 @@ def collect_dep_recipes(d, doc, spdx_recipe):
return dep_recipes
-collect_dep_recipes[vardepsexclude] += "BB_TASKDEPDATA"
-
+collect_dep_recipes[vardepsexclude] = "SSTATE_ARCHS"
def collect_dep_sources(d, dep_recipes):
import oe.sbom
@@ -402,6 +320,54 @@ def collect_dep_sources(d, dep_recipes):
return sources
+def add_download_packages(d, doc, recipe):
+ import os.path
+ from bb.fetch2 import decodeurl, CHECKSUM_LIST
+ import bb.process
+ import oe.spdx
+ import oe.sbom
+
+ for download_idx, src_uri in enumerate(d.getVar('SRC_URI').split()):
+ f = bb.fetch2.FetchData(src_uri, d)
+
+ for name in f.names:
+ package = oe.spdx.SPDXPackage()
+ package.name = "%s-source-%d" % (d.getVar("PN"), download_idx + 1)
+ package.SPDXID = oe.sbom.get_download_spdxid(d, download_idx + 1)
+
+ if f.type == "file":
+ continue
+
+ uri = f.type
+ proto = getattr(f, "proto", None)
+ if proto is not None:
+ uri = uri + "+" + proto
+ uri = uri + "://" + f.host + f.path
+
+ if f.method.supports_srcrev():
+ uri = uri + "@" + f.revisions[name]
+
+ if f.method.supports_checksum(f):
+ for checksum_id in CHECKSUM_LIST:
+ if checksum_id.upper() not in oe.spdx.SPDXPackage.ALLOWED_CHECKSUMS:
+ continue
+
+ expected_checksum = getattr(f, "%s_expected" % checksum_id)
+ if expected_checksum is None:
+ continue
+
+ c = oe.spdx.SPDXChecksum()
+ c.algorithm = checksum_id.upper()
+ c.checksumValue = expected_checksum
+ package.checksums.append(c)
+
+ package.downloadLocation = uri
+ doc.packages.append(package)
+ doc.add_relationship(doc, "DESCRIBES", package)
+ # In the future, we might be able to do more fancy dependencies,
+ # but this should be sufficient for now
+ doc.add_relationship(package, "BUILD_DEPENDENCY_OF", recipe)
+
python do_create_spdx() {
from datetime import datetime, timezone
@@ -433,13 +399,14 @@ python do_create_spdx() {
include_sources = d.getVar("SPDX_INCLUDE_SOURCES") == "1"
archive_sources = d.getVar("SPDX_ARCHIVE_SOURCES") == "1"
archive_packaged = d.getVar("SPDX_ARCHIVE_PACKAGED") == "1"
+ pkg_arch = d.getVar("SSTATE_PKGARCH")
creation_time = datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
doc = oe.spdx.SPDXDocument()
doc.name = "recipe-" + d.getVar("PN")
- doc.documentNamespace = get_doc_namespace(d, doc)
+ doc.documentNamespace = get_namespace(d, doc.name)
doc.creationInfo.created = creation_time
doc.creationInfo.comment = "This document was created by analyzing recipe files during the build."
doc.creationInfo.licenseListVersion = d.getVar("SPDX_LICENSE_DATA")["licenseListVersion"]
@@ -455,14 +422,6 @@ python do_create_spdx() {
if bb.data.inherits_class("native", d) or bb.data.inherits_class("cross", d):
recipe.annotations.append(create_annotation(d, "isNative"))
- for s in d.getVar('SRC_URI').split():
- if not s.startswith("file://"):
- s = s.split(';')[0]
- recipe.downloadLocation = s
- break
- else:
- recipe.downloadLocation = "NOASSERTION"
-
homepage = d.getVar("HOMEPAGE")
if homepage:
recipe.homepage = homepage
@@ -479,6 +438,10 @@ python do_create_spdx() {
if description:
recipe.description = description
+ if d.getVar("SPDX_CUSTOM_ANNOTATION_VARS"):
+ for var in d.getVar('SPDX_CUSTOM_ANNOTATION_VARS').split():
+ recipe.annotations.append(create_annotation(d, var + "=" + d.getVar(var)))
+
# Some CVEs may be patched during the build process without incrementing the version number,
# so querying for CVEs based on the CPE id can lead to false positives. To account for this,
# save the CVEs fixed by patches to source information field in the SPDX.
@@ -500,6 +463,8 @@ python do_create_spdx() {
doc.packages.append(recipe)
doc.add_relationship(doc, "DESCRIBES", recipe)
+ add_download_packages(d, doc, recipe)
+
if process_sources(d) and include_sources:
recipe_archive = deploy_dir_spdx / "recipes" / (doc.name + ".tar.zst")
with optional_tarfile(recipe_archive, archive_sources) as archive:
@@ -522,7 +487,7 @@ python do_create_spdx() {
dep_recipes = collect_dep_recipes(d, doc, recipe)
- doc_sha1 = oe.sbom.write_doc(d, doc, "recipes", indent=get_json_indent(d))
+ doc_sha1 = oe.sbom.write_doc(d, doc, pkg_arch, "recipes", indent=get_json_indent(d))
dep_recipes.append(oe.sbom.DepRecipe(doc, doc_sha1, recipe))
recipe_ref = oe.spdx.SPDXExternalDocumentRef()
@@ -545,7 +510,7 @@ python do_create_spdx() {
package_doc = oe.spdx.SPDXDocument()
pkg_name = d.getVar("PKG:%s" % package) or package
package_doc.name = pkg_name
- package_doc.documentNamespace = get_doc_namespace(d, package_doc)
+ package_doc.documentNamespace = get_namespace(d, package_doc.name)
package_doc.creationInfo.created = creation_time
package_doc.creationInfo.comment = "This document was created by analyzing packages created during the build."
package_doc.creationInfo.licenseListVersion = d.getVar("SPDX_LICENSE_DATA")["licenseListVersion"]
@@ -587,10 +552,11 @@ python do_create_spdx() {
add_package_sources_from_debug(d, package_doc, spdx_package, package, package_files, sources)
- oe.sbom.write_doc(d, package_doc, "packages", indent=get_json_indent(d))
+ oe.sbom.write_doc(d, package_doc, pkg_arch, "packages", indent=get_json_indent(d))
}
+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 before do_populate_sdk do_build do_rm_work
+addtask do_create_spdx after 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}"
@@ -604,39 +570,6 @@ addtask do_create_spdx_setscene
do_create_spdx[dirs] = "${SPDXWORK}"
do_create_spdx[cleandirs] = "${SPDXDEPLOY} ${SPDXWORK}"
do_create_spdx[depends] += "${PATCHDEPENDENCY}"
-do_create_spdx[deptask] = "do_create_spdx"
-
-def collect_package_providers(d):
- from pathlib import Path
- import oe.sbom
- import oe.spdx
- import json
-
- deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX"))
-
- providers = {}
-
- taskdepdata = d.getVar("BB_TASKDEPDATA", False)
- deps = sorted(set(
- dep[0] for dep in taskdepdata.values() if dep[0] != d.getVar("PN")
- ))
- deps.append(d.getVar("PN"))
-
- for dep_pn in deps:
- recipe_data = oe.packagedata.read_pkgdata(dep_pn, d)
-
- for pkg in recipe_data.get("PACKAGES", "").split():
-
- pkg_data = oe.packagedata.read_subpkgdata_dict(pkg, d)
- rprovides = set(n for n, _ in bb.utils.explode_dep_versions2(pkg_data.get("RPROVIDES", "")).items())
- rprovides.add(pkg)
-
- for r in rprovides:
- providers[r] = pkg
-
- return providers
-
-collect_package_providers[vardepsexclude] += "BB_TASKDEPDATA"
python do_create_runtime_spdx() {
from datetime import datetime, timezone
@@ -652,6 +585,9 @@ python do_create_runtime_spdx() {
creation_time = datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
providers = collect_package_providers(d)
+ pkg_arch = d.getVar("SSTATE_PKGARCH")
+ package_archs = d.getVar("SSTATE_ARCHS").split()
+ package_archs.reverse()
if not is_native:
bb.build.exec_func("read_subpackage_metadata", d)
@@ -668,7 +604,7 @@ python do_create_runtime_spdx() {
if not oe.packagedata.packaged(package, localdata):
continue
- pkg_spdx_path = deploy_dir_spdx / "packages" / (pkg_name + ".spdx.json")
+ pkg_spdx_path = oe.sbom.doc_path(deploy_dir_spdx, pkg_name, pkg_arch, "packages")
package_doc, package_doc_sha1 = oe.sbom.read_doc(pkg_spdx_path)
@@ -681,7 +617,7 @@ python do_create_runtime_spdx() {
runtime_doc = oe.spdx.SPDXDocument()
runtime_doc.name = "runtime-" + pkg_name
- runtime_doc.documentNamespace = get_doc_namespace(localdata, runtime_doc)
+ runtime_doc.documentNamespace = get_namespace(localdata, runtime_doc.name)
runtime_doc.creationInfo.created = creation_time
runtime_doc.creationInfo.comment = "This document was created by analyzing package runtime dependencies."
runtime_doc.creationInfo.licenseListVersion = d.getVar("SPDX_LICENSE_DATA")["licenseListVersion"]
@@ -712,7 +648,7 @@ python do_create_runtime_spdx() {
if dep not in providers:
continue
- dep = providers[dep]
+ (dep, dep_hashfn) = providers[dep]
if not oe.packagedata.packaged(dep, localdata):
continue
@@ -723,7 +659,9 @@ python do_create_runtime_spdx() {
if dep in dep_package_cache:
(dep_spdx_package, dep_package_ref) = dep_package_cache[dep]
else:
- dep_path = deploy_dir_spdx / "packages" / ("%s.spdx.json" % dep_pkg)
+ dep_path = oe.sbom.doc_find_by_hashfn(deploy_dir_spdx, package_archs, dep_pkg, dep_hashfn)
+ if not dep_path:
+ bb.fatal("No SPDX file found for package %s, %s" % (dep_pkg, dep_hashfn))
spdx_dep_doc, spdx_dep_sha1 = oe.sbom.read_doc(dep_path)
@@ -751,9 +689,11 @@ python do_create_runtime_spdx() {
)
seen_deps.add(dep)
- oe.sbom.write_doc(d, runtime_doc, "runtime", spdx_deploy, indent=get_json_indent(d))
+ oe.sbom.write_doc(d, runtime_doc, pkg_arch, "runtime", spdx_deploy, indent=get_json_indent(d))
}
+do_create_runtime_spdx[vardepsexclude] += "OVERRIDES SSTATE_ARCHS"
+
addtask do_create_runtime_spdx after do_create_spdx before do_build do_rm_work
SSTATETASKS += "do_create_runtime_spdx"
do_create_runtime_spdx[sstate-inputdirs] = "${SPDXRUNTIMEDEPLOY}"
@@ -768,65 +708,15 @@ do_create_runtime_spdx[dirs] = "${SPDXRUNTIMEDEPLOY}"
do_create_runtime_spdx[cleandirs] = "${SPDXRUNTIMEDEPLOY}"
do_create_runtime_spdx[rdeptask] = "do_create_spdx"
-def spdx_get_src(d):
- """
- save patched source of the recipe in SPDX_WORKDIR.
- """
- import shutil
- spdx_workdir = d.getVar('SPDXWORK')
- spdx_sysroot_native = d.getVar('STAGING_DIR_NATIVE')
- pn = d.getVar('PN')
-
- workdir = d.getVar("WORKDIR")
-
- try:
- # The kernel class functions require it to be on work-shared, so we dont change WORKDIR
- if not is_work_shared_spdx(d):
- # Change the WORKDIR to make do_unpack do_patch run in another dir.
- d.setVar('WORKDIR', spdx_workdir)
- # Restore the original path to recipe's native sysroot (it's relative to WORKDIR).
- d.setVar('STAGING_DIR_NATIVE', spdx_sysroot_native)
-
- # The changed 'WORKDIR' also caused 'B' changed, create dir 'B' for the
- # possibly requiring of the following tasks (such as some recipes's
- # do_patch required 'B' existed).
- bb.utils.mkdirhier(d.getVar('B'))
-
- bb.build.exec_func('do_unpack', d)
- # Copy source of kernel to spdx_workdir
- if is_work_shared_spdx(d):
- share_src = d.getVar('WORKDIR')
- d.setVar('WORKDIR', spdx_workdir)
- d.setVar('STAGING_DIR_NATIVE', spdx_sysroot_native)
- src_dir = spdx_workdir + "/" + d.getVar('PN')+ "-" + d.getVar('PV') + "-" + d.getVar('PR')
- bb.utils.mkdirhier(src_dir)
- if bb.data.inherits_class('kernel',d):
- share_src = d.getVar('STAGING_KERNEL_DIR')
- cmd_copy_share = "cp -rf " + share_src + "/* " + src_dir + "/"
- cmd_copy_shared_res = os.popen(cmd_copy_share).read()
- bb.note("cmd_copy_shared_result = " + cmd_copy_shared_res)
-
- git_path = src_dir + "/.git"
- if os.path.exists(git_path):
- shutils.rmtree(git_path)
-
- # Make sure gcc and kernel sources are patched only once
- if not (d.getVar('SRC_URI') == "" or is_work_shared_spdx(d)):
- bb.build.exec_func('do_patch', d)
-
- # Some userland has no source.
- if not os.path.exists( spdx_workdir ):
- bb.utils.mkdirhier(spdx_workdir)
- finally:
- d.setVar("WORKDIR", workdir)
-
do_rootfs[recrdeptask] += "do_create_spdx do_create_runtime_spdx"
+do_rootfs[cleandirs] += "${SPDXIMAGEWORK}"
-ROOTFS_POSTUNINSTALL_COMMAND =+ "image_combine_spdx ; "
+ROOTFS_POSTUNINSTALL_COMMAND =+ "image_combine_spdx"
do_populate_sdk[recrdeptask] += "do_create_spdx do_create_runtime_spdx"
-POPULATE_SDK_POST_HOST_COMMAND:append:task-populate-sdk = " sdk_host_combine_spdx; "
-POPULATE_SDK_POST_TARGET_COMMAND:append:task-populate-sdk = " sdk_target_combine_spdx; "
+do_populate_sdk[cleandirs] += "${SPDXSDKWORK}"
+POPULATE_SDK_POST_HOST_COMMAND:append:task-populate-sdk = " sdk_host_combine_spdx"
+POPULATE_SDK_POST_TARGET_COMMAND:append:task-populate-sdk = " sdk_target_combine_spdx"
python image_combine_spdx() {
import os
@@ -840,7 +730,7 @@ python image_combine_spdx() {
img_spdxid = oe.sbom.get_image_spdxid(image_name)
packages = image_list_installed_packages(d)
- combine_spdx(d, image_name, imgdeploydir, img_spdxid, packages)
+ combine_spdx(d, image_name, imgdeploydir, img_spdxid, packages, Path(d.getVar("SPDXIMAGEWORK")))
def make_image_link(target_path, suffix):
if image_link_name:
@@ -848,12 +738,8 @@ python image_combine_spdx() {
if link != target_path:
link.symlink_to(os.path.relpath(target_path, link.parent))
- image_spdx_path = imgdeploydir / (image_name + ".spdx.json")
- make_image_link(image_spdx_path, ".spdx.json")
spdx_tar_path = imgdeploydir / (image_name + ".spdx.tar.zst")
make_image_link(spdx_tar_path, ".spdx.tar.zst")
- spdx_index_path = imgdeploydir / (image_name + ".spdx.index.json")
- make_image_link(spdx_index_path, ".spdx.index.json")
}
python sdk_host_combine_spdx() {
@@ -869,13 +755,13 @@ def sdk_combine_spdx(d, sdk_type):
from pathlib import Path
from oe.sdk import sdk_list_installed_packages
- sdk_name = d.getVar("SDK_NAME") + "-" + sdk_type
+ sdk_name = d.getVar("TOOLCHAIN_OUTPUTNAME") + "-" + sdk_type
sdk_deploydir = Path(d.getVar("SDKDEPLOYDIR"))
sdk_spdxid = oe.sbom.get_sdk_spdxid(sdk_name)
sdk_packages = sdk_list_installed_packages(d, sdk_type == "target")
- combine_spdx(d, sdk_name, sdk_deploydir, sdk_spdxid, sdk_packages)
+ combine_spdx(d, sdk_name, sdk_deploydir, sdk_spdxid, sdk_packages, Path(d.getVar('SPDXSDKWORK')))
-def combine_spdx(d, rootfs_name, rootfs_deploydir, rootfs_spdxid, packages):
+def combine_spdx(d, rootfs_name, rootfs_deploydir, rootfs_spdxid, packages, spdx_workdir):
import os
import oe.spdx
import oe.sbom
@@ -886,13 +772,17 @@ def combine_spdx(d, rootfs_name, rootfs_deploydir, rootfs_spdxid, packages):
import tarfile
import bb.compress.zstd
+ providers = collect_package_providers(d)
+ package_archs = d.getVar("SSTATE_ARCHS").split()
+ package_archs.reverse()
+
creation_time = datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX"))
source_date_epoch = d.getVar("SOURCE_DATE_EPOCH")
doc = oe.spdx.SPDXDocument()
doc.name = rootfs_name
- doc.documentNamespace = get_doc_namespace(d, doc)
+ doc.documentNamespace = get_namespace(d, doc.name)
doc.creationInfo.created = creation_time
doc.creationInfo.comment = "This document was created by analyzing the source of the Yocto recipe during the build."
doc.creationInfo.licenseListVersion = d.getVar("SPDX_LICENSE_DATA")["licenseListVersion"]
@@ -909,7 +799,15 @@ def combine_spdx(d, rootfs_name, rootfs_deploydir, rootfs_spdxid, packages):
doc.packages.append(image)
for name in sorted(packages.keys()):
- pkg_spdx_path = deploy_dir_spdx / "packages" / (name + ".spdx.json")
+ if name not in providers:
+ bb.fatal("Unable to find SPDX provider for '%s'" % name)
+
+ pkg_name, pkg_hashfn = providers[name]
+
+ pkg_spdx_path = oe.sbom.doc_find_by_hashfn(deploy_dir_spdx, package_archs, pkg_name, pkg_hashfn)
+ if not pkg_spdx_path:
+ bb.fatal("No SPDX file found for package %s, %s" % (pkg_name, pkg_hashfn))
+
pkg_doc, pkg_doc_sha1 = oe.sbom.read_doc(pkg_spdx_path)
for p in pkg_doc.packages:
@@ -926,7 +824,10 @@ def combine_spdx(d, rootfs_name, rootfs_deploydir, rootfs_spdxid, packages):
else:
bb.fatal("Unable to find package with name '%s' in SPDX file %s" % (name, pkg_spdx_path))
- runtime_spdx_path = deploy_dir_spdx / "runtime" / ("runtime-" + name + ".spdx.json")
+ runtime_spdx_path = oe.sbom.doc_find_by_hashfn(deploy_dir_spdx, package_archs, "runtime-" + name, pkg_hashfn)
+ if not runtime_spdx_path:
+ bb.fatal("No runtime SPDX document found for %s, %s" % (name, pkg_hashfn))
+
runtime_doc, runtime_doc_sha1 = oe.sbom.read_doc(runtime_spdx_path)
runtime_ref = oe.spdx.SPDXExternalDocumentRef()
@@ -943,8 +844,8 @@ def combine_spdx(d, rootfs_name, rootfs_deploydir, rootfs_spdxid, packages):
"%s:%s" % (runtime_ref.externalDocumentId, runtime_doc.SPDXID),
comment="Runtime dependencies for %s" % name
)
-
- image_spdx_path = rootfs_deploydir / (rootfs_name + ".spdx.json")
+ bb.utils.mkdirhier(spdx_workdir)
+ image_spdx_path = spdx_workdir / (rootfs_name + ".spdx.json")
with image_spdx_path.open("wb") as f:
doc.to_json(f, sort_keys=True, indent=get_json_indent(d))
@@ -998,7 +899,9 @@ def combine_spdx(d, rootfs_name, rootfs_deploydir, rootfs_spdxid, packages):
})
for ref in doc.externalDocumentRefs:
- ref_path = deploy_dir_spdx / "by-namespace" / ref.spdxDocument.replace("/", "_")
+ ref_path = oe.sbom.doc_find_by_namespace(deploy_dir_spdx, package_archs, ref.spdxDocument)
+ if not ref_path:
+ bb.fatal("Cannot find any SPDX file for document %s" % ref.spdxDocument)
collect_spdx_document(ref_path)
collect_spdx_document(image_spdx_path)
@@ -1021,6 +924,4 @@ def combine_spdx(d, rootfs_name, rootfs_deploydir, rootfs_spdxid, packages):
tar.addfile(info, fileobj=index_str)
- spdx_index_path = rootfs_deploydir / (rootfs_name + ".spdx.index.json")
- with spdx_index_path.open("w") as f:
- json.dump(index, f, sort_keys=True, indent=get_json_indent(d))
+combine_spdx[vardepsexclude] += "BB_NUMBER_THREADS SSTATE_ARCHS"