diff options
Diffstat (limited to 'meta/classes/create-spdx-2.2.bbclass')
-rw-r--r-- | meta/classes/create-spdx-2.2.bbclass | 367 |
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" |