From patchwork Wed Sep 16 05:39:07 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Wagenknecht X-Patchwork-Id: 98385 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2AA5DC88E77 for ; Wed, 16 Sep 2026 05:39:37 +0000 (UTC) Received: from mx1.emlix.com (mx1.emlix.com [178.63.209.131]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.8311.1789537172941309483 for ; Tue, 15 Sep 2026 22:39:33 -0700 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: body hash did not verify" header.i=@emlix.com header.s=20250930 header.b=P5Ab2A//; dkim=fail reason="dkim: body hash did not verify" header.i=@emlix.com header.s=20250930 header.b=P5Ab2A//; spf=pass (domain: emlix.com, ip: 178.63.209.131, mailfrom: dwagenknecht@emlix.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=emlix.com; s=20250930; t=1789537170; bh=u/jvFDI+EB3TS0vuCX0+fxabnuGW5luSgoKFmuG5bWw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=P5Ab2A//Zq5jyxzni4vugI782zOEFdyVC3bZycAZ6tFmxYdGcr2T/QaYQd67RROyT 4A30iJk7gvlSpzN/5EZtM8lcjk/ZWuwU72rkKjJE0xH2LR5RBN16itkYm7H3oTX3tz B6isj9fFTi/ETT/g5YMhJCGKrv/6QfFX4fkmaZF7idbLMegbTwn0Zn1sC4YUyYraO4 CBXsy1eNU/pbWLwGoFjIWrOVltOk4WwkzMhWZWYB9IqcOf7kvwzMuiY5aEw9tcTRoS 4YBQxSn8wvGWa9Sm5646OlZXmZTAroqlADUEk8QJG6szc/Pt274yATtA8dnzz73Gr6 BsWcyqVBkmbTg== Received: from mx1.emlix.com (localhost [127.0.0.1]) by mx1.emlix.com (Postfix) with ESMTP id 4F9235F8F9 for ; Wed, 16 Sep 2026 07:39:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=emlix.com; s=20250930; t=1789537170; bh=u/jvFDI+EB3TS0vuCX0+fxabnuGW5luSgoKFmuG5bWw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=P5Ab2A//Zq5jyxzni4vugI782zOEFdyVC3bZycAZ6tFmxYdGcr2T/QaYQd67RROyT 4A30iJk7gvlSpzN/5EZtM8lcjk/ZWuwU72rkKjJE0xH2LR5RBN16itkYm7H3oTX3tz B6isj9fFTi/ETT/g5YMhJCGKrv/6QfFX4fkmaZF7idbLMegbTwn0Zn1sC4YUyYraO4 CBXsy1eNU/pbWLwGoFjIWrOVltOk4WwkzMhWZWYB9IqcOf7kvwzMuiY5aEw9tcTRoS 4YBQxSn8wvGWa9Sm5646OlZXmZTAroqlADUEk8QJG6szc/Pt274yATtA8dnzz73Gr6 BsWcyqVBkmbTg== Received: from mailer.emlix.com (p5098be52.dip0.t-ipconnect.de [80.152.190.82]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.emlix.com (Postfix) with ESMTPS id 365EC5F749 for ; Wed, 16 Sep 2026 07:39:30 +0200 (CEST) From: Daniel Wagenknecht To: openembedded-core@lists.openembedded.org Cc: Daniel Wagenknecht Subject: [PATCH v2 1/2] spdx: add entries for meta-layers Date: Wed, 16 Sep 2026 07:39:07 +0200 Message-ID: <20260916053927.2198761-1-dwagenknecht@emlix.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: References: MIME-Version: 1.0 X-Virus-Scanned: ClamAV using ClamSMTP List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 16 Sep 2026 05:39:37 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/245914 This adds support for determining the revision and downloadLocation of meta-layers and adds them to the OE generated SPDX document. This downloadLocation will result in a NOASSERTION entry if - the meta-layer is not in a git repo - the git repo is dirty - the git remote does not have a valid URL - the git remote does not contain the used revision as far as can be eluded from the local repo Variables to configure the behavior: - SPDX_LAYER_REMOTE (default: origin): select the name of the git remote to use for layers. Set to an empty string to always return NOASSERTION - SPDX_LAYER_REMOTE_layername: override SPDX_LAYER_REMOTE for a specific layer - SPDX_REQUIRE_LAYER_ASSERTION: set to true to fail a build if determining downloadLocation fails for any layer Signed-off-by: Daniel Wagenknecht --- meta/classes/create-spdx-3.0.bbclass | 1 + meta/classes/spdx-common.bbclass | 3 ++ meta/lib/oe/sbom30.py | 12 +++++ meta/lib/oe/spdx30_tasks.py | 72 ++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+) diff --git a/meta/classes/create-spdx-3.0.bbclass b/meta/classes/create-spdx-3.0.bbclass index 56fd01fd533..9e4f737f85d 100644 --- a/meta/classes/create-spdx-3.0.bbclass +++ b/meta/classes/create-spdx-3.0.bbclass @@ -286,6 +286,7 @@ python spdx30_build_started_handler () { import oe.spdx30_tasks d = e.data.createCopy() oe.spdx30_tasks.write_bitbake_spdx(d) + oe.spdx30_tasks.write_layers_spdx(d) } addhandler spdx30_build_started_handler diff --git a/meta/classes/spdx-common.bbclass b/meta/classes/spdx-common.bbclass index 40701730a69..b64537dfb48 100644 --- a/meta/classes/spdx-common.bbclass +++ b/meta/classes/spdx-common.bbclass @@ -89,6 +89,9 @@ SPDX_FILE_EXCLUDE_PATTERNS[doc] = "Space-separated list of Python regular \ (no filtering). Example: \ SPDX_FILE_EXCLUDE_PATTERNS = '\\.patch$ \\.diff$ /test/ \\.pyc$ \\.o$'" +SPDX_REQUIRE_LAYER_ASSERTION ??= "0" +SPDX_LAYER_REMOTE ??= "origin" + python () { from oe.cve_check import extend_cve_status extend_cve_status(d) diff --git a/meta/lib/oe/sbom30.py b/meta/lib/oe/sbom30.py index e02382c3cc7..46dae429ffb 100644 --- a/meta/lib/oe/sbom30.py +++ b/meta/lib/oe/sbom30.py @@ -762,6 +762,16 @@ class ObjectSet(oe.spdx30.SHACLObjectSet): return bb_objset + def import_layers_objset(self): + deploy_dir_spdx = Path(self.d.getVar("DEPLOY_DIR_SPDX")) + layers_objset = load_jsonld( + self.d, deploy_dir_spdx / "layers.spdx.json", required=True + ) + self.doc.import_.extend(layers_objset.doc.import_) + self.update(layers_objset.objects) + + return layers_objset + def import_bitbake_build(self): def find_bitbake_build(objset): return objset.find_filter( @@ -803,6 +813,8 @@ class ObjectSet(oe.spdx30.SHACLObjectSet): [build], ) + layers = self.import_layers_objset() + if self.d.getVar("SPDX_INCLUDE_BUILD_VARIABLES") == "1": for varname in sorted(self.d.keys()): if varname.startswith("__"): diff --git a/meta/lib/oe/spdx30_tasks.py b/meta/lib/oe/spdx30_tasks.py index 9978ae731ca..9094a6cbd64 100644 --- a/meta/lib/oe/spdx30_tasks.py +++ b/meta/lib/oe/spdx30_tasks.py @@ -1205,6 +1205,78 @@ def create_package_spdx(d): oe.sbom30.write_recipe_jsonld_doc(d, common_objset, "common-package", deploydir) +def git_remote_has_rev(path, remote, rev): + try: + branchlist, _ = bb.process.run(f"git branch --remotes --list '{remote}/*' --contains {rev}", cwd=path) + except bb.process.ExecutionError: + return False + return len(branchlist) > 0 + +def get_layer_downloadLocation(d, l_path, l_name, l_branch, l_rev, l_ismodified): + from urllib.parse import urlparse, urlunparse + + repo = oe.buildcfg.get_metadata_git_toplevel(l_path) + + if not repo or not l_name or l_ismodified: + return "NOASSERTION" + + remoteName = d.getVar(f"SPDX_LAYER_REMOTE_{l_name}") or d.getVar(f"SPDX_LAYER_REMOTE") + if not remoteName: + return "NOASSERTION" + + # parse and reconstruct the url to drop potentially present username and password + remoteUrl = urlparse(oe.buildcfg.get_metadata_git_remote_url(repo, remoteName)) + if not remoteUrl or remoteUrl.scheme not in ["https", "http", "git", "ssh"]: + return "NOASSERTION" + remoteUrl = urlunparse(remoteUrl) + + revision = l_rev + if revision == "" or not git_remote_has_rev(repo, remoteName, revision): + return "NOASSERTION" + + relpath = os.path.relpath(l_path, repo) + + return f"git+{remoteUrl}@{revision}{'#' + relpath if relpath else ''}" + + +def write_layers_spdx(d): + d.setVar("PN", "layers") + d.setVar("BB_TASKHASH", "layers") + oe.spdx_common.load_spdx_license_data(d) + deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX")) + + layer_objset = oe.sbom30.ObjectSet.new_objset(d, "layers") + + layers = oe.buildcfg.get_layer_revisions(d) + for (l_path, l_name, l_branch, l_rev, l_ismodified) in layers: + downloadLocation = get_layer_downloadLocation(d, l_path, l_name, l_branch, l_rev, l_ismodified) + + + if oe.utils.vartrue("SPDX_REQUIRE_LAYER_ASSERTION", True, False, d) and downloadLocation == "NOASSERTION": + bb.fatal(f"Layer {l_name} does not have a valid downloadLocation") + layer = oe.spdx30.software_Package( + _id=layer_objset.new_spdxid("layer", l_name), + creationInfo=layer_objset.doc.creationInfo, + name=l_name, + software_packageVersion=l_rev, + software_primaryPurpose=oe.spdx30.software_SoftwarePurpose.specification, + software_downloadLocation=downloadLocation, + software_sourceInfo=json.dumps( + { + "branch": l_branch, + "is-modified": (len(l_ismodified) > 0), + }, + separators=(",", ":"), + ), + ) + + layer_objset.add_root(layer) + layer_objset.set_element_alias(layer) + + oe.sbom30.write_jsonld_doc(d, layer_objset, deploy_dir_spdx / "layers.spdx.json") + + + def write_bitbake_spdx(d): # Set PN to "bitbake" so that SPDX IDs can be generated d.setVar("PN", "bitbake") From patchwork Wed Sep 16 05:39:08 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Wagenknecht X-Patchwork-Id: 98386 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 0B99DC88E5C for ; Wed, 16 Sep 2026 05:39:47 +0000 (UTC) Received: from mx1.emlix.com (mx1.emlix.com [178.63.209.131]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.8313.1789537182117217709 for ; Tue, 15 Sep 2026 22:39:42 -0700 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: body hash did not verify" header.i=@emlix.com header.s=20250930 header.b=Y2m7tG+t; dkim=fail reason="dkim: body hash did not verify" header.i=@emlix.com header.s=20250930 header.b=Y2m7tG+t; spf=pass (domain: emlix.com, ip: 178.63.209.131, mailfrom: dwagenknecht@emlix.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=emlix.com; s=20250930; t=1789537176; bh=HVLDOSQcM9bBDR/BD0IX2dwbFQPCPeLRJ/9xCT5QVeM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Y2m7tG+tqNrap2nQ1mY5H1MrAswNidCdpkk3eFCQQIVHsVgQOimkQDFaiLjTXB+ij GgQlJnM5eizA+7qPMZAR/U2U57adKrKuAFt/tqAIy6Ikhs2ijVDlvL87Cc8Wk2GtOk QkwckXVJT3OK99pNh0a7USke5m54vEpjVlYLvMip0H4+Vl4rDy9vlmV4Puur3MLFGm Tw8HYgVjVpoPQJ1Uyj9577I1r4WvDJy7BrxL+t0p69Dt+HR0BjUOnZBGHU7FXTF3Op q6gqc7pKoQjwYxll6Vc2OAu5vhrTQTOK7sv8Cphj7votpEdRWUL3O4XeXyHhsMrbe2 pjtoN8kVK2RQQ== Received: from mx1.emlix.com (localhost [127.0.0.1]) by mx1.emlix.com (Postfix) with ESMTP id CE7585F8F9 for ; Wed, 16 Sep 2026 07:39:36 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=emlix.com; s=20250930; t=1789537176; bh=HVLDOSQcM9bBDR/BD0IX2dwbFQPCPeLRJ/9xCT5QVeM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Y2m7tG+tqNrap2nQ1mY5H1MrAswNidCdpkk3eFCQQIVHsVgQOimkQDFaiLjTXB+ij GgQlJnM5eizA+7qPMZAR/U2U57adKrKuAFt/tqAIy6Ikhs2ijVDlvL87Cc8Wk2GtOk QkwckXVJT3OK99pNh0a7USke5m54vEpjVlYLvMip0H4+Vl4rDy9vlmV4Puur3MLFGm Tw8HYgVjVpoPQJ1Uyj9577I1r4WvDJy7BrxL+t0p69Dt+HR0BjUOnZBGHU7FXTF3Op q6gqc7pKoQjwYxll6Vc2OAu5vhrTQTOK7sv8Cphj7votpEdRWUL3O4XeXyHhsMrbe2 pjtoN8kVK2RQQ== Received: from mailer.emlix.com (p5098be52.dip0.t-ipconnect.de [80.152.190.82]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.emlix.com (Postfix) with ESMTPS id BE6CC5F749 for ; Wed, 16 Sep 2026 07:39:36 +0200 (CEST) From: Daniel Wagenknecht To: openembedded-core@lists.openembedded.org Cc: Daniel Wagenknecht Subject: [PATCH v2 2/2] spdx: link files to the meta-layer providing them Date: Wed, 16 Sep 2026 07:39:08 +0200 Message-ID: <20260916053927.2198761-2-dwagenknecht@emlix.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: References: MIME-Version: 1.0 X-Virus-Scanned: ClamAV using ClamSMTP List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 16 Sep 2026 05:39:47 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/245915 Many recipes ship files that originate from a meta-layer. Link each file added via the `file` protocol to the spdx entry of the meta-layer containing that file. Some files might not originate from a meta-layer (`file` protocol supports absolute paths), fail the build if SPDX_REQUIRE_LAYER_ASSERTION is set to true. Signed-off-by: Daniel Wagenknecht --- meta/lib/oe/spdx30_tasks.py | 44 +++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/meta/lib/oe/spdx30_tasks.py b/meta/lib/oe/spdx30_tasks.py index 9094a6cbd64..6b1308c5586 100644 --- a/meta/lib/oe/spdx30_tasks.py +++ b/meta/lib/oe/spdx30_tasks.py @@ -453,6 +453,7 @@ def add_download_files(d, objset): primary_purpose = oe.spdx30.software_SoftwarePurpose.source if fd.type == "file": + (layer_path, layer_objset) = get_layer_info(d, fd.localpath) if os.path.isdir(fd.localpath): walk_idx = 1 for root, dirs, files in os.walk(fd.localpath, onerror=walk_error): @@ -468,23 +469,36 @@ def add_download_files(d, objset): objset.new_spdxid( "source", str(download_idx + 1), str(walk_idx) ), - os.path.join( - file_name, os.path.relpath(f_path, fd.localpath) - ), + os.path.relpath(f_path, layer_path), f_path, purposes=[primary_purpose], ) + if layer_objset is not None: + objset.new_scoped_relationship( + [oe.sbom30.get_element_link_id(layer_objset)], + oe.spdx30.RelationshipType.contains, + oe.spdx30.LifecycleScopeType.build, + [file], + ) + inputs.add(file) walk_idx += 1 else: file = objset.new_file( objset.new_spdxid("source", str(download_idx + 1)), - file_name, + os.path.relpath(fd.localpath, layer_path), fd.localpath, purposes=[primary_purpose], ) + if layer_objset is not None: + objset.new_scoped_relationship( + [oe.sbom30.get_element_link_id(layer_objset)], + oe.spdx30.RelationshipType.contains, + oe.spdx30.LifecycleScopeType.build, + [file], + ) inputs.add(file) else: @@ -774,6 +788,28 @@ def load_recipe_spdx(d): oe.spdx30.software_Package, ) +def get_layer_info(d, path): + layer = None + path = Path(path) + + layers = oe.buildcfg.get_layer_revisions(d) + for (l_path, l_name, l_branch, l_rev, l_ismodified) in layers: + l_path = Path(l_path) + if l_path in path.parents: + layer = l_name + break + + if not l_path: + if oe.utils.vartrue("SPDX_REQUIRE_LAYER_ASSERTION", True, False, d): + bb.fatal(f"Path {path} does not originate from a meta-layer!") + return ("/", None) + + deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX")) + objset = oe.sbom30.load_jsonld(d, deploy_dir_spdx / "layers.spdx.json", required=True) + spdx_obj = objset.find_root(oe.spdx30.software_Package, name=l_name) + + return (l_path, spdx_obj) + def create_spdx(d): def set_var_field(var, obj, name, package=None):