From patchwork Thu Feb 26 17:33:02 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joshua Watt X-Patchwork-Id: 82029 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 4A9B7FD8FF2 for ; Thu, 26 Feb 2026 17:39:42 +0000 (UTC) Received: from mail-ot1-f65.google.com (mail-ot1-f65.google.com [209.85.210.65]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.75801.1772127574760200732 for ; Thu, 26 Feb 2026 09:39:34 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20230601 header.b=X9mubWG3; spf=pass (domain: gmail.com, ip: 209.85.210.65, mailfrom: jpewhacker@gmail.com) Received: by mail-ot1-f65.google.com with SMTP id 46e09a7af769-7d4bd4db87aso491174a34.2 for ; Thu, 26 Feb 2026 09:39:34 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772127574; x=1772732374; darn=lists.openembedded.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=kZsTCBgME1PNRjxHczDMbLqJbIl+DF2WK2U4einEvRE=; b=X9mubWG3rCEeVbvdXi0G+z5QBPokRVYlaY6FGbcrh6+dAVz/WazVVANeuwSDbkET/z QU11a9pMUzyOks2uENguIrStv8O5J5bgTZdomTvKWF7phLZWSP6EHrC2v2p/ZkJ64WO+ iFLB2rYnlyi2Fz2hr7YOuG5MMhNeT27kWDFrqrKydOT/CZ71HG6On+uMnjg6Cw4s5pgt hAYCAAFIdfN50o1KA8fEEhighpqRp0LGE9hBMyu/YmxCPlMlcY6AGTiS1bEQKh+5Dd/e Q/VmLWDRtuyqOPEhgXJ1h0++sjj4asGEpR9ocWxxqJ8yeB2gjbpe/5cAuWGTtBJ/2dbl dmTg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772127574; x=1772732374; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=kZsTCBgME1PNRjxHczDMbLqJbIl+DF2WK2U4einEvRE=; b=IlodVPB3bHebTYB12L3xjmktUj66uRf/go44rdevI9EIigSBJYzEP7okHAmiJi1rCB vwXNLd6tJcISDj5EAbIw1RvW8dVqg2fGCd4bd7i1zp1brVmzeGJLY3e6alQKIUje8Fy2 uvhcMxpxf6KaN+Ui7OQElbeaFopRPgEfiOmNmM/90ykT42vOrjlW4wkzXm8M0lw/jWnL XSCGOrVz3RoumVKk6zC2YR7hWLr1vGG1j0x/UQLJB7u+QG7a2lIznJeKa5AhMInK7HTB keoKxU6uMNKZpo6aGCQRDY9WVeK20YKj9cLpd9VSwoD/OtgD739iv0/WJvLCWOykIocx hqcw== X-Gm-Message-State: AOJu0YwXTztckaj0Wbf4Mmm8dtWQvEmvkYf/hIOV8+PDq71zG8s+TKsg sDSy4SUmNjfUQuqr6iyk9ZgUezkfWakZa4SIHwLZr0T01BYvaPrcCBsnmmyoKLWC X-Gm-Gg: ATEYQzz56okiNL0pamcXlkBRPiell7jfMS2UWLqpFFSlbY4PJlyRh+Gt+H4XLANt0g+ qE6ppFEt4LkE4uo8RJNTb9kO/3dGHLi+WabFilGWCf3n6JHI5LRHqhuim9wftXYnbOUTnK1+63M X1yPEgG1H5Nm+m/RVJtvcVrI02sYbUNPAaQ6ioBtCR1OgLs4Emd+Gyk1CIXYAeUnYJwllSyp911 9BsMQ2evJgC7EPZlAKVlV9w7sYaELPb6t6G0sVxrw0aU56myFbSQ7Ro3WXgrN+vq+oVfYx1z35H Y9V8HPqIudKVAXItljQ5vjKvt/Vb2bTH43fLNWbbC4atu41QdwJDRkdlK3qz3Yip/aqUBqSzrsZ C6xfBQoxGq3/LLshNNNay8Zt37MAcdbyS6ibd31VrAQBpCtaDOV/acZsgXNZdCdHA8jgez3iMPq OYmLrCaywgObKq/Z1mzZf3 X-Received: by 2002:a05:6830:25d5:b0:7c7:6043:dd93 with SMTP id 46e09a7af769-7d591b2656amr4795a34.13.1772127573813; Thu, 26 Feb 2026 09:39:33 -0800 (PST) Received: from localhost.localdomain ([2601:282:4200:11c0::6492]) by smtp.gmail.com with ESMTPSA id 46e09a7af769-7d586653f81sm2173027a34.23.2026.02.26.09.39.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 26 Feb 2026 09:39:33 -0800 (PST) From: Joshua Watt X-Google-Original-From: Joshua Watt To: openembedded-core@lists.openembedded.org Cc: benjamin.robin@bootlin.com, ross.burton@arm.com, Joshua Watt Subject: [OE-core][PATCH v3 1/8] llvm-project-source: Use allarch.bbclass Date: Thu, 26 Feb 2026 10:33:02 -0700 Message-ID: <20260226173930.2847872-2-JPEWhacker@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260226173930.2847872-1-JPEWhacker@gmail.com> References: <20260224230234.679049-1-JPEWhacker@gmail.com> <20260226173930.2847872-1-JPEWhacker@gmail.com> MIME-Version: 1.0 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 ; Thu, 26 Feb 2026 17:39:42 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/232047 Converts the recipe to use allarch.bbclass. This is necessary because SSTATE_PKGARCH is set to "allarch" based on if allarch is inherited or not. If it is not, SSTATE_PKGARCH has the value "all", which means any data written out based on it cannot be found (because "all" is not in SSTATE_ARCHS) Signed-off-by: Joshua Watt --- meta/recipes-devtools/clang/llvm-project-source.inc | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/meta/recipes-devtools/clang/llvm-project-source.inc b/meta/recipes-devtools/clang/llvm-project-source.inc index 13e54efbc2..6bb595b7bc 100644 --- a/meta/recipes-devtools/clang/llvm-project-source.inc +++ b/meta/recipes-devtools/clang/llvm-project-source.inc @@ -5,7 +5,7 @@ deltask do_populate_sysroot deltask do_populate_lic RM_WORK_EXCLUDE += "${PN}" -inherit nopackages +inherit nopackages allarch PN = "llvm-project-source-${PV}" WORKDIR = "${TMPDIR}/work-shared/llvm-project-source-${PV}-${PR}" @@ -14,14 +14,8 @@ SSTATE_SWSPEC = "sstate:llvm-project-source::${PV}:${PR}::${SSTATE_VERSION}:" STAMP = "${STAMPS_DIR}/work-shared/llvm-project-source-${PV}-${PR}" STAMPCLEAN = "${STAMPS_DIR}/work-shared/llvm-project-source-${PV}-*" -INHIBIT_DEFAULT_DEPS = "1" DEPENDS = "" PACKAGES = "" -TARGET_ARCH = "allarch" -TARGET_AS_ARCH = "none" -TARGET_CC_ARCH = "none" -TARGET_LD_ARCH = "none" -TARGET_OS = "linux" baselib = "lib" PACKAGE_ARCH = "all" From patchwork Thu Feb 26 17:33:03 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joshua Watt X-Patchwork-Id: 82031 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 77450FD8FF6 for ; Thu, 26 Feb 2026 17:39:42 +0000 (UTC) Received: from mail-ot1-f68.google.com (mail-ot1-f68.google.com [209.85.210.68]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.75829.1772127575437863482 for ; Thu, 26 Feb 2026 09:39:35 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20230601 header.b=Sp6N3ROi; spf=pass (domain: gmail.com, ip: 209.85.210.68, mailfrom: jpewhacker@gmail.com) Received: by mail-ot1-f68.google.com with SMTP id 46e09a7af769-7d18f80b5c2so1101636a34.3 for ; Thu, 26 Feb 2026 09:39:35 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772127574; x=1772732374; darn=lists.openembedded.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=7bs/2ESNOjlKv5F2a1L5fY1cfh3Mb2GOypfpxUG+ELw=; b=Sp6N3ROiMAqWQdQHrLRnj6V7kI0pDn0VXOo3frlmt43xQMVj11pcRGMVTuScEEKNPl IMm8gm6TwEcwoZJOJ8HpBVhByM+UwZooucbtVnCUkE7ZPTNgFCpiiFON3D6pKPL6SF/j cAoOYPrzsFDNbDAdu1jUaIkCvpUKwFhYPjx8ntmFRIaMZPT7P90b1JKw9lKrZmra/od0 eD/SUmI54clIVu7l4AhuTUSUiNLxiwhSPwa6nr3W35iMA30ScRTxHxFzupxMFx1VWKiB Fs/yGbHPeJrOJDHugAV9Lm26mw6z56xk/3W3ycaYQ83PFu7z2aSBTRHTdUgI6wwN648D KBog== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772127574; x=1772732374; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=7bs/2ESNOjlKv5F2a1L5fY1cfh3Mb2GOypfpxUG+ELw=; b=kyYh2heuGuzS74DjJvDrsZQ1abeHV+Elv9vs0TOHiMr/X457el0FzlS0hwzMQ36cCW B6nHt1Mu+upBHsQ4q2dl9Va6z0ulQTZshyVoReRco0rQB/IMVKgMgrmO7+XCD7GJcO/C eK7M3h93VR9kM6yezQWsSCOx/AyLApNYFfHKG7LorEbw68ql0GBmPeWSmNWJeqZnoXG9 NPeFR0R+7Oz2JF7bdPKRrv8yFPaQenz/z7eZkd0JvNs3XQlAP9a6i2gIxVuPqp3iVARf ZrCiKGDma5nUI86ky0wTiiPWwIA/Jy1poiwxVUeL+HVjl92PggtAT3Peodr66Ihp6Qtd EsqQ== X-Gm-Message-State: AOJu0YyKe7aHd2tDONGHOFy1EOr30OKwf9ofBWkKQu4ZKYvluk6KNOd9 9GH5isBeadX35XdlofZhSYwS3sMY51WwSGbQtxX9iCDtwTgDxdxL6f/IzAGNWN7D X-Gm-Gg: ATEYQzw24YP7axe3ZiUWxEwrlp4ZjPmw4A1Yn9ruOMrGWVJ5MRCCk1L9AkCTwiGKBSS DKBZm0q99hlO98QEwiDN0fAJDK6kFsBYEc+VYOytBJOtloPNm/I97J2bW20OboRTW/U1ZkY8hth 3l4Df0SkIXN08KgAK4RSAf0M6NRQOMqjELNvBmyejzLwce6wv9E29sGec7U1y/qXpcMpyEK70py VOG2wqti51Yl8Tcr3jQws/DTlLyxJRjqlSOStW9L3OuOiXs4rqCxgiGeNemW4pHod9iHqT/1p5t i6OR7fPBaIl2eq6dQAGrt8zEz4dh4TIPsc2EtIPNbg2rxRxH0RuIyHrcGlqRA5uFguNsPokzgEu Lmk+EssrqlPeq03XenOKE5XwdT/DXLOpRvv2hWeDXVAV4Cp9LlC5UJ19fdc2a8+0+frRc7VzSvb +Idd1hw5sk6CvA7jHsxAtp X-Received: by 2002:a05:6830:6a97:b0:7d4:5057:5ad1 with SMTP id 46e09a7af769-7d591b27bd8mr6270a34.13.1772127574481; Thu, 26 Feb 2026 09:39:34 -0800 (PST) Received: from localhost.localdomain ([2601:282:4200:11c0::6492]) by smtp.gmail.com with ESMTPSA id 46e09a7af769-7d586653f81sm2173027a34.23.2026.02.26.09.39.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 26 Feb 2026 09:39:34 -0800 (PST) From: Joshua Watt X-Google-Original-From: Joshua Watt To: openembedded-core@lists.openembedded.org Cc: benjamin.robin@bootlin.com, ross.burton@arm.com, Joshua Watt Subject: [OE-core][PATCH v3 2/8] gcc-source: Use allarch.bbclass Date: Thu, 26 Feb 2026 10:33:03 -0700 Message-ID: <20260226173930.2847872-3-JPEWhacker@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260226173930.2847872-1-JPEWhacker@gmail.com> References: <20260224230234.679049-1-JPEWhacker@gmail.com> <20260226173930.2847872-1-JPEWhacker@gmail.com> MIME-Version: 1.0 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 ; Thu, 26 Feb 2026 17:39:42 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/232048 Converts the recipe to use allarch.bbclass. This is necessary because SSTATE_PKGARCH is set to "allarch" based on if allarch is inherited or not. If it is not, SSTATE_PKGARCH has the value "all", which means any data written out based on it cannot be found (because "all" is not in SSTATE_ARCHS) Signed-off-by: Joshua Watt --- meta/recipes-devtools/gcc/gcc-source.inc | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/meta/recipes-devtools/gcc/gcc-source.inc b/meta/recipes-devtools/gcc/gcc-source.inc index 265bcf4bef..3ac679b1a6 100644 --- a/meta/recipes-devtools/gcc/gcc-source.inc +++ b/meta/recipes-devtools/gcc/gcc-source.inc @@ -1,11 +1,11 @@ -deltask do_configure -deltask do_compile -deltask do_install +deltask do_configure +deltask do_compile +deltask do_install deltask do_populate_sysroot -deltask do_populate_lic +deltask do_populate_lic RM_WORK_EXCLUDE += "${PN}" -inherit nopackages +inherit nopackages allarch PN = "gcc-source-${PV}" WORKDIR = "${TMPDIR}/work-shared/gcc-${PV}-${PR}" @@ -14,14 +14,8 @@ SSTATE_SWSPEC = "sstate:gcc::${PV}:${PR}::${SSTATE_VERSION}:" STAMP = "${STAMPS_DIR}/work-shared/gcc-${PV}-${PR}" STAMPCLEAN = "${STAMPS_DIR}/work-shared/gcc-${PV}-*" -INHIBIT_DEFAULT_DEPS = "1" DEPENDS = "" PACKAGES = "" -TARGET_ARCH = "allarch" -TARGET_AS_ARCH = "none" -TARGET_CC_ARCH = "none" -TARGET_LD_ARCH = "none" -TARGET_OS = "linux" baselib = "lib" PACKAGE_ARCH = "all" From patchwork Thu Feb 26 17:33:04 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joshua Watt X-Patchwork-Id: 82032 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 6A47CFD8FF4 for ; Thu, 26 Feb 2026 17:39:42 +0000 (UTC) Received: from mail-ot1-f43.google.com (mail-ot1-f43.google.com [209.85.210.43]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.75803.1772127576396820077 for ; Thu, 26 Feb 2026 09:39:36 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20230601 header.b=G07laXg7; spf=pass (domain: gmail.com, ip: 209.85.210.43, mailfrom: jpewhacker@gmail.com) Received: by mail-ot1-f43.google.com with SMTP id 46e09a7af769-7d4c307db9aso616109a34.3 for ; Thu, 26 Feb 2026 09:39:36 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772127575; x=1772732375; darn=lists.openembedded.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=jEO3cQj+M0p/g8hATaVWyLjyh9ZbeSVdgajF2lN3BZU=; b=G07laXg7sgas1athm3OMEF7Q07xAasSOMRFVJBNgugCRT/EDUbFcDTxwZVzPjrwIZT fe0pmDnqUs3G5p1B39XsI2wPmS3d6LqbXkOgD8UcfY7anzgdAY/saXDQ9bUgkg5Z7RY2 sm1J7YT/oEqOTeT9N3QqlyCgcmnHupxLNRocMPGY6h5R/B25+rcRVouGGr6vripNqE7y 8zVSKia+dSkil8gnSJU5jHg4gzmYhJvOoAvYtyitU8+m+RcRxbVUzTk0dfX68u/GNU1x f9RsuJzoabsHOR3f4lH2pHpa3/hYaBA7vdkyu7PX2PcUaY6ZiXFr+MMg1SFD3xPLRYq4 Tp7w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772127575; x=1772732375; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=jEO3cQj+M0p/g8hATaVWyLjyh9ZbeSVdgajF2lN3BZU=; b=uNjQFKRCSvw0xlN9z6CYUtcjKg2p6kTQRW5zfKPEJu5XAeKiAr/SKv5rigGwlcKhBd 3r2lW6O8KRGXcbMHSaj9+JwgA80nr56WwwrMPUQwKdwUrQXVV2iuIR0ehY1ElV6aIZH1 Yc8TT+ihVyxlf2ybbK+vH0dRgrgnqDikRDjRscnG99oeavS6jFBnkuft9SGqJrE18m8G GvwqhmOXqQqUZBRj+JdnyU+3fBBAZf9D/mjI7VrUMO47fp/T5g/FxHBoEiip0r6vo7bX xZYF0+9rvgdgNEtT6ciJJN7mgEpEoJuQ/Kv3sYVUZpRDNVkV9pFNIMCY3FtLl1ZPAOvB dPPQ== X-Gm-Message-State: AOJu0Yy1icCbsS+GmJgrmrSZDrvcIQABhrI/b+FllgktYdH5LJqLVm1b adYYt/0qg8JosPOKRnkRK1qufY+Fp7amVEMheXo8/AiqTRcMpkmYlsO3sWOATQ== X-Gm-Gg: ATEYQzx5xeI8coW7tLALjO/Pa7IgIctv87+FeqydUfUKww0Cj+y91xWy7YGE9vr1CIa aH9IkL957aMiYRon9tiC4WJSbVlG5J84CGhYnCgmuK5OaOUXP7fmyXporP5241UC8vHgaKt7Kx6 XRsfPm24FZGz/FSYHO8sl0CUB0zejOrptHBIXhr/jXKvRJvG2xbs0F8s518BCGY0YOxH1+tOmdt V2r+rJrZJKRVR0y2eW/DsHFxpSrPBa3+GvCZdZFtVDSp3/5pro+1TFvXGIreoLePz9SiU3F6qsI bC3j/3UFe4fK9yod45W1UxisM/vdBVZ7Pi/mNOKYERthronzI87VjYDCtTxQgJkFJH8i+H+a/Ch jmqxcER6fFedCgCIUduSdMa38kxLcJO4xlr3PMaPc5ylm+V6YdP7+sYIoZHO8KNli5CokU9qbBU Y/G8wg8cKg0+h08kaoCMGX X-Received: by 2002:a05:6830:67ff:b0:7cf:dbb4:320e with SMTP id 46e09a7af769-7d582a67950mr3417005a34.18.1772127575281; Thu, 26 Feb 2026 09:39:35 -0800 (PST) Received: from localhost.localdomain ([2601:282:4200:11c0::6492]) by smtp.gmail.com with ESMTPSA id 46e09a7af769-7d586653f81sm2173027a34.23.2026.02.26.09.39.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 26 Feb 2026 09:39:34 -0800 (PST) From: Joshua Watt X-Google-Original-From: Joshua Watt To: openembedded-core@lists.openembedded.org Cc: benjamin.robin@bootlin.com, ross.burton@arm.com, Joshua Watt Subject: [OE-core][PATCH v3 3/8] spdx3: Add recipe SPDX data Date: Thu, 26 Feb 2026 10:33:04 -0700 Message-ID: <20260226173930.2847872-4-JPEWhacker@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260226173930.2847872-1-JPEWhacker@gmail.com> References: <20260224230234.679049-1-JPEWhacker@gmail.com> <20260226173930.2847872-1-JPEWhacker@gmail.com> MIME-Version: 1.0 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 ; Thu, 26 Feb 2026 17:39:42 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/232049 Adds a new package to the SPDX output that represents the recipe data for a given recipe. Importantly, this data contains only things that can be determined statically from only the recipe, so it doesn't require fetching or building anything. This means that build time dependencies and CVE information for recipes can be analyzed without needing to actually do any builds. Sadly, license data cannot be included because NO_GENERIC_LICENSE means that actual license text might only be available after do_fetch Signed-off-by: Joshua Watt --- meta/classes-global/sstate.bbclass | 4 +- .../create-spdx-image-3.0.bbclass | 4 +- .../create-spdx-sdk-3.0.bbclass | 4 +- meta/classes-recipe/kernel.bbclass | 2 +- meta/classes-recipe/nospdx.bbclass | 1 + meta/classes/create-spdx-2.2.bbclass | 12 +- meta/classes/create-spdx-3.0.bbclass | 50 ++- meta/classes/spdx-common.bbclass | 15 +- meta/lib/oe/spdx30_tasks.py | 402 ++++++++++++------ 9 files changed, 343 insertions(+), 151 deletions(-) diff --git a/meta/classes-global/sstate.bbclass b/meta/classes-global/sstate.bbclass index 2fd29d7323..95c44f404e 100644 --- a/meta/classes-global/sstate.bbclass +++ b/meta/classes-global/sstate.bbclass @@ -954,7 +954,7 @@ def sstate_checkhashes(sq_data, d, siginfo=False, currentcount=0, summary=True, extrapath = d.getVar("NATIVELSBSTRING") + "/" else: extrapath = "" - + tname = bb.runqueue.taskname_from_tid(task)[3:] if tname in ["fetch", "unpack", "patch", "populate_lic", "preconfigure"] and splithashfn[2]: @@ -1116,7 +1116,7 @@ def setscene_depvalid(task, taskdependees, notneeded, d, log=None): logit("Considering setscene task: %s" % (str(taskdependees[task])), log) - directtasks = ["do_populate_lic", "do_deploy_source_date_epoch", "do_shared_workdir", "do_stash_locale", "do_gcc_stash_builddir", "do_create_spdx", "do_deploy_archives"] + directtasks = ["do_populate_lic", "do_deploy_source_date_epoch", "do_shared_workdir", "do_stash_locale", "do_gcc_stash_builddir", "do_create_spdx", "do_create_recipe_spdx", "do_deploy_archives"] def isNativeCross(x): return x.endswith("-native") or "-cross-" in x or "-crosssdk" in x or x.endswith("-cross") diff --git a/meta/classes-recipe/create-spdx-image-3.0.bbclass b/meta/classes-recipe/create-spdx-image-3.0.bbclass index 636ab14eb0..15a91e90e2 100644 --- a/meta/classes-recipe/create-spdx-image-3.0.bbclass +++ b/meta/classes-recipe/create-spdx-image-3.0.bbclass @@ -34,7 +34,7 @@ addtask do_create_rootfs_spdx after do_rootfs before do_image SSTATETASKS += "do_create_rootfs_spdx" do_create_rootfs_spdx[sstate-inputdirs] = "${SPDXROOTFSDEPLOY}" do_create_rootfs_spdx[sstate-outputdirs] = "${DEPLOY_DIR_SPDX}" -do_create_rootfs_spdx[recrdeptask] += "do_create_spdx do_create_package_spdx" +do_create_rootfs_spdx[recrdeptask] += "do_create_recipe_spdx do_create_spdx do_create_package_spdx" do_create_rootfs_spdx[cleandirs] += "${SPDXROOTFSDEPLOY}" do_create_rootfs_spdx[file-checksums] += "${SPDX3_DEP_FILES}" @@ -76,7 +76,7 @@ do_create_image_sbom_spdx[sstate-inputdirs] = "${SPDXIMAGEDEPLOYDIR}" do_create_image_sbom_spdx[sstate-outputdirs] = "${DEPLOY_DIR_IMAGE}" do_create_image_sbom_spdx[stamp-extra-info] = "${MACHINE_ARCH}" do_create_image_sbom_spdx[cleandirs] = "${SPDXIMAGEDEPLOYDIR}" -do_create_image_sbom_spdx[recrdeptask] += "do_create_spdx do_create_package_spdx" +do_create_image_sbom_spdx[recrdeptask] += "do_create_recipe_spdx do_create_spdx do_create_package_spdx" do_create_image_sbom_spdx[file-checksums] += "${SPDX3_DEP_FILES}" python do_create_image_sbom_spdx_setscene() { diff --git a/meta/classes-recipe/create-spdx-sdk-3.0.bbclass b/meta/classes-recipe/create-spdx-sdk-3.0.bbclass index e5f220cdfa..a4b8ed3bf9 100644 --- a/meta/classes-recipe/create-spdx-sdk-3.0.bbclass +++ b/meta/classes-recipe/create-spdx-sdk-3.0.bbclass @@ -5,14 +5,14 @@ # # SPDX SDK tasks -do_populate_sdk[recrdeptask] += "do_create_spdx do_create_package_spdx" +do_populate_sdk[recrdeptask] += "do_create_recipe_spdx do_create_spdx do_create_package_spdx" do_populate_sdk[cleandirs] += "${SPDXSDKWORK}" do_populate_sdk[postfuncs] += "sdk_create_sbom" do_populate_sdk[file-checksums] += "${SPDX3_DEP_FILES}" POPULATE_SDK_POST_HOST_COMMAND:append:task-populate-sdk = " sdk_host_create_spdx" POPULATE_SDK_POST_TARGET_COMMAND:append:task-populate-sdk = " sdk_target_create_spdx" -do_populate_sdk_ext[recrdeptask] += "do_create_spdx do_create_package_spdx" +do_populate_sdk_ext[recrdeptask] += "do_create_recipe_spdx do_create_spdx do_create_package_spdx" do_populate_sdk_ext[cleandirs] += "${SPDXSDKEXTWORK}" do_populate_sdk_ext[postfuncs] += "sdk_ext_create_sbom" do_populate_sdk_ext[file-checksums] += "${SPDX3_DEP_FILES}" diff --git a/meta/classes-recipe/kernel.bbclass b/meta/classes-recipe/kernel.bbclass index f989b31c47..3a2c20dec2 100644 --- a/meta/classes-recipe/kernel.bbclass +++ b/meta/classes-recipe/kernel.bbclass @@ -904,7 +904,7 @@ python do_create_kernel_config_spdx() { bb.error(f"Failed to parse kernel config file: {e}") build, build_objset = oe.sbom30.find_root_obj_in_jsonld( - d, "recipes", f"recipe-{pn}", oe.spdx30.build_Build + d, "builds", f"build-{pn}", oe.spdx30.build_Build ) kernel_build = build_objset.add_root( diff --git a/meta/classes-recipe/nospdx.bbclass b/meta/classes-recipe/nospdx.bbclass index b20e28218b..90e14442ba 100644 --- a/meta/classes-recipe/nospdx.bbclass +++ b/meta/classes-recipe/nospdx.bbclass @@ -5,6 +5,7 @@ # deltask do_collect_spdx_deps +deltask do_create_recipe_spdx deltask do_create_spdx deltask do_create_spdx_runtime deltask do_create_package_spdx diff --git a/meta/classes/create-spdx-2.2.bbclass b/meta/classes/create-spdx-2.2.bbclass index 65d10d86db..f1ee0f9afd 100644 --- a/meta/classes/create-spdx-2.2.bbclass +++ b/meta/classes/create-spdx-2.2.bbclass @@ -399,6 +399,15 @@ def get_license_list_version(license_data, d): return ".".join(license_data["licenseListVersion"].split(".")[:2]) +# This task is added for compatibility with tasks shared with SPDX 3, but +# doesn't do anything +do_create_recipe_spdx() { + : +} +do_create_recipe_spdx[noexec] = "1" +addtask do_create_recipe_spdx after do_collect_spdx_deps + + python do_create_spdx() { from datetime import datetime, timezone import oe.sbom @@ -594,7 +603,7 @@ python do_create_spdx() { } 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 do_collect_spdx_deps before do_populate_sdk do_build do_rm_work +addtask do_create_spdx after do_create_recipe_spdx 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}" @@ -605,6 +614,7 @@ python do_create_spdx_setscene () { } addtask do_create_spdx_setscene +do_create_spdx[deptask] += "do_create_spdx" do_create_spdx[dirs] = "${SPDXWORK}" do_create_spdx[cleandirs] = "${SPDXDEPLOY} ${SPDXWORK}" do_create_spdx[depends] += " \ diff --git a/meta/classes/create-spdx-3.0.bbclass b/meta/classes/create-spdx-3.0.bbclass index d4575d61c4..c5f6462c5a 100644 --- a/meta/classes/create-spdx-3.0.bbclass +++ b/meta/classes/create-spdx-3.0.bbclass @@ -159,11 +159,18 @@ SPDX3_DEP_FILES = "\ ${SPDX_LICENSES}:True \ " -python do_create_spdx() { +python do_create_recipe_spdx() { import oe.spdx30_tasks - oe.spdx30_tasks.create_spdx(d) + oe.spdx30_tasks.create_recipe_spdx(d) } -do_create_spdx[vardeps] += "\ +addtask do_create_recipe_spdx after do_collect_spdx_deps + +SSTATETASKS += "do_create_recipe_spdx" +do_create_recipe_spdx[sstate-inputdirs] = "${SPDXRECIPEDEPLOY}" +do_create_recipe_spdx[sstate-outputdirs] = "${DEPLOY_DIR_SPDX}" +do_create_recipe_spdx[file-checksums] += "${SPDX3_DEP_FILES}" +do_create_recipe_spdx[cleandirs] = "${SPDXRECIPEDEPLOY}" +do_create_recipe_spdx[vardeps] += "\ SPDX_INCLUDE_BITBAKE_PARENT_BUILD \ SPDX_PACKAGE_ADDITIONAL_PURPOSE \ SPDX_PROFILES \ @@ -171,7 +178,18 @@ do_create_spdx[vardeps] += "\ SPDX_UUID_NAMESPACE \ " +python do_create_recipe_spdx_setscene () { + sstate_setscene(d) +} +addtask do_create_recipe_spdx_setscene + +python do_create_spdx() { + import oe.spdx30_tasks + oe.spdx30_tasks.create_spdx(d) +} addtask do_create_spdx after \ + do_unpack \ + do_create_recipe_spdx \ do_collect_spdx_deps \ do_deploy_source_date_epoch \ do_populate_sysroot do_package do_packagedata \ @@ -181,18 +199,25 @@ SSTATETASKS += "do_create_spdx" do_create_spdx[sstate-inputdirs] = "${SPDXDEPLOY}" do_create_spdx[sstate-outputdirs] = "${DEPLOY_DIR_SPDX}" do_create_spdx[file-checksums] += "${SPDX3_DEP_FILES}" - -python do_create_spdx_setscene () { - sstate_setscene(d) -} -addtask do_create_spdx_setscene - +do_create_spdx[deptask] += "do_create_spdx" do_create_spdx[dirs] = "${SPDXWORK}" do_create_spdx[cleandirs] = "${SPDXDEPLOY} ${SPDXWORK}" do_create_spdx[depends] += " \ ${PATCHDEPENDENCY} \ ${@create_spdx_source_deps(d)} \ " +do_create_spdx[vardeps] += "\ + SPDX_INCLUDE_BITBAKE_PARENT_BUILD \ + SPDX_PACKAGE_ADDITIONAL_PURPOSE \ + SPDX_PROFILES \ + SPDX_NAMESPACE_PREFIX \ + SPDX_UUID_NAMESPACE \ + " + +python do_create_spdx_setscene () { + sstate_setscene(d) +} +addtask do_create_spdx_setscene python do_create_package_spdx() { import oe.spdx30_tasks @@ -205,16 +230,15 @@ SSTATETASKS += "do_create_package_spdx" do_create_package_spdx[sstate-inputdirs] = "${SPDXRUNTIMEDEPLOY}" do_create_package_spdx[sstate-outputdirs] = "${DEPLOY_DIR_SPDX}" do_create_package_spdx[file-checksums] += "${SPDX3_DEP_FILES}" +do_create_package_spdx[dirs] = "${SPDXRUNTIMEDEPLOY}" +do_create_package_spdx[cleandirs] = "${SPDXRUNTIMEDEPLOY}" +do_create_package_spdx[rdeptask] = "do_create_spdx" python do_create_package_spdx_setscene () { sstate_setscene(d) } addtask do_create_package_spdx_setscene -do_create_package_spdx[dirs] = "${SPDXRUNTIMEDEPLOY}" -do_create_package_spdx[cleandirs] = "${SPDXRUNTIMEDEPLOY}" -do_create_package_spdx[rdeptask] = "do_create_spdx" - python spdx30_build_started_handler () { import oe.spdx30_tasks d = e.data.createCopy() diff --git a/meta/classes/spdx-common.bbclass b/meta/classes/spdx-common.bbclass index 3110230c9e..2804c27b0b 100644 --- a/meta/classes/spdx-common.bbclass +++ b/meta/classes/spdx-common.bbclass @@ -23,6 +23,7 @@ SPDXDEPS = "${SPDXDIR}/deps.json" SPDX_TOOL_NAME ??= "oe-spdx-creator" SPDX_TOOL_VERSION ??= "1.0" +SPDXRECIPEDEPLOY = "${SPDXDIR}/recipe-deploy" SPDXRUNTIMEDEPLOY = "${SPDXDIR}/runtime-deploy" SPDX_INCLUDE_SOURCES ??= "0" @@ -72,6 +73,8 @@ def create_spdx_source_deps(d): # ourselves if oe.spdx_common.has_task(d, "do_unpack"): deps.append("%s:do_unpack" % pn) + if oe.spdx_common.has_task(d, "do_patch"): + deps.append("%s:do_patch" % pn) if oe.spdx_common.is_work_shared_spdx(d) and \ oe.spdx_common.process_sources(d): @@ -97,8 +100,8 @@ python do_collect_spdx_deps() { # This task calculates the build time dependencies of the recipe, and is # required because while a task can deptask on itself, those dependencies # do not show up in BB_TASKDEPDATA. To work around that, this task does the - # deptask on do_create_spdx and writes out the dependencies it finds, then - # do_create_spdx reads in the found dependencies when writing the actual + # deptask on do_create_recipe_spdx and writes out the dependencies it finds, then + # downstream tasks read in the found dependencies when writing the actual # SPDX document import json import oe.spdx_common @@ -106,15 +109,13 @@ python do_collect_spdx_deps() { spdx_deps_file = Path(d.getVar("SPDXDEPS")) - deps = oe.spdx_common.collect_direct_deps(d, "do_create_spdx") + deps = oe.spdx_common.collect_direct_deps(d, "do_create_recipe_spdx") with spdx_deps_file.open("w") as f: json.dump(deps, f) } -# NOTE: depending on do_unpack is a hack that is necessary to get it's dependencies for archive the source -addtask do_collect_spdx_deps after do_unpack -do_collect_spdx_deps[depends] += "${PATCHDEPENDENCY}" -do_collect_spdx_deps[deptask] = "do_create_spdx" +addtask do_collect_spdx_deps +do_collect_spdx_deps[deptask] = "do_create_recipe_spdx" do_collect_spdx_deps[dirs] = "${SPDXDIR}" oe.spdx_common.collect_direct_deps[vardepsexclude] += "BB_TASKDEPDATA" diff --git a/meta/lib/oe/spdx30_tasks.py b/meta/lib/oe/spdx30_tasks.py index 99f2892dfb..a8b4525e3d 100644 --- a/meta/lib/oe/spdx30_tasks.py +++ b/meta/lib/oe/spdx30_tasks.py @@ -32,7 +32,9 @@ def set_timestamp_now(d, o, prop): delattr(o, prop) -def add_license_expression(d, objset, license_expression, license_data): +def add_license_expression( + d, objset, license_expression, license_data, search_objsets=[] +): simple_license_text = {} license_text_map = {} license_ref_idx = 0 @@ -44,14 +46,15 @@ def add_license_expression(d, objset, license_expression, license_data): if name in simple_license_text: return simple_license_text[name] - lic = objset.find_filter( - oe.spdx30.simplelicensing_SimpleLicensingText, - name=name, - ) + for o in [objset] + search_objsets: + lic = o.find_filter( + oe.spdx30.simplelicensing_SimpleLicensingText, + name=name, + ) - if lic is not None: - simple_license_text[name] = lic - return lic + if lic is not None: + simple_license_text[name] = lic + return lic lic = objset.add( oe.spdx30.simplelicensing_SimpleLicensingText( @@ -178,7 +181,9 @@ def add_package_files( # Check if file is compiled if check_compiled_sources: - if not oe.spdx_common.is_compiled_source(filename, compiled_sources, types): + if not oe.spdx_common.is_compiled_source( + filename, compiled_sources, types + ): continue spdx_file = objset.new_file( @@ -293,17 +298,16 @@ def get_package_sources_from_debug( return dep_source_files -def collect_dep_objsets(d, build): +def collect_dep_objsets(d, subdir, fn_prefix, obj_type, **attr_filter): deps = oe.spdx_common.get_spdx_deps(d) dep_objsets = [] - dep_builds = set() + dep_objs = set() - dep_build_spdxids = set() for dep in deps: bb.debug(1, "Fetching SPDX for dependency %s" % (dep.pn)) - dep_build, dep_objset = oe.sbom30.find_root_obj_in_jsonld( - d, "recipes", "recipe-" + dep.pn, oe.spdx30.build_Build + dep_obj, dep_objset = oe.sbom30.find_root_obj_in_jsonld( + d, subdir, fn_prefix + dep.pn, obj_type, **attr_filter ) # If the dependency is part of the taskhash, return it to be linked # against. Otherwise, it cannot be linked against because this recipe @@ -311,10 +315,10 @@ def collect_dep_objsets(d, build): if dep.in_taskhash: dep_objsets.append(dep_objset) - # The build _can_ be linked against (by alias) - dep_builds.add(dep_build) + # The object _can_ be linked against (by alias) + dep_objs.add(dep_obj) - return dep_objsets, dep_builds + return dep_objsets, dep_objs def index_sources_by_hash(sources, dest): @@ -423,9 +427,7 @@ def add_download_files(d, objset): if fd.method.supports_checksum(fd): # TODO Need something better than hard coding this for checksum_id in ["sha256", "sha1"]: - expected_checksum = getattr( - fd, "%s_expected" % checksum_id, None - ) + expected_checksum = getattr(fd, "%s_expected" % checksum_id, None) if expected_checksum is None: continue @@ -462,50 +464,96 @@ def set_purposes(d, element, *var_names, force_purposes=[]): ] -def create_spdx(d): - def set_var_field(var, obj, name, package=None): - val = None - if package: - val = d.getVar("%s:%s" % (var, package)) +def set_purls(spdx_package, purls): + if purls: + spdx_package.software_packageUrl = purls[0] - if not val: - val = d.getVar(var) + for p in sorted(set(purls)): + spdx_package.externalIdentifier.append( + oe.spdx30.ExternalIdentifier( + externalIdentifierType=oe.spdx30.ExternalIdentifierType.packageUrl, + identifier=p, + ) + ) - if val: - setattr(obj, name, val) + +def create_recipe_spdx(d): + deploydir = Path(d.getVar("SPDXRECIPEDEPLOY")) + deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX")) + pn = d.getVar("PN") license_data = oe.spdx_common.load_spdx_license_data(d) - deploydir = Path(d.getVar("SPDXDEPLOY")) - deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX")) - spdx_workdir = Path(d.getVar("SPDXWORK")) - include_sources = d.getVar("SPDX_INCLUDE_SOURCES") == "1" - pkg_arch = d.getVar("SSTATE_PKGARCH") - is_native = bb.data.inherits_class("native", d) or bb.data.inherits_class( - "cross", d - ) include_vex = d.getVar("SPDX_INCLUDE_VEX") if not include_vex in ("none", "current", "all"): bb.fatal("SPDX_INCLUDE_VEX must be one of 'none', 'current', 'all'") - build_objset = oe.sbom30.ObjectSet.new_objset(d, "recipe-" + d.getVar("PN")) + recipe_objset = oe.sbom30.ObjectSet.new_objset(d, "static-" + pn) - build = build_objset.new_task_build("recipe", "recipe") - build_objset.set_element_alias(build) + recipe = recipe_objset.add_root( + oe.spdx30.software_Package( + _id=recipe_objset.new_spdxid("recipe", pn), + creationInfo=recipe_objset.doc.creationInfo, + name=d.getVar("PN"), + software_packageVersion=d.getVar("PV"), + software_primaryPurpose=oe.spdx30.software_SoftwarePurpose.specification, + software_sourceInfo=json.dumps( + { + "FILENAME": os.path.basename(d.getVar("FILE")), + "FILE_LAYERNAME": d.getVar("FILE_LAYERNAME"), + }, + separators=(",", ":"), + ), + ) + ) - build_objset.doc.rootElement.append(build) + set_purls(recipe, (d.getVar("SPDX_PACKAGE_URLS") or "").split()) + + # TODO: This doesn't work before do_unpack because the license text has to + # be available for recipes with NO_GENERIC_LICENSE + # recipe_spdx_license = add_license_expression( + # d, + # recipe_objset, + # d.getVar("LICENSE"), + # license_data, + # ) + # recipe_objset.new_relationship( + # [recipe], + # oe.spdx30.RelationshipType.hasDeclaredLicense, + # [oe.sbom30.get_element_link_id(recipe_spdx_license)], + # ) + + if val := d.getVar("HOMEPAGE"): + recipe.software_homePage = val + + if val := d.getVar("SUMMARY"): + recipe.summary = val + + if val := d.getVar("DESCRIPTION"): + recipe.description = val + + for cpe_id in oe.cve_check.get_cpe_ids( + d.getVar("CVE_PRODUCT"), d.getVar("CVE_VERSION") + ): + recipe.externalIdentifier.append( + oe.spdx30.ExternalIdentifier( + externalIdentifierType=oe.spdx30.ExternalIdentifierType.cpe23, + identifier=cpe_id, + ) + ) - build_objset.set_is_native(is_native) + dep_objsets, dep_recipes = collect_dep_objsets( + d, "static", "static-", oe.spdx30.software_Package + ) - for var in (d.getVar("SPDX_CUSTOM_ANNOTATION_VARS") or "").split(): - build_objset.new_annotation( - build, - "%s=%s" % (var, d.getVar(var)), - oe.spdx30.AnnotationType.other, + if dep_recipes: + recipe_objset.new_scoped_relationship( + [recipe], + oe.spdx30.RelationshipType.dependsOn, + oe.spdx30.LifecycleScopeType.build, + sorted(oe.sbom30.get_element_link_id(dep) for dep in dep_recipes), ) - build_inputs = set() - # Add CVEs cve_by_status = {} if include_vex != "none": @@ -514,7 +562,7 @@ def create_spdx(d): decoded_status = { "mapping": patched_cve["abbrev-status"], "detail": patched_cve["status"], - "description": patched_cve.get("justification", None) + "description": patched_cve.get("justification", None), } # If this CVE is fixed upstream, skip it unless all CVEs are @@ -531,8 +579,7 @@ def create_spdx(d): bb.debug(1, "Skipping %s since it is already fixed upstream" % cve) continue - spdx_cve = build_objset.new_cve_vuln(cve) - build_objset.set_element_alias(spdx_cve) + spdx_cve = recipe_objset.new_cve_vuln(cve) cve_by_status.setdefault(decoded_status["mapping"], {})[cve] = ( spdx_cve, @@ -540,13 +587,118 @@ def create_spdx(d): decoded_status["description"], ) + all_cves = set() + for status, cves in cve_by_status.items(): + for cve, items in cves.items(): + spdx_cve, detail, description = items + spdx_cve_id = oe.sbom30.get_element_link_id(spdx_cve) + + all_cves.add(spdx_cve) + + if status == "Patched": + recipe_objset.new_vex_patched_relationship([spdx_cve_id], [recipe]) + elif status == "Unpatched": + recipe_objset.new_vex_unpatched_relationship([spdx_cve_id], [recipe]) + elif status == "Ignored": + spdx_vex = recipe_objset.new_vex_ignored_relationship( + [spdx_cve_id], + [recipe], + impact_statement=description, + ) + + vex_just_type = d.getVarFlag("CVE_CHECK_VEX_JUSTIFICATION", detail) + if vex_just_type: + if ( + vex_just_type + not in oe.spdx30.security_VexJustificationType.NAMED_INDIVIDUALS + ): + bb.fatal( + f"Unknown vex justification '{vex_just_type}', detail '{detail}', for ignored {cve}" + ) + + for v in spdx_vex: + v.security_justificationType = ( + oe.spdx30.security_VexJustificationType.NAMED_INDIVIDUALS[ + vex_just_type + ] + ) + + elif status == "Unknown": + bb.note(f"Skipping {cve} with status 'Unknown'") + else: + bb.fatal(f"Unknown {cve} status '{status}'") + + if all_cves: + recipe_objset.new_relationship( + [recipe], + oe.spdx30.RelationshipType.hasAssociatedVulnerability, + sorted(list(all_cves)), + ) + + oe.sbom30.write_recipe_jsonld_doc(d, recipe_objset, "static", deploydir) + + +def load_recipe_spdx(d): + + return oe.sbom30.find_root_obj_in_jsonld( + d, + "static", + "static-" + d.getVar("PN"), + oe.spdx30.software_Package, + ) + + +def create_spdx(d): + def set_var_field(var, obj, name, package=None): + val = None + if package: + val = d.getVar("%s:%s" % (var, package)) + + if not val: + val = d.getVar(var) + + if val: + setattr(obj, name, val) + + license_data = oe.spdx_common.load_spdx_license_data(d) + + pn = d.getVar("PN") + deploydir = Path(d.getVar("SPDXDEPLOY")) + deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX")) + spdx_workdir = Path(d.getVar("SPDXWORK")) + include_sources = d.getVar("SPDX_INCLUDE_SOURCES") == "1" + pkg_arch = d.getVar("SSTATE_PKGARCH") + is_native = bb.data.inherits_class("native", d) or bb.data.inherits_class( + "cross", d + ) + + recipe, recipe_objset = load_recipe_spdx(d) + + build_objset = oe.sbom30.ObjectSet.new_objset(d, "build-" + pn) + + build = build_objset.new_task_build("recipe", "recipe") + build_objset.set_element_alias(build) + + build_objset.doc.rootElement.append(build) + + build_objset.set_is_native(is_native) + + for var in (d.getVar("SPDX_CUSTOM_ANNOTATION_VARS") or "").split(): + build_objset.new_annotation( + build, + "%s=%s" % (var, d.getVar(var)), + oe.spdx30.AnnotationType.other, + ) + + build_inputs = set() + cpe_ids = oe.cve_check.get_cpe_ids(d.getVar("CVE_PRODUCT"), d.getVar("CVE_VERSION")) source_files = add_download_files(d, build_objset) build_inputs |= source_files recipe_spdx_license = add_license_expression( - d, build_objset, d.getVar("LICENSE"), license_data + d, build_objset, d.getVar("LICENSE"), license_data, [recipe_objset] ) build_objset.new_relationship( source_files, @@ -575,7 +727,10 @@ def create_spdx(d): build_inputs |= files index_sources_by_hash(files, dep_sources) - dep_objsets, dep_builds = collect_dep_objsets(d, build) + dep_objsets, dep_builds = collect_dep_objsets( + d, "builds", "build-", oe.spdx30.build_Build + ) + if dep_builds: build_objset.new_scoped_relationship( [build], @@ -587,6 +742,22 @@ def create_spdx(d): debug_source_ids = set() source_hash_cache = {} + # Collect all VEX statements from the recipe + vex_statements = {} + for rel in recipe_objset.foreach_filter( + oe.spdx30.Relationship, + relationshipType=oe.spdx30.RelationshipType.hasAssociatedVulnerability, + ): + for cve in rel.to: + vex_statements[cve] = [] + + for cve in vex_statements.keys(): + for rel in recipe_objset.foreach_filter( + oe.spdx30.security_VexVulnAssessmentRelationship, + from_=cve, + ): + vex_statements[cve].append(rel) + # Write out the package SPDX data now. It is not complete as we cannot # write the runtime data, so write it to a staging area and a later task # will write out the final collection @@ -645,16 +816,7 @@ def create_spdx(d): or "" ).split() - if purls: - spdx_package.software_packageUrl = purls[0] - - for p in sorted(set(purls)): - spdx_package.externalIdentifier.append( - oe.spdx30.ExternalIdentifier( - externalIdentifierType=oe.spdx30.ExternalIdentifierType.packageUrl, - identifier=p, - ) - ) + set_purls(spdx_package, purls) pkg_objset.new_scoped_relationship( [oe.sbom30.get_element_link_id(build)], @@ -663,6 +825,13 @@ def create_spdx(d): [spdx_package], ) + pkg_objset.new_scoped_relationship( + [oe.sbom30.get_element_link_id(recipe)], + oe.spdx30.RelationshipType.generates, + oe.spdx30.LifecycleScopeType.build, + [spdx_package], + ) + for cpe_id in cpe_ids: spdx_package.externalIdentifier.append( oe.spdx30.ExternalIdentifier( @@ -696,7 +865,11 @@ def create_spdx(d): package_license = d.getVar("LICENSE:%s" % package) if package_license and package_license != d.getVar("LICENSE"): package_spdx_license = add_license_expression( - d, build_objset, package_license, license_data + d, + build_objset, + package_license, + license_data, + [recipe_objset], ) else: package_spdx_license = recipe_spdx_license @@ -721,58 +894,41 @@ def create_spdx(d): [oe.sbom30.get_element_link_id(concluded_spdx_license)], ) - # NOTE: CVE Elements live in the recipe collection - all_cves = set() - for status, cves in cve_by_status.items(): - for cve, items in cves.items(): - spdx_cve, detail, description = items - spdx_cve_id = oe.sbom30.get_element_link_id(spdx_cve) - - all_cves.add(spdx_cve_id) + # Copy CVEs from recipe + if vex_statements: + pkg_objset.new_relationship( + [spdx_package], + oe.spdx30.RelationshipType.hasAssociatedVulnerability, + sorted( + oe.sbom30.get_element_link_id(cve) + for cve in vex_statements.keys() + ), + ) - if status == "Patched": + for cve, vexes in vex_statements.items(): + for vex in vexes: + if vex.relationshipType == oe.spdx30.RelationshipType.fixedIn: pkg_objset.new_vex_patched_relationship( - [spdx_cve_id], [spdx_package] + [oe.sbom30.get_element_link_id(cve)], [spdx_package] ) - elif status == "Unpatched": + elif vex.relationshipType == oe.spdx30.RelationshipType.affects: pkg_objset.new_vex_unpatched_relationship( - [spdx_cve_id], [spdx_package] + [oe.sbom30.get_element_link_id(cve)], [spdx_package] ) - elif status == "Ignored": + elif ( + vex.relationshipType == oe.spdx30.RelationshipType.doesNotAffect + ): spdx_vex = pkg_objset.new_vex_ignored_relationship( - [spdx_cve_id], + [oe.sbom30.get_element_link_id(cve)], [spdx_package], - impact_statement=description, + impact_statement=vex.security_impactStatement, ) - vex_just_type = d.getVarFlag( - "CVE_CHECK_VEX_JUSTIFICATION", detail - ) - if vex_just_type: - if ( - vex_just_type - not in oe.spdx30.security_VexJustificationType.NAMED_INDIVIDUALS - ): - bb.fatal( - f"Unknown vex justification '{vex_just_type}', detail '{detail}', for ignored {cve}" - ) - + if vex.security_justificationType: for v in spdx_vex: - v.security_justificationType = oe.spdx30.security_VexJustificationType.NAMED_INDIVIDUALS[ - vex_just_type - ] - - elif status == "Unknown": - bb.note(f"Skipping {cve} with status 'Unknown'") - else: - bb.fatal(f"Unknown {cve} status '{status}'") - - if all_cves: - pkg_objset.new_relationship( - [spdx_package], - oe.spdx30.RelationshipType.hasAssociatedVulnerability, - sorted(list(all_cves)), - ) + v.security_justificationType = ( + vex.security_justificationType + ) bb.debug(1, "Adding package files to SPDX for package %s" % pkg_name) package_files = add_package_files( @@ -851,14 +1007,15 @@ def create_spdx(d): status = "enabled" if feature in enabled else "disabled" build.build_parameter.append( oe.spdx30.DictionaryEntry( - key=f"PACKAGECONFIG:{feature}", - value=status + key=f"PACKAGECONFIG:{feature}", value=status ) ) - bb.note(f"Added PACKAGECONFIG entries: {len(enabled)} enabled, {len(disabled)} disabled") + bb.note( + f"Added PACKAGECONFIG entries: {len(enabled)} enabled, {len(disabled)} disabled" + ) - oe.sbom30.write_recipe_jsonld_doc(d, build_objset, "recipes", deploydir) + oe.sbom30.write_recipe_jsonld_doc(d, build_objset, "builds", deploydir) def create_package_spdx(d): @@ -1197,17 +1354,17 @@ def create_image_spdx(d): image_path = image_deploy_dir / image_filename if os.path.isdir(image_path): a = add_package_files( - d, - objset, - image_path, - lambda file_counter: objset.new_spdxid( - "imagefile", str(file_counter) - ), - lambda filepath: [], - license_data=None, - ignore_dirs=[], - ignore_top_level_dirs=[], - archive=None, + d, + objset, + image_path, + lambda file_counter: objset.new_spdxid( + "imagefile", str(file_counter) + ), + lambda filepath: [], + license_data=None, + ignore_dirs=[], + ignore_top_level_dirs=[], + archive=None, ) artifacts.extend(a) else: @@ -1234,7 +1391,6 @@ def create_image_spdx(d): set_timestamp_now(d, a, "builtTime") - if artifacts: objset.new_scoped_relationship( [image_build], From patchwork Thu Feb 26 17:33:05 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joshua Watt X-Patchwork-Id: 82026 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 2BD67FD8FEC for ; Thu, 26 Feb 2026 17:39:42 +0000 (UTC) Received: from mail-ot1-f41.google.com (mail-ot1-f41.google.com [209.85.210.41]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.75804.1772127577096476130 for ; Thu, 26 Feb 2026 09:39:37 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20230601 header.b=NStkYOpw; spf=pass (domain: gmail.com, ip: 209.85.210.41, mailfrom: jpewhacker@gmail.com) Received: by mail-ot1-f41.google.com with SMTP id 46e09a7af769-7d4c7d04890so551548a34.3 for ; Thu, 26 Feb 2026 09:39:37 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772127576; x=1772732376; darn=lists.openembedded.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=ZP0Wp9MniMcKfuYvIt2TNmTYjdil8nqBqroSCEaCiYo=; b=NStkYOpwVxQKjdqS53osI8AL3E3r6Rj7qcAOkPuyighAaWVURjVq0BYTNJ92ri5NFN /QT8xaVcBcxYWkC/AHXJFfgxnWQmwsvvr5QfvtS2trzOndikRisFD4K4vLSfTis0hxmx Np6fI/v4C0xX9mWYTMeS8oRT42baQ0drWBncRSsfxyCRiNljySK3btNr/8ksks8Tmte8 Vk7kDs3xbrXJJNaWOEtelRSaS8UNX1aQB9/S0KAlkBJHaHjkD6IIxDr5rWOcU63V/smd Ze5dLQgIY40kEMI/B1cynPwzKNCVv9jQDfByCbi97A4fJOgoBLdP9DJo2kiRJgBYcMUR 82Uw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772127576; x=1772732376; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=ZP0Wp9MniMcKfuYvIt2TNmTYjdil8nqBqroSCEaCiYo=; b=QWftGslsUrMoP3E1uJDdso4PCIIPdJ7KQ4inznrAyquMMhLBx87feh9fniXe8RH/0P wiIulm1RFmsqbVYAaO86LFOzwG47fQq5PVT70s1rw2dJDpF7enM67I68Tz9mG9/LQmnS 0RiOaqvciWL4Bv5TEkFAot9u+V1CIF7LDmlGhnx7l3135FOV3ZUMvtmjsLcXDPDeoHRR a8ulRFemOFvgNhjyfjdUgE/n/LaihA/xOkLF+maI1j84FcGCR/sa6KAjcqjfqk34vXls w4/yXe4pGB1RLGTTtwOGar1WDfs4jnj/sVipq062rWsDNPWN8xD37TBtsIQnLOL/J7uD Bycg== X-Gm-Message-State: AOJu0YwXRKOA5EdTvKs2EXKOaruF52KqiBGLNdriqeJQMcYWuHWOZjRN ymXOfGfb4qD3YVE9y3IhTQ0wvUcqV6FKWPkpvKfyxrQiRbP6wSsB8Abe8OmLhw== X-Gm-Gg: ATEYQzw7MTatEePjRTz4FtODOEsQg1D4Acb1nd3K2EWtFJUXa1OPBNi0PZNUsFl64Qr MepvPkVILkbgWZ06SV3/P5gZv13QZX10XYrKuAHh1NYed2nKXyhqIBNDqOW6PD6p2/DmF6lk3+5 IDLyxG+7aMJ2pikzEJOE04/3Qu0Uf4uPrckNyQoozauCNghUHScZSdpUiWT0vdFQ0iIQF4URLLf iTfrK4aDmigD5lf4Y08E66mhFD0DcI5jjUzZzQ5E0V/wkHBWjPypFNrkcQc6ymmSEh7/TfWWeer +JDNqTiUhmKpD6otyJknCAGqh4Cp1xfakvUiwvKI1RyPFNvKwdNXjrjpOsPChOHXrnhgM3biVJt +fMFFiLEQ32TgmgknkISnAyfFe9QW/JAboS16mJbjiw0SUoupZ56Lz0scJiWyY9d4XTNoPxsd1R h3Nhxb0I73GpDC1Pml0GcW X-Received: by 2002:a05:6830:6201:b0:7c7:69c8:2d2 with SMTP id 46e09a7af769-7d591b324dbmr2695a34.13.1772127576135; Thu, 26 Feb 2026 09:39:36 -0800 (PST) Received: from localhost.localdomain ([2601:282:4200:11c0::6492]) by smtp.gmail.com with ESMTPSA id 46e09a7af769-7d586653f81sm2173027a34.23.2026.02.26.09.39.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 26 Feb 2026 09:39:35 -0800 (PST) From: Joshua Watt X-Google-Original-From: Joshua Watt To: openembedded-core@lists.openembedded.org Cc: benjamin.robin@bootlin.com, ross.burton@arm.com, Joshua Watt Subject: [OE-core][PATCH v3 4/8] spdx3: Add recipe SBoM task Date: Thu, 26 Feb 2026 10:33:05 -0700 Message-ID: <20260226173930.2847872-5-JPEWhacker@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260226173930.2847872-1-JPEWhacker@gmail.com> References: <20260224230234.679049-1-JPEWhacker@gmail.com> <20260226173930.2847872-1-JPEWhacker@gmail.com> MIME-Version: 1.0 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 ; Thu, 26 Feb 2026 17:39:42 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/232050 Adds a task that will create the complete recipe-level SBoM for a given target recipe, following all dependencies. For example: ``` bitbake -c create_recipe_sbom zstd ``` Would produce the complete recipe SBoM for the zstd recipe, include all build time dependencies (recursively). The complete SBoM for all (target) recipes can be built with: ``` bitbake -c create_recipe_sbom meta-world-recipe-sbom ``` Co-authored-by: Benjamin Robin Signed-off-by: Joshua Watt --- meta/classes/create-spdx-3.0.bbclass | 28 +++++++++++++++++++ meta/classes/spdx-common.bbclass | 1 + meta/lib/oe/spdx30_tasks.py | 10 +++++++ .../meta/meta-world-recipe-sbom.bb | 28 +++++++++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 meta/recipes-core/meta/meta-world-recipe-sbom.bb diff --git a/meta/classes/create-spdx-3.0.bbclass b/meta/classes/create-spdx-3.0.bbclass index c5f6462c5a..782aa2c52e 100644 --- a/meta/classes/create-spdx-3.0.bbclass +++ b/meta/classes/create-spdx-3.0.bbclass @@ -239,6 +239,34 @@ python do_create_package_spdx_setscene () { } addtask do_create_package_spdx_setscene +addtask do_create_recipe_sbom after create_recipe_spdx +python do_create_recipe_sbom() { + import oe.spdx30_tasks + from pathlib import Path + deploydir = Path(d.getVar("SPDXRECIPESBOMDEPLOY")) + oe.spdx30_tasks.create_recipe_sbom(d, deploydir) +} + +SSTATETASKS += "do_create_recipe_sbom" +do_create_recipe_sbom[recrdeptask] = "do_create_recipe_spdx" +do_create_recipe_sbom[nostamp] = "1" +do_create_recipe_sbom[sstate-inputdirs] = "${SPDXRECIPESBOMDEPLOY}" +do_create_recipe_sbom[sstate-outputdirs] = "${DEPLOY_DIR_IMAGE}" +do_create_recipe_sbom[file-checksums] += "${SPDX3_DEP_FILES}" +do_create_recipe_sbom[cleandirs] = "${SPDXRECIPESBOMDEPLOY}" +do_create_recipe_sbom[vardeps] += "\ + SPDX_INCLUDE_BITBAKE_PARENT_BUILD \ + SPDX_PACKAGE_ADDITIONAL_PURPOSE \ + SPDX_PROFILES \ + SPDX_NAMESPACE_PREFIX \ + SPDX_UUID_NAMESPACE \ + " + +python do_create_recipe_sbom_setscene () { + sstate_setscene(d) +} +addtask do_create_recipe_sbom_setscene + python spdx30_build_started_handler () { import oe.spdx30_tasks d = e.data.createCopy() diff --git a/meta/classes/spdx-common.bbclass b/meta/classes/spdx-common.bbclass index 2804c27b0b..1ec4877a6a 100644 --- a/meta/classes/spdx-common.bbclass +++ b/meta/classes/spdx-common.bbclass @@ -25,6 +25,7 @@ SPDX_TOOL_VERSION ??= "1.0" SPDXRECIPEDEPLOY = "${SPDXDIR}/recipe-deploy" SPDXRUNTIMEDEPLOY = "${SPDXDIR}/runtime-deploy" +SPDXRECIPESBOMDEPLOY = "${SPDXDIR}/recipes-bom-deploy" SPDX_INCLUDE_SOURCES ??= "0" SPDX_INCLUDE_COMPILED_SOURCES ??= "0" diff --git a/meta/lib/oe/spdx30_tasks.py b/meta/lib/oe/spdx30_tasks.py index a8b4525e3d..9a312a870d 100644 --- a/meta/lib/oe/spdx30_tasks.py +++ b/meta/lib/oe/spdx30_tasks.py @@ -1564,3 +1564,13 @@ def create_sdk_sbom(d, sdk_deploydir, spdx_work_dir, toolchain_outputname): oe.sbom30.write_jsonld_doc( d, objset, sdk_deploydir / (toolchain_outputname + ".spdx.json") ) + + +def create_recipe_sbom(d, deploydir): + sbom_name = d.getVar("PN") + "-recipe-sbom" + + recipe, recipe_objset = load_recipe_spdx(d) + + objset, sbom = oe.sbom30.create_sbom(d, sbom_name, [recipe], [recipe_objset]) + + oe.sbom30.write_jsonld_doc(d, objset, deploydir / (sbom_name + ".spdx.json")) diff --git a/meta/recipes-core/meta/meta-world-recipe-sbom.bb b/meta/recipes-core/meta/meta-world-recipe-sbom.bb new file mode 100644 index 0000000000..5731e27c83 --- /dev/null +++ b/meta/recipes-core/meta/meta-world-recipe-sbom.bb @@ -0,0 +1,28 @@ +SUMMARY = "Generates a combined SBoM for all world recipes" +LICENSE = "MIT" + +INHIBIT_DEFAULT_DEPS = "1" + +PACKAGE_ARCH = "${MACHINE_ARCH}" + +inherit nopackages +deltask do_fetch +deltask do_unpack +deltask do_patch +deltask do_configure +deltask do_compile +deltask do_install + +do_prepare_recipe_sysroot[deptask] = "" + +WORLD_SBOM_EXCLUDE ?= "" + +EXCLUDE_FROM_WORLD = "1" + +python calculate_extra_depends() { + exclude = set('${WORLD_SBOM_EXCLUDE}'.split()) + exclude |= set(f"{v}-{self_pn}" for v in '${MULTILIB_VARIANTS}'.split()) + exclude.add(self_pn) + + deps.extend(p for p in world_target if p not in exclude) +} From patchwork Thu Feb 26 17:33:06 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joshua Watt X-Patchwork-Id: 82030 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 483A3FD8FF1 for ; Thu, 26 Feb 2026 17:39:42 +0000 (UTC) Received: from mail-ot1-f52.google.com (mail-ot1-f52.google.com [209.85.210.52]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.75833.1772127577818028665 for ; Thu, 26 Feb 2026 09:39:37 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20230601 header.b=gVLAlyhk; spf=pass (domain: gmail.com, ip: 209.85.210.52, mailfrom: jpewhacker@gmail.com) Received: by mail-ot1-f52.google.com with SMTP id 46e09a7af769-7d4bd4db87aso491197a34.2 for ; Thu, 26 Feb 2026 09:39:37 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772127577; x=1772732377; darn=lists.openembedded.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=B26QEFGZ1BfU8osvAqsZdiqlbAEmeNAht5QsWjTxO+M=; b=gVLAlyhkrBSUjsS48jQ09o889ch+KoEE7jAva+RbfP9eJf9Rb9LbMdhU4VV0IwvY15 +zPzNlRFEVXp0WYy/BDSEmWRj3i7crOiNC/TGWog2YvC7wFH27+ugklqTpuw5p/I3zZZ NdTc1KPs/XOMypY+XEYlySwJ14wiLGE/nI45YnUIoWJt6qaKlt0SQiX5ly6EI0c9ZcMk hEY92BI4WfZnVwsK2CWYiFv18b5XEgUxhCb7mwawsD1n7UdafYECFraLTn4tsKTQ2+96 a7Gn2g8Y/hwN9fjKCAwXARFhdmoZmJQGIXq1h5rh2XwPFeXKCeGIptTAyLOoym27svbU 3kmw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772127577; x=1772732377; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=B26QEFGZ1BfU8osvAqsZdiqlbAEmeNAht5QsWjTxO+M=; b=Op8eaImrDrgw5UDf/uGuX9Jl/RWLqeQUrFmjpRMST2rXJMAMrgNX6W49GrgwtJBAJe i1A8C39qwd8Ha3beIjFyn+SiTSxL9ZgBfZrNGdybzuwaUe029p53Xfn7dmeSNTTVr2au cI1AzLxiXdyLMEamE0fPocZtSFgr0uNqrI7fGFliFdblfuE3OLdnr2mN5jPYCdcrz7DX 5Ve0oTWhAepAszvciDpNrYtgVGBOQazmmXwztUaL5fDp2h8hTj3hwdxyaa2B+H21AZMd ZXzYlb9cAne2R3XhmIM63n7IbzyfoB8RYMR9aqXX4iZIfx3va14C6Pyk/K3A5XTx8mLr rUSg== X-Gm-Message-State: AOJu0YwQylMI5eQD1qJE5nw/cPAz+4k16aPF07ucc8a6nfszg0LrMQFy mVtFaul2cY95WE0Lis2NKjMkIDXJId5Ilfhk/b/QFJLDSgoBJ1FFUK2LMWGGww== X-Gm-Gg: ATEYQzyxy6P7VzhAH5tMPIKRg3Q6Lfnoq3ET1tRKAQlaRsg1790AkIWQsU3Fd2CURyJ iZgqnD4fX6LhXhBYWMDcznL8aJg8c5R06Ub45CYqK1YSMdx8nHjwlqB+/2gd30lkPhcXIL2xZ3v iRQRvxgfoFvXAt6SRPMJNWv4/Y/Y2gGCLlLc9eXa5XVb5FzOVJ82QTDDxw0b2s295mI607fGSgD QhGEpsxDJ0unFrBna0NGMYC9E7TfwhA1da3RyZE+kq2l+vIFCecvHgCsDAhPJ8ISr6+a5O4zJ4t Ax+BwWz2mm78fOvv3vcQS6rvcP/Z/fy7rGIou3q0rxe3Xk16a6hqjoLdDk00HNFgolwiS3DUVwC W+cUxGz/tEtO1Kvv8UiyQ4YgGiY0QhuvvGqASVrS4yKVTf+bVZx53O2Pbr3Dnnu5Q4m68UlO5J/ YGWRxQr+MTsrbdU2j1/A96vGFwLWJcz1s= X-Received: by 2002:a05:6830:3787:b0:7c7:8113:6f6e with SMTP id 46e09a7af769-7d586f605c7mr1703681a34.27.1772127576818; Thu, 26 Feb 2026 09:39:36 -0800 (PST) Received: from localhost.localdomain ([2601:282:4200:11c0::6492]) by smtp.gmail.com with ESMTPSA id 46e09a7af769-7d586653f81sm2173027a34.23.2026.02.26.09.39.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 26 Feb 2026 09:39:36 -0800 (PST) From: Joshua Watt X-Google-Original-From: Joshua Watt To: openembedded-core@lists.openembedded.org Cc: benjamin.robin@bootlin.com, ross.burton@arm.com, Joshua Watt Subject: [OE-core][PATCH v3 5/8] spdx3: Add is-native property Date: Thu, 26 Feb 2026 10:33:06 -0700 Message-ID: <20260226173930.2847872-6-JPEWhacker@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260226173930.2847872-1-JPEWhacker@gmail.com> References: <20260224230234.679049-1-JPEWhacker@gmail.com> <20260226173930.2847872-1-JPEWhacker@gmail.com> MIME-Version: 1.0 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 ; Thu, 26 Feb 2026 17:39:42 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/232051 Adds a custom is-native property to the recipe package to indicate if it is a native recipe Signed-off-by: Joshua Watt --- meta/lib/oe/sbom30.py | 20 ++++++++++++++++++++ meta/lib/oe/spdx30_tasks.py | 18 +++++++++++------- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/meta/lib/oe/sbom30.py b/meta/lib/oe/sbom30.py index 227ac51877..50a72fce39 100644 --- a/meta/lib/oe/sbom30.py +++ b/meta/lib/oe/sbom30.py @@ -118,6 +118,26 @@ class OEDocumentExtension(oe.spdx30.extension_Extension): ) +@oe.spdx30.register(OE_SPDX_BASE + "recipe-extension") +class OERecipeExtension(oe.spdx30.extension_Extension): + """ + This extension is added to recipe software_Packages to indicate various + useful bits of information about the recipe + """ + + CLOSED = True + + @classmethod + def _register_props(cls): + super()._register_props() + cls._add_property( + "is_native", + oe.spdx30.BooleanProp(), + OE_SPDX_BASE + "is-native", + max_count=1, + ) + + def spdxid_hash(*items): h = hashlib.md5() for i in items: diff --git a/meta/lib/oe/spdx30_tasks.py b/meta/lib/oe/spdx30_tasks.py index 9a312a870d..fff1ca6bea 100644 --- a/meta/lib/oe/spdx30_tasks.py +++ b/meta/lib/oe/spdx30_tasks.py @@ -477,6 +477,10 @@ def set_purls(spdx_package, purls): ) +def get_is_native(d): + return bb.data.inherits_class("native", d) or bb.data.inherits_class("cross", d) + + def create_recipe_spdx(d): deploydir = Path(d.getVar("SPDXRECIPEDEPLOY")) deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX")) @@ -507,6 +511,11 @@ def create_recipe_spdx(d): ) ) + if get_is_native(d): + ext = oe.sbom30.OERecipeExtension() + ext.is_native = True + recipe.extension.append(ext) + set_purls(recipe, (d.getVar("SPDX_PACKAGE_URLS") or "").split()) # TODO: This doesn't work before do_unpack because the license text has to @@ -668,9 +677,7 @@ def create_spdx(d): spdx_workdir = Path(d.getVar("SPDXWORK")) include_sources = d.getVar("SPDX_INCLUDE_SOURCES") == "1" pkg_arch = d.getVar("SSTATE_PKGARCH") - is_native = bb.data.inherits_class("native", d) or bb.data.inherits_class( - "cross", d - ) + is_native = get_is_native(d) recipe, recipe_objset = load_recipe_spdx(d) @@ -1021,14 +1028,11 @@ def create_spdx(d): def create_package_spdx(d): deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX")) deploydir = Path(d.getVar("SPDXRUNTIMEDEPLOY")) - is_native = bb.data.inherits_class("native", d) or bb.data.inherits_class( - "cross", d - ) providers = oe.spdx_common.collect_package_providers(d) pkg_arch = d.getVar("SSTATE_PKGARCH") - if is_native: + if get_is_native(d): return bb.build.exec_func("read_subpackage_metadata", d) From patchwork Thu Feb 26 17:33:07 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joshua Watt X-Patchwork-Id: 82027 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 3D444FD8FEE for ; Thu, 26 Feb 2026 17:39:42 +0000 (UTC) Received: from mail-ot1-f45.google.com (mail-ot1-f45.google.com [209.85.210.45]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.75806.1772127578543352908 for ; Thu, 26 Feb 2026 09:39:38 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20230601 header.b=I74Lsti5; spf=pass (domain: gmail.com, ip: 209.85.210.45, mailfrom: jpewhacker@gmail.com) Received: by mail-ot1-f45.google.com with SMTP id 46e09a7af769-7d4ba9abbecso1215619a34.1 for ; Thu, 26 Feb 2026 09:39:38 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772127577; x=1772732377; darn=lists.openembedded.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=6hV0ClIeawdfJviTL2VvpRpRo7713Nd0mIEcCehLwkw=; b=I74Lsti5Q+UJkKienBsTjROmTqZky9EWkEtaASCjaEZRFPPCaxLbfWQr1TRd77856/ olmwhNx2bLmZ4s2lh9HsULr6ZU07By1xFWgEHbMM9O1W1aDsvGIHJyilt8FH+7peI8Cd sQ12ffCo0NPON5Rit/5txqsw10gkZXCWVftsdrAOuPhm2u1Ir5wuv7yYOuMTy4jfX+ge irZJMH2hAfCeCBbfxaHnJeuKm8a/V93ApAMvaQiKPvOYWCD8e78KFGLF7TYfvB2m+ICS TmitwhDmh6QpoERm/LjDbBwexeV2mVRDL465CpbZtb+zdYA2oWL+oxyQ55Ghb1vLXj/L mhrQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772127577; x=1772732377; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=6hV0ClIeawdfJviTL2VvpRpRo7713Nd0mIEcCehLwkw=; b=i1yFGU6tjv7zik2XVJyWojVAtY9hAXA66vzj68Recuf/VRSVseOO85UekUffJQNVRq AQNcbdT0gfBwNmQGJsAG+SXpoSfm1it5cc1D5ONtAAit9HOo8WcKZ/DtaqKWeOhDZfCt X9/f2ysRyuNCO0Yf3NKzVq09Yio4Z8wDI0D7WiJvC97DlJ5IQenWlMhjxaf+9sxywzdp ByGP5dJBmTEJ1nbulS2mru4lgRtIlevuCCsMoVQ2Ua2QjykffqcJ9dEhB9TFYe9woqMw 5yDe2KfaWjJ7IzpFH8frYTzffyMeHOqhe6GfSwZUDsfhYO18qd4xl+0JSPi4pqr5Acjb bDUQ== X-Gm-Message-State: AOJu0YwtyRDhlpKUsevwTcmbOVhgUQ+wCbJvZlbCVqNBQ4RUKaAI1LYB sW6n54ijeL9FLmt+XH+yXrGcOaPQdn4CXF0jiqe071d0K/lTZVvvJPpYqyaGWw== X-Gm-Gg: ATEYQzwUfwUhtHmQVurqIJPz0FM/Ge3crgrpSUqUmCK2bZhrFDXx+hbPZeDSiWbqFJF uePS8aisTHg17D1lddAD/chXrV5BEv7B5wKW7HVO7Gtj3msv+NrqSvSYx27fW/Z93xxtRoV7857 SsMBAHirRfavicVby2LuuT16wT+dRN0+uR3aEmv5B+zVh1I7bK1jg4PUML9W4Yot98qmTOWGcW/ n9Jtcx5TQW1YSXAjVCsKT0LkTNEZd5NNkerP84vc6iH07aIv7LGRiRMVYwGOGI2QSMpAa+9AxvC cs0VchnO6pBn0TM6KlyohWhOOSaiS4M1y0q4cFCTlTOot1ejs1Vtga+bf6hOHDljIl1CgAu6yvc AoBNRQnt3pFnQlkl3UaaRy4w2gxpjzybf0ivHzg7Lc2UvP2CwRZXmki3/Uh/1T2M0kRXJmgOGRF cT+shMwTrdlckYJg/SteMi X-Received: by 2002:a05:6830:449e:b0:7cf:e4e6:2ce9 with SMTP id 46e09a7af769-7d591b3181bmr3767a34.9.1772127577559; Thu, 26 Feb 2026 09:39:37 -0800 (PST) Received: from localhost.localdomain ([2601:282:4200:11c0::6492]) by smtp.gmail.com with ESMTPSA id 46e09a7af769-7d586653f81sm2173027a34.23.2026.02.26.09.39.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 26 Feb 2026 09:39:37 -0800 (PST) From: Joshua Watt X-Google-Original-From: Joshua Watt To: openembedded-core@lists.openembedded.org Cc: benjamin.robin@bootlin.com, ross.burton@arm.com, Joshua Watt Subject: [OE-core][PATCH v3 6/8] spdx30: Include patch file information in VEX Date: Thu, 26 Feb 2026 10:33:07 -0700 Message-ID: <20260226173930.2847872-7-JPEWhacker@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260226173930.2847872-1-JPEWhacker@gmail.com> References: <20260224230234.679049-1-JPEWhacker@gmail.com> <20260226173930.2847872-1-JPEWhacker@gmail.com> MIME-Version: 1.0 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 ; Thu, 26 Feb 2026 17:39:42 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/232052 Modifies the SPDX VEX output to include the patches that fix a particular vulnerability. This is done by adding a `patchedBy` relationship from the `VexFixedVulnAssessmentRelationship` to the `File` that provides the fix. If the file can be located without fetching (e.g. is a file:// in SRC_URI), the checksum will be included. Signed-off-by: Joshua Watt --- meta/lib/oe/sbom30.py | 60 ++++++++++++++------------- meta/lib/oe/spdx30_tasks.py | 81 ++++++++++++++++++++++++++++--------- 2 files changed, 92 insertions(+), 49 deletions(-) diff --git a/meta/lib/oe/sbom30.py b/meta/lib/oe/sbom30.py index 50a72fce39..21f084dc16 100644 --- a/meta/lib/oe/sbom30.py +++ b/meta/lib/oe/sbom30.py @@ -620,37 +620,38 @@ class ObjectSet(oe.spdx30.SHACLObjectSet): ) spdx_file.extension.append(OELicenseScannedExtension()) - def new_file(self, _id, name, path, *, purposes=[]): - sha256_hash = bb.utils.sha256_file(path) + def new_file(self, _id, name, path, *, purposes=[], hashfile=True): + if hashfile: + sha256_hash = bb.utils.sha256_file(path) - for f in self.by_sha256_hash.get(sha256_hash, []): - if not isinstance(f, oe.spdx30.software_File): - continue + for f in self.by_sha256_hash.get(sha256_hash, []): + if not isinstance(f, oe.spdx30.software_File): + continue - if purposes: - new_primary = purposes[0] - new_additional = [] + if purposes: + new_primary = purposes[0] + new_additional = [] - if f.software_primaryPurpose: - new_additional.append(f.software_primaryPurpose) - new_additional.extend(f.software_additionalPurpose) + if f.software_primaryPurpose: + new_additional.append(f.software_primaryPurpose) + new_additional.extend(f.software_additionalPurpose) - new_additional = sorted( - list(set(p for p in new_additional if p != new_primary)) - ) + new_additional = sorted( + list(set(p for p in new_additional if p != new_primary)) + ) - f.software_primaryPurpose = new_primary - f.software_additionalPurpose = new_additional + f.software_primaryPurpose = new_primary + f.software_additionalPurpose = new_additional - if f.name != name: - for e in f.extension: - if isinstance(e, OEFileNameAliasExtension): - e.aliases.append(name) - break - else: - f.extension.append(OEFileNameAliasExtension(aliases=[name])) + if f.name != name: + for e in f.extension: + if isinstance(e, OEFileNameAliasExtension): + e.aliases.append(name) + break + else: + f.extension.append(OEFileNameAliasExtension(aliases=[name])) - return f + return f spdx_file = oe.spdx30.software_File( _id=_id, @@ -661,12 +662,13 @@ class ObjectSet(oe.spdx30.SHACLObjectSet): spdx_file.software_primaryPurpose = purposes[0] spdx_file.software_additionalPurpose = purposes[1:] - spdx_file.verifiedUsing.append( - oe.spdx30.Hash( - algorithm=oe.spdx30.HashAlgorithm.sha256, - hashValue=sha256_hash, + if hashfile: + spdx_file.verifiedUsing.append( + oe.spdx30.Hash( + algorithm=oe.spdx30.HashAlgorithm.sha256, + hashValue=sha256_hash, + ) ) - ) return self.add(spdx_file) diff --git a/meta/lib/oe/spdx30_tasks.py b/meta/lib/oe/spdx30_tasks.py index fff1ca6bea..1c9346128c 100644 --- a/meta/lib/oe/spdx30_tasks.py +++ b/meta/lib/oe/spdx30_tasks.py @@ -568,44 +568,63 @@ def create_recipe_spdx(d): if include_vex != "none": patched_cves = oe.cve_check.get_patched_cves(d) for cve, patched_cve in patched_cves.items(): - decoded_status = { - "mapping": patched_cve["abbrev-status"], - "detail": patched_cve["status"], - "description": patched_cve.get("justification", None), - } + mapping = patched_cve["abbrev-status"] + detail = patched_cve["status"] + description = patched_cve.get("justification", None) + resources = patched_cve.get("resource", []) # If this CVE is fixed upstream, skip it unless all CVEs are # specified. - if ( - include_vex != "all" - and "detail" in decoded_status - and decoded_status["detail"] - in ( - "fixed-version", - "cpe-stable-backport", - ) + if include_vex != "all" and detail in ( + "fixed-version", + "cpe-stable-backport", ): bb.debug(1, "Skipping %s since it is already fixed upstream" % cve) continue spdx_cve = recipe_objset.new_cve_vuln(cve) - cve_by_status.setdefault(decoded_status["mapping"], {})[cve] = ( + cve_by_status.setdefault(mapping, {})[cve] = ( spdx_cve, - decoded_status["detail"], - decoded_status["description"], + detail, + description, + resources, ) all_cves = set() for status, cves in cve_by_status.items(): for cve, items in cves.items(): - spdx_cve, detail, description = items + spdx_cve, detail, description, resources = items spdx_cve_id = oe.sbom30.get_element_link_id(spdx_cve) all_cves.add(spdx_cve) if status == "Patched": - recipe_objset.new_vex_patched_relationship([spdx_cve_id], [recipe]) + spdx_vex = recipe_objset.new_vex_patched_relationship( + [spdx_cve_id], [recipe] + ) + patches = [] + for idx, filepath in enumerate(resources): + patches.append( + recipe_objset.new_file( + recipe_objset.new_spdxid( + "patch", str(idx), os.path.basename(filepath) + ), + os.path.basename(filepath), + filepath, + purposes=[oe.spdx30.software_SoftwarePurpose.patch], + hashfile=os.path.isfile(filepath), + ) + ) + + if patches: + recipe_objset.new_scoped_relationship( + spdx_vex, + oe.spdx30.RelationshipType.patchedBy, + oe.spdx30.LifecycleScopeType.build, + patches, + ) + elif status == "Unpatched": recipe_objset.new_vex_unpatched_relationship([spdx_cve_id], [recipe]) elif status == "Ignored": @@ -751,12 +770,14 @@ def create_spdx(d): # Collect all VEX statements from the recipe vex_statements = {} + vex_patches = {} for rel in recipe_objset.foreach_filter( oe.spdx30.Relationship, relationshipType=oe.spdx30.RelationshipType.hasAssociatedVulnerability, ): for cve in rel.to: vex_statements[cve] = [] + vex_patches[cve] = [] for cve in vex_statements.keys(): for rel in recipe_objset.foreach_filter( @@ -764,6 +785,13 @@ def create_spdx(d): from_=cve, ): vex_statements[cve].append(rel) + if rel.relationshipType == oe.spdx30.RelationshipType.fixedIn: + for patch_rel in recipe_objset.foreach_filter( + oe.spdx30.Relationship, + relationshipType=oe.spdx30.RelationshipType.patchedBy, + from_=rel, + ): + vex_patches[cve].extend(patch_rel.to) # Write out the package SPDX data now. It is not complete as we cannot # write the runtime data, so write it to a staging area and a later task @@ -889,7 +917,9 @@ def create_spdx(d): # Add concluded license relationship if manually set # Only add when license analysis has been explicitly performed - concluded_license_str = d.getVar("SPDX_CONCLUDED_LICENSE:%s" % package) or d.getVar("SPDX_CONCLUDED_LICENSE") + concluded_license_str = d.getVar( + "SPDX_CONCLUDED_LICENSE:%s" % package + ) or d.getVar("SPDX_CONCLUDED_LICENSE") if concluded_license_str: concluded_spdx_license = add_license_expression( d, build_objset, concluded_license_str, license_data @@ -915,9 +945,20 @@ def create_spdx(d): for cve, vexes in vex_statements.items(): for vex in vexes: if vex.relationshipType == oe.spdx30.RelationshipType.fixedIn: - pkg_objset.new_vex_patched_relationship( + spdx_vex = pkg_objset.new_vex_patched_relationship( [oe.sbom30.get_element_link_id(cve)], [spdx_package] ) + if vex_patches[cve]: + pkg_objset.new_scoped_relationship( + spdx_vex, + oe.spdx30.RelationshipType.patchedBy, + oe.spdx30.LifecycleScopeType.build, + [ + oe.sbom30.get_element_link_id(p) + for p in vex_patches[cve] + ], + ) + elif vex.relationshipType == oe.spdx30.RelationshipType.affects: pkg_objset.new_vex_unpatched_relationship( [oe.sbom30.get_element_link_id(cve)], [spdx_package] From patchwork Thu Feb 26 17:33:08 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joshua Watt X-Patchwork-Id: 82028 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 1FAA7FD8FE8 for ; Thu, 26 Feb 2026 17:39:42 +0000 (UTC) Received: from mail-ot1-f51.google.com (mail-ot1-f51.google.com [209.85.210.51]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.75807.1772127579255217582 for ; Thu, 26 Feb 2026 09:39:39 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20230601 header.b=Af+8PmYA; spf=pass (domain: gmail.com, ip: 209.85.210.51, mailfrom: jpewhacker@gmail.com) Received: by mail-ot1-f51.google.com with SMTP id 46e09a7af769-7d1890f5cafso441128a34.1 for ; Thu, 26 Feb 2026 09:39:39 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772127578; x=1772732378; darn=lists.openembedded.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=RgZWWYUETcp+KAK54Y1gHYnk1xGLeXOCGllecsNhDhw=; b=Af+8PmYA0k4fJhEPeX+gDNQGlICcvQu1cIRBgbAA4CqSDMhEogOiNqbtjMHi4KnfMm 0TNcRSEOzKRnSt+OgNwOWwk7T0B5SP/QrPL1qAds3f35Dqs8qBQ2deCu9IPT0CgAtOTz 3i6iEqhRM00dD2StKuxHGqXDKQfTLTODRRmBpGB5UHOvDiTHwjBXOp9P1kKPWNC/tqip GtjVvmEoQWtsNH559yT88xIFGIX+gP6wiDBVTWIhitrG+Nmou1ZTCHK+E2PPFUE6qE+H Db8vSett7GzFgEAN72M7ocN5lt982D5rLiZ02UGkV8CR8SmP4c0qNTw/ew7P5tgUzI8h 4WFA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772127578; x=1772732378; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=RgZWWYUETcp+KAK54Y1gHYnk1xGLeXOCGllecsNhDhw=; b=LVlTA/kFmxjeCun3OmrcBsAp9geJ4n1fogM8l/ul3E3+0pyLEruXhOBjyfYio7yseP BfSxRADW4tLLsF+JTKwZ1bk5y1gZsp/ZthyYklBi+/YJDysOdr832a1QT80onNZlu/DR 6nk1f14Xp0NVwENsXB5IenDqz7ylkRprZOv3YWWx8Go7ASTzAn6ls4nvlFudsfG2352Z j0kp+JRRXGb0Pp1w2LTe5Nj9bepizaUwg9Ll721T6clIeO37O1zOpRBmWo/h10IUCHiR h+sbcsvwxlcyd2wBCVjjYWGauxfVYxDDRoYbsRe+VXQQXED/VB6ejVsd2QjdXuOEEILQ so9g== X-Gm-Message-State: AOJu0YxocHNz8n8MXiIVQ9Vpfe7cTtHa7MhRVqhL60VOtBUxBdIUpix8 kB/t82uIX1IoM+9CMDnmXAbYdKOwg5dqXVMOz08cNODwWRht6tiXZGW2RaJjoQ== X-Gm-Gg: ATEYQzxSJanKannFUnf/Pfq5euma9O67T+V0h3p6uVT8ay8WgW7diauamUEBkkwl77F fNugXVQVLTSUhiE1KmQXvo4rrX/dY5a/PqN708zpFRsBDJEWnS5vhZzPcZ5ACfovBdSwa5gPmVR SbIOZclDBeO2Bzux9ikB7RTnT4mofbFLMUTaXftIexn7OWnuLs7vEsIU3gVvALFnEYor2zX0GwI ura2bvOztiDWLiaC+BdvzL8+mbXSf9+FHCdeEH8YbP0KzYdehBh1sAmVQQCIspp5Dzr6M8Jg/A+ gzNT53tQp8uMPkpAty3jc+vn8R3d6LB4swXxYnMMStFcGVQeqX3TQ1V8z6G/DrM0HTtJ44cyGZp Mfio/MDMNnsoU8Au/Fhkxzf6ovcPpqAtzT8foMzojIdxt9s0cBs8H/dITN171QBbCQMORqhbT2k H4jHUGJasI6nEUcgyKrbmT X-Received: by 2002:a05:6830:4707:b0:7cf:dc0c:8cfe with SMTP id 46e09a7af769-7d582a92228mr2840784a34.34.1772127578308; Thu, 26 Feb 2026 09:39:38 -0800 (PST) Received: from localhost.localdomain ([2601:282:4200:11c0::6492]) by smtp.gmail.com with ESMTPSA id 46e09a7af769-7d586653f81sm2173027a34.23.2026.02.26.09.39.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 26 Feb 2026 09:39:37 -0800 (PST) From: Joshua Watt X-Google-Original-From: Joshua Watt To: openembedded-core@lists.openembedded.org Cc: benjamin.robin@bootlin.com, ross.burton@arm.com, Joshua Watt Subject: [OE-core][PATCH v3 7/8] spdx: De-duplicate CreationInfo Date: Thu, 26 Feb 2026 10:33:08 -0700 Message-ID: <20260226173930.2847872-8-JPEWhacker@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260226173930.2847872-1-JPEWhacker@gmail.com> References: <20260224230234.679049-1-JPEWhacker@gmail.com> <20260226173930.2847872-1-JPEWhacker@gmail.com> MIME-Version: 1.0 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 ; Thu, 26 Feb 2026 17:39:42 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/232053 De-duplicates CreationInfo objects that are identical (except for ID) when writing out an SBoM. This significantly reduces the number of CreationInfo objects that end up in the final document. Signed-off-by: Joshua Watt --- meta/lib/oe/sbom30.py | 112 ++++++++++++++++++++++++++++++------------ meta/lib/oe/spdx30.py | 2 +- 2 files changed, 81 insertions(+), 33 deletions(-) diff --git a/meta/lib/oe/sbom30.py b/meta/lib/oe/sbom30.py index 21f084dc16..55a2863d2d 100644 --- a/meta/lib/oe/sbom30.py +++ b/meta/lib/oe/sbom30.py @@ -14,6 +14,7 @@ import uuid import os import oe.spdx_common from datetime import datetime, timezone +from contextlib import contextmanager OE_SPDX_BASE = "https://rdf.openembedded.org/spdx/3.0/" @@ -191,6 +192,25 @@ def to_list(l): return l +class Dedup(object): + def __init__(self, objset): + self.unique = set() + self.dedup = {} + self.objset = objset + + def find_duplicates(self, cmp, typ, **kwargs): + for o in self.objset.foreach_filter(typ, **kwargs): + for u in self.unique: + if cmp(u, o): + self.dedup[o] = u + break + else: + self.unique.add(o) + + def get(self, o): + return self.dedup.get(o, o) + + class ObjectSet(oe.spdx30.SHACLObjectSet): def __init__(self, d): super().__init__() @@ -895,6 +915,45 @@ class ObjectSet(oe.spdx30.SHACLObjectSet): self.missing_ids -= set(imports.keys()) return self.missing_ids + @contextmanager + def deduplicate(self): + d = Dedup(self) + + yield d + + visited = set() + + def visit(o, path): + if isinstance(o, oe.spdx30.SHACLObject): + if o in visited: + return False + visited.add(o) + + for k in o: + v = o[k] + if isinstance(v, oe.spdx30.SHACLObject): + o[k] = d.get(v) + + elif isinstance(o, oe.spdx30.ListProxy): + for idx, v in enumerate(o): + if isinstance(v, oe.spdx30.SHACLObject): + o[idx] = d.get(v) + + return True + + if d.dedup: + for o in self.objects: + o.walk(visit) + + for k, v in d.dedup.items(): + bb.debug( + 1, + f"Removing duplicate {k.__class__.__name__} {k._id or id(k)} -> {v._id or id(v)}", + ) + self.objects.discard(k) + + self.create_index() + def load_jsonld(d, path, required=False): deserializer = oe.spdx30.JSONLDDeserializer() @@ -1080,39 +1139,28 @@ def create_sbom(d, name, root_elements, add_objectsets=[]): # SBoM should be the only root element of the document objset.doc.rootElement = [sbom] - # De-duplicate licenses - unique = set() - dedup = {} - for lic in objset.foreach_type(oe.spdx30.simplelicensing_LicenseExpression): - for u in unique: - if ( - u.simplelicensing_licenseExpression - == lic.simplelicensing_licenseExpression - and u.simplelicensing_licenseListVersion - == lic.simplelicensing_licenseListVersion - ): - dedup[lic] = u - break - else: - unique.add(lic) - - if dedup: - for rel in objset.foreach_filter( - oe.spdx30.Relationship, - relationshipType=oe.spdx30.RelationshipType.hasDeclaredLicense, - ): - rel.to = [dedup.get(to, to) for to in rel.to] - - for rel in objset.foreach_filter( - oe.spdx30.Relationship, - relationshipType=oe.spdx30.RelationshipType.hasConcludedLicense, - ): - rel.to = [dedup.get(to, to) for to in rel.to] + def cmp_license_expression(a, b): + return ( + a.simplelicensing_licenseExpression == b.simplelicensing_licenseExpression + and a.simplelicensing_licenseListVersion + == b.simplelicensing_licenseListVersion + ) - for k, v in dedup.items(): - bb.debug(1, f"Removing duplicate License {k._id} -> {v._id}") - objset.objects.remove(k) + def cmp_creation_info(a, b): + data_a = {k: a[k] for k in a} + data_b = {k: b[k] for k in b} + data_a["@id"] = "" + data_b["@id"] = "" + return data_a == data_b + + with objset.deduplicate() as dedup: + # De-duplicate licenses + dedup.find_duplicates( + cmp_license_expression, + oe.spdx30.simplelicensing_LicenseExpression, + ) - objset.create_index() + # Deduplicate creation info + dedup.find_duplicates(cmp_creation_info, oe.spdx30.CreationInfo) return objset, sbom diff --git a/meta/lib/oe/spdx30.py b/meta/lib/oe/spdx30.py index cd97eebd18..1f58402ffc 100644 --- a/meta/lib/oe/spdx30.py +++ b/meta/lib/oe/spdx30.py @@ -701,7 +701,7 @@ class SHACLObject(object): self.__dict__["_obj_data"][iri] = prop.init() def __iter__(self): - return self._OBJ_PROPERTIES.keys() + return iter(self._OBJ_PROPERTIES.keys()) def walk(self, callback, path=None): """ From patchwork Thu Feb 26 17:33:09 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joshua Watt X-Patchwork-Id: 82025 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 0D84FFD8FDA for ; Thu, 26 Feb 2026 17:39:42 +0000 (UTC) Received: from mail-ot1-f42.google.com (mail-ot1-f42.google.com [209.85.210.42]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.75834.1772127579861345581 for ; Thu, 26 Feb 2026 09:39:39 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20230601 header.b=iSZTQ5Fm; spf=pass (domain: gmail.com, ip: 209.85.210.42, mailfrom: jpewhacker@gmail.com) Received: by mail-ot1-f42.google.com with SMTP id 46e09a7af769-7d4be94eeacso676472a34.2 for ; Thu, 26 Feb 2026 09:39:39 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772127579; x=1772732379; darn=lists.openembedded.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=6Tj4cGGvEEbE1QT7Q+4TdJLcoNUT6Vuz9fhKtg+CMko=; b=iSZTQ5FmesYyEcvxmRwH66IN22Vo0/jZaC6wU27PXv1yJqJGp0n7MQM57JXGN25V/L x/jcaEHP9f9T+tMgu6qZcc6+Z81+iW0QTGOm2QTvVsYmiL3MorsiGLlbewYvaqdLoZ3l 6QTz6USfaz3UzfzlHDq7tPz5b1uDgt0tZCyWuyrSuJcBTDgiaQs8B+E20hZMhtcpuk7K wAvj+VsYi0tIsIpsElnzTMkwIjAOg4UhwgB1d4L9/Ye3XJn2vBdtE3egYBLITTHnR0uK +T2KRYsorj3pAgHIe0oGiBd8qDXmidd43+cx2ZWnhVZbN+i0YzGAWaiN5GnWcEkjYFE7 0kvA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772127579; x=1772732379; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=6Tj4cGGvEEbE1QT7Q+4TdJLcoNUT6Vuz9fhKtg+CMko=; b=svIZ9N1nZDRNObj4VbdmTP3Q+znj4oAqS6tEGXRZWu8r2RybOpTLV8ocFa69V6ihiI rUseuszVFy6TEvFYo5LY8Mx+2MxgNfhzRZW83XvLIGYOSAvIsE2WVP+7kRZm6/zwHA+x ZYw4uqrr/46L95c9CpybPGapqIh5C/TapNVPcrY0d8hxz12nTfmjyhrTpmvrXiKTRPga mKGnFEjnHCDSE62N+/m2IqQTtRdo8gAjgylZTEgvCLIipnC0xU/NLbLMVlz6+hyOVhVa 52AHp0mkCyBTvbmXJgubPI8RxWU+85voLDliVK66K4gT3WR0RUta66b6p4xOOOtlEtF2 tXTA== X-Gm-Message-State: AOJu0YwPDHqvGaQAbLrOcyNmejgNNMDQzmaYUam4gRqzfuax99fOB4xK L5iP2KoSv0jX2fccuJskHz/v10JByD7lGvIOQIomjjwaMHBhid1UTNJ1Am0h/A== X-Gm-Gg: ATEYQzzE1Y3tPqE+JsKRn+5+vDxSr9GCuPdpnnXZHpaufvbt3wORwrZLNob6Wp9pshD 8MBWzGvNF1ee9LgeVdBEBWvA0JFE33L51xmzLxSdRRbHJ/gORiLT4QpvrEcFAdlhVwPsGP4cW+0 I073VasbogZoC1VLAXApNwjQUWs50t+Rkv0y2pll9MNixpAYLDpQcnndrIkFuaHZs/v8KurR6h9 YyFhx2J8VKsoxS5H6js1+oyvodV+xpvVbWK+DEEUMO9QBbROn1dz2FILOIRq5sDcMTJ/KeeCmx9 D3srSNwTz4UBPkHe2KrW59RFtu7fhblqSdbpA5XofiCB2XR9NGUQ0sON9pzb6PoysBx9w8NIECm Ug8LSBGpnM8NC/NV5zUcyEYUNASg5Q4LhCqlSdBwRBw8r7ofI1j2DoRQOVVNF0QcbSBjghYK79P Rpkuz987ool3TSOojFEtv2QUjc7zUJsrA= X-Received: by 2002:a05:6830:6c15:b0:7cf:d8c1:8e19 with SMTP id 46e09a7af769-7d586f561d7mr1436198a34.25.1772127578980; Thu, 26 Feb 2026 09:39:38 -0800 (PST) Received: from localhost.localdomain ([2601:282:4200:11c0::6492]) by smtp.gmail.com with ESMTPSA id 46e09a7af769-7d586653f81sm2173027a34.23.2026.02.26.09.39.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 26 Feb 2026 09:39:38 -0800 (PST) From: Joshua Watt X-Google-Original-From: Joshua Watt To: openembedded-core@lists.openembedded.org Cc: benjamin.robin@bootlin.com, ross.burton@arm.com, Joshua Watt Subject: [OE-core][PATCH v3 8/8] spdx_common: Check for dependent task in task flags Date: Thu, 26 Feb 2026 10:33:09 -0700 Message-ID: <20260226173930.2847872-9-JPEWhacker@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260226173930.2847872-1-JPEWhacker@gmail.com> References: <20260224230234.679049-1-JPEWhacker@gmail.com> <20260226173930.2847872-1-JPEWhacker@gmail.com> MIME-Version: 1.0 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 ; Thu, 26 Feb 2026 17:39:42 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/232054 Checks that the task being used to detect dependencies is present in at least one dependency task flag of the current task. This helps prevent errors where the wrong task is specified and never found. Signed-off-by: Joshua Watt --- meta/lib/oe/spdx_common.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/meta/lib/oe/spdx_common.py b/meta/lib/oe/spdx_common.py index 72c24180d5..3aaf2a9c8b 100644 --- a/meta/lib/oe/spdx_common.py +++ b/meta/lib/oe/spdx_common.py @@ -96,6 +96,17 @@ def collect_direct_deps(d, dep_task): taskdepdata = d.getVar("BB_TASKDEPDATA", False) + # Check that the task is listed one of the task dependency flags of the + # current task + depflags = ( + set((d.getVarFlag(current_task, "deptask") or "").split()) + | set((d.getVarFlag(current_task, "rdeptask") or "").split()) + | set((d.getVarFlag(current_task, "recrdeptask") or "").split()) + ) + + if not dep_task in depflags: + bb.fatal(f"Task {dep_task} was not found in any dependency flag of {pn}:{current_task}") + for this_dep in taskdepdata.values(): if this_dep[0] == pn and this_dep[1] == current_task: break