From patchwork Tue Mar 3 00:43:48 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joshua Watt X-Patchwork-Id: 82303 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 E09D2EB3655 for ; Tue, 3 Mar 2026 00:46:03 +0000 (UTC) Received: from mail-oi1-f178.google.com (mail-oi1-f178.google.com [209.85.167.178]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.7683.1772498755092041875 for ; Mon, 02 Mar 2026 16:45:55 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20230601 header.b=BLjE2ZtL; spf=pass (domain: gmail.com, ip: 209.85.167.178, mailfrom: jpewhacker@gmail.com) Received: by mail-oi1-f178.google.com with SMTP id 5614622812f47-463a0e14b4cso2194774b6e.1 for ; Mon, 02 Mar 2026 16:45:55 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772498754; x=1773103554; 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=BLjE2ZtLhcqry0LtyQ3FtwBmXjYAb4pzTKM/2t5k3894knMCpzOWiu53a2yhAAhV/B qALGC8L2nipIbOCWfGXmje9+37JK8/ySLtCTt3N1b7lBu1DghoTfehAu9XHzCKc3C8GZ vCuYKYm3acTSupK5cvDrT7HllVhWhGDygi9T/b4sAUMJcgJWu0/9Y5rMzaYkgrtlzWO8 35sbc5OjunG/500kKmWCsgiS7KWqUll/NoMY4pkTb/qmw/AYJnhiRYvJEFXWftzG3kqN nbR+d0oU0lWRjTHl6Oo7OLOAuq0v/2NGLEPwoUGnADxtDEEpVVP94ZN+c30YJ7+M/qIB Y+tQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772498754; x=1773103554; 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=CbZZQDIbJDyNqJlv/4+DqfBk4Xq+M8L1GrOqNbt/i9SrhQWMKgNQ5B2E5+jEXvXt7F ngaTSjg3ePUhH0L5ov+lg/YrKtYZmmOLLlWElMf1OVIOULxGHpabIdaPNRDWeRt1P1R0 p3vRTQX0B1lo44c+S4P+i/LH92Bv3RSOOtQFWUAxv13E2/MgCAPviTkpe/0iJWg+5Fw9 vgmNsBh2HzkxCw1cT7MqnM9MUmRq+jcZ3x4uAoKfCwnT7vR2AEj9SeeJfDSC11myaU2C Agp4CaNBD0WiiMZeVw5A+3eKo/ubUlhv+jAgKnjYr7y42DTBLG1e/wADfiTpRga7kFGN iIVw== X-Gm-Message-State: AOJu0YwuqfQWdFoq1qvD9KvwJX56Y9B3FZbR/33eA6PsnIEcdNQ9LTFi Q4SlavOaqzvriOvArDOHQspHVjN4TiUwF77wQef/wLLwPrngRUQ60p7s14WIXA== X-Gm-Gg: ATEYQzx7pncxMp7GxJMj0R3BjIDWgaxn+xeauP1G7PgSpusl1s3u1bFi3i4CNCbvIMq CUICn/R2zSSTi5elPWVqYESwWQK8aMtZyBtWkSAKxbDH9LpkWD4/0hirO6hwGRT8Nzk5QjC1jQ7 DlP/SXe1OuSkYh+X/ej/iEUXzN6czhJhJax8ncoDr26TCT+Z5iMZaS/GeGzEUA7oHrGWuYDWTlD 8lYjS6rb76DYsBrGVAYZspj5R1O8MoRRUL66G0PeF0ZO7U5oNsYlBMe1Y5SAzMRLV9MEgf1BY+S 8xZxbH42ANAAVe/8YJBWVmrKQAPGP5AJkQNzQv1pZ9DxUjOB3QTqMjL08UQt8it93GDfL3CRzWL rAPGSsDyZcVveWS5stohN5fDVWxj/MtaSYfYL7uo64tmbknPFELReLW/Fzm8C885XvF+iJ1PqSp 5fsctCzG1HV1Rh+QZm/jr9vk8sbkAjxgU= X-Received: by 2002:a05:6808:1492:b0:45e:f91f:9730 with SMTP id 5614622812f47-464befb9a1amr8522744b6e.51.1772498754164; Mon, 02 Mar 2026 16:45:54 -0800 (PST) Received: from localhost.localdomain ([2601:282:4200:11c0::ba6c]) by smtp.gmail.com with ESMTPSA id 5614622812f47-464bb59b66fsm8637446b6e.10.2026.03.02.16.45.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 02 Mar 2026 16:45:53 -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 v4 1/9] llvm-project-source: Use allarch.bbclass Date: Mon, 2 Mar 2026 17:43:48 -0700 Message-ID: <20260303004550.650726-2-JPEWhacker@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260303004550.650726-1-JPEWhacker@gmail.com> References: <20260226173930.2847872-1-JPEWhacker@gmail.com> <20260303004550.650726-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 ; Tue, 03 Mar 2026 00:46:03 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/232224 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 Tue Mar 3 00:43:49 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joshua Watt X-Patchwork-Id: 82305 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 EB135EB3657 for ; Tue, 3 Mar 2026 00:46:03 +0000 (UTC) Received: from mail-oi1-f182.google.com (mail-oi1-f182.google.com [209.85.167.182]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.7686.1772498755754064853 for ; Mon, 02 Mar 2026 16:45:55 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20230601 header.b=bCSDwbZp; spf=pass (domain: gmail.com, ip: 209.85.167.182, mailfrom: jpewhacker@gmail.com) Received: by mail-oi1-f182.google.com with SMTP id 5614622812f47-46391f4c1f9so3512030b6e.0 for ; Mon, 02 Mar 2026 16:45:55 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772498755; x=1773103555; 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=bCSDwbZp0g04oWEOSbv8o5YnXG5tbaUC/1Gvuj7qXdxQSTvws1EdItLSX9C1Ga2pRO mHgkiejAxRGjNnk8WvmwcJJPjb36EcblBIDaAIBqGd7C1k7oQkENNYW8nl42tabtyGMF 8uXEOF63dNAV5SkfpC912E5Rug/PGtsWgBX6Ck20osKSq8BD43eRiXz8IKpX5u4NuPcn aSVPQqgwEKx8LWygMAJnArZz3KxHyl/HphboZHOVn5W+aCUa+VyKb6SWiAo0DzDYj/H+ t1AOHk1tQeh2pfmymu8cXPtC+I0Dp3xvVv3yUBip4LyDjpb9rmUqhIyJ4dPfPjGBSwiY AFFg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772498755; x=1773103555; 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=bZv3Tu2lEYEdD+9FTFgEVmRioEEPqYl6r8YpfLevJ4xZuG+R1npRRO6XPHniQaJctV z+sf9niELxdLCpZoGm2jHDgWwtaZ1GrUXJ1xReZMjL1o5yEbCzUIRE/jvP/z2rcCqrLt 1AEZ8j1CwsfWpmsG5zOYfkoZInNLBZr5Kdibikk7GDonVA6EKFklVopTZadYkGU65Iat 8xMEY/67v0d2yRQbWov1/JIk/vJdnlEi2slDvftrBh5d5tl8b3GQte6PJNABMLJ+dkYu SSpDK8biIV68cIXE2GuJO3DYkkfBkBnbXkEhSetikFrKOSI10d+XwVnFjWFrsiEK6Vu+ GY5Q== X-Gm-Message-State: AOJu0YykOJ6XwRZuFtWlHptUgJXmlidaAZ/fnzz6I0O86rxmJNXvV3yY xHtZUtq2Je2m8uMVN4yd4z7xPQygwbwet6FspbbL74pt9taebH3mYVEezTC1Iw== X-Gm-Gg: ATEYQzw9I0jjZUmmvSfZjbzmQSP5YtxaIiqKYgphyetZocfibUTL0GpxcnUY1vwhxF6 JW2G3UjWB7eA6vor2EIQhJez5EJpSXKlUCYOGnpoYucPwGU+4we8m1KbAOpqMaYDBKNc4ipIs6c e5zhWZcUfe7yJ8RlbziLjjEnBUWLFNONqG8kz8at/KgyYALcC3kB0G0k0yibSeaVA1FspnBUflH ggtpfVXfCa5KpQKzvFMMORzOgGLyRTT3q34VjJCiCa8wtEEIgy03/WnAzEQTzayiCT+VcTQtHlb F0wRITr8Dio4VVHDZpHNwk02hdRd77aG+9x/3n3Cxf7mYBlvlhnXP43C7Z7yRE8aXmlFZdFYelp j9BAjNYVgEzCJNDH5kpcAIShzYoi9MTlfHagH1Tg9y+wvvBnW7wmVQfgDCNggkTWiD00jMhBbc6 TbKZwyVabbKn2HZSun0IIyRNK7bwVY9s0= X-Received: by 2002:a05:6808:f05:b0:44f:775b:729f with SMTP id 5614622812f47-464be9e3931mr8274995b6e.28.1772498754831; Mon, 02 Mar 2026 16:45:54 -0800 (PST) Received: from localhost.localdomain ([2601:282:4200:11c0::ba6c]) by smtp.gmail.com with ESMTPSA id 5614622812f47-464bb59b66fsm8637446b6e.10.2026.03.02.16.45.54 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 02 Mar 2026 16:45:54 -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 v4 2/9] gcc-source: Use allarch.bbclass Date: Mon, 2 Mar 2026 17:43:49 -0700 Message-ID: <20260303004550.650726-3-JPEWhacker@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260303004550.650726-1-JPEWhacker@gmail.com> References: <20260226173930.2847872-1-JPEWhacker@gmail.com> <20260303004550.650726-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 ; Tue, 03 Mar 2026 00:46:03 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/232225 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 Tue Mar 3 00:43:50 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joshua Watt X-Patchwork-Id: 82298 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 9FB00EB3649 for ; Tue, 3 Mar 2026 00:46:03 +0000 (UTC) Received: from mail-oi1-f175.google.com (mail-oi1-f175.google.com [209.85.167.175]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.7684.1772498757509217838 for ; Mon, 02 Mar 2026 16:45:57 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20230601 header.b=fsWxLxZY; spf=pass (domain: gmail.com, ip: 209.85.167.175, mailfrom: jpewhacker@gmail.com) Received: by mail-oi1-f175.google.com with SMTP id 5614622812f47-4648447c899so3464528b6e.0 for ; Mon, 02 Mar 2026 16:45:57 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772498756; x=1773103556; 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=733+vfQTssEm3IWSGM7mOkOZQibcuFrzuyMXEjUYiFw=; b=fsWxLxZYOJctcNy1cCnF9LuTn+5Rz3s83VfZ7+kXwTs8kGPIz+HyhIfkxm0MjK1byq OP6WUiCO3nzydon8eoeBhunSFtFUZG2I/nH8drvKSUK4F+RroTNLRXvo9d4OzbwqlkEW i5j2PH5NqKLHuBx/MaogC9w9FwtcbM9ZJLzLWFYJZM3nhFzeSgkO8WM7wvN8YSp2WtuE fU5d9YoC6Cpv3xG7Xq+R55GvaAapIbOgoF9y1Kt5qzx9qg2yjp9+0E+HWXzFUfqpczJz dHayNn3Lq761vDEEnsKIygrkiVm3pMC45spH3oVlKoYJNibT7fhBO1iQ6Vy0jX/dtOWx /iCQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772498756; x=1773103556; 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=733+vfQTssEm3IWSGM7mOkOZQibcuFrzuyMXEjUYiFw=; b=R295z89Td8CRmU5szAF6BW9khrJ5O4Q4h2512q2uPiU0asHP3xYHapXsxGCnaBmSr6 otaOrfUrZM7yTrmZEov227M1DdeSWOR0/ScZg2xCCyH7wupXoaRX4V7T5fATj9q93V72 wEazAGxulkneEYViskydDP67PyDpGtWkfeKJy7xYdPAgAbUxhq3Yzc7n2NLSDiFpVCQW gaXHT1u8kLu24m1essZORqP/qqC6mVpZulSdgT6yXE4ElzbFA5LnieGhxcEgrT++Eqe1 rkYab6mSwt/qsGSr9vDLAfHD0gxrrf+jiALElovQ5R5FNSEKg5khAF8KPDzjQ/bFisrf q6hw== X-Gm-Message-State: AOJu0Yz89yg+9KTIGNgwAKO/RBZ90jefwJ0pywJtiLssz8/5xZmr+HUa XU9rmg2/AeDQVdvKx3goORCf2vX9jPKnwHtKXj9oBS50bCKo9hQMS0IVzCXHNQ== X-Gm-Gg: ATEYQzzJ4hKYnhH3XNMxRrKGc2klbkdnyfrYx1IaQ14RdwsjeR3GCld4jtOJysdv+fc rUolholYGbp7Ph9GYUX59q6GN7LK6Vr28DfniV6wcF3I7eU9qOmvDGTSl4lSnVQpawagHkXtkjp bxvhp38qoARLwUucdL6l2KMJ8iWVpesAlGdSvvlalei6ywBOHndFOggeQCWPoLTIRtCKdG+vFE9 T4+Wzz5SvoPH56A8DQQqkO2lNjgtD110arhpxXjP3z5ry7GTBYN/IftgxLrXGnuV6E7DjkNMvV2 esy3qXisLhVs1bRdjtej5fCXMTJ4jmjF493HDTwB0kfjyTT/LB2+vThy5GrShei7y/syEVpoPbH WR19pKWP2gl2py+znduoP2900jtxmaQHedJd8BDf7LHJD4vtYRrJfZ3HmDgEv22V72nNR/IxWT0 yikfWlhZH11yKGA2wxbO4o X-Received: by 2002:a05:6808:e8b:b0:462:d1c1:6de2 with SMTP id 5614622812f47-464be9e125bmr7527311b6e.26.1772498756214; Mon, 02 Mar 2026 16:45:56 -0800 (PST) Received: from localhost.localdomain ([2601:282:4200:11c0::ba6c]) by smtp.gmail.com with ESMTPSA id 5614622812f47-464bb59b66fsm8637446b6e.10.2026.03.02.16.45.54 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 02 Mar 2026 16:45:55 -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 v4 3/9] spdx3: Add recipe SPDX data Date: Mon, 2 Mar 2026 17:43:50 -0700 Message-ID: <20260303004550.650726-4-JPEWhacker@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260303004550.650726-1-JPEWhacker@gmail.com> References: <20260226173930.2847872-1-JPEWhacker@gmail.com> <20260303004550.650726-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 ; Tue, 03 Mar 2026 00:46:03 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/232226 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 | 51 ++- meta/classes/spdx-common.bbclass | 21 +- meta/lib/oe/spdx30_tasks.py | 402 ++++++++++++------ meta/lib/oeqa/selftest/cases/spdx.py | 19 +- 10 files changed, 354 insertions(+), 166 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 22568d6e9c..6621a17f85 100644 --- a/meta/classes-recipe/kernel.bbclass +++ b/meta/classes-recipe/kernel.bbclass @@ -895,7 +895,7 @@ do_create_spdx:append() { except Exception as e: bb.error(f"Failed to parse kernel config file: {e}") - path = oe.sbom30.jsonld_arch_path(d, pkg_arch, "recipes", f"recipe-{pn}", deploydir=deploydir) + path = oe.sbom30.jsonld_arch_path(d, pkg_arch, "builds", f"build-{pn}", deploydir=deploydir) build_objset = oe.sbom30.load_jsonld(d, path, required=True) build = build_objset.find_root(oe.spdx30.build_Build) if not build: 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..3288cdf75a 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_patch 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..672ca27cd0 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,19 @@ 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_patch \ + do_create_recipe_spdx \ do_collect_spdx_deps \ do_deploy_source_date_epoch \ do_populate_sysroot do_package do_packagedata \ @@ -181,18 +200,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 +231,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..3c239a718b 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" @@ -67,12 +68,6 @@ def create_spdx_source_deps(d): deps = [] if d.getVar("SPDX_INCLUDE_SOURCES") == "1": pn = d.getVar('PN') - # do_unpack is a hack for now; we only need it to get the - # dependencies do_unpack already has so we can extract the source - # ourselves - if oe.spdx_common.has_task(d, "do_unpack"): - deps.append("%s:do_unpack" % pn) - if oe.spdx_common.is_work_shared_spdx(d) and \ oe.spdx_common.process_sources(d): # For kernel source code @@ -84,8 +79,6 @@ def create_spdx_source_deps(d): # For gcc-source-${PV} source code if oe.spdx_common.has_task(d, "do_preconfigure"): deps.append("%s:do_preconfigure" % pn) - elif oe.spdx_common.has_task(d, "do_patch"): - deps.append("%s:do_patch" % pn) # For gcc-cross-x86_64 source code elif oe.spdx_common.has_task(d, "do_configure"): deps.append("%s:do_configure" % pn) @@ -97,8 +90,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 +99,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], diff --git a/meta/lib/oeqa/selftest/cases/spdx.py b/meta/lib/oeqa/selftest/cases/spdx.py index 5830d7c087..759ca86b73 100644 --- a/meta/lib/oeqa/selftest/cases/spdx.py +++ b/meta/lib/oeqa/selftest/cases/spdx.py @@ -141,6 +141,11 @@ class SPDX30Check(SPDX3CheckBase, OESelftestTestCase): SPDX_CLASS = "create-spdx-3.0" def test_base_files(self): + self.check_recipe_spdx( + "base-files", + "{DEPLOY_DIR_SPDX}/{MACHINE_ARCH}/static/static-base-files.spdx.json", + task="create_recipe_spdx", + ) self.check_recipe_spdx( "base-files", "{DEPLOY_DIR_SPDX}/{MACHINE_ARCH}/packages/package-base-files.spdx.json", @@ -149,7 +154,7 @@ class SPDX30Check(SPDX3CheckBase, OESelftestTestCase): def test_gcc_include_source(self): objset = self.check_recipe_spdx( "gcc", - "{DEPLOY_DIR_SPDX}/{SSTATE_PKGARCH}/recipes/recipe-gcc.spdx.json", + "{DEPLOY_DIR_SPDX}/{SSTATE_PKGARCH}/builds/build-gcc.spdx.json", extraconf="""\ SPDX_INCLUDE_SOURCES = "1" """, @@ -162,12 +167,12 @@ class SPDX30Check(SPDX3CheckBase, OESelftestTestCase): if software_file.name == filename: found = True self.logger.info( - f"The spdxId of {filename} in recipe-gcc.spdx.json is {software_file.spdxId}" + f"The spdxId of {filename} in build-gcc.spdx.json is {software_file.spdxId}" ) break self.assertTrue( - found, f"Not found source file {filename} in recipe-gcc.spdx.json\n" + found, f"Not found source file {filename} in build-gcc.spdx.json\n" ) def test_core_image_minimal(self): @@ -305,7 +310,7 @@ class SPDX30Check(SPDX3CheckBase, OESelftestTestCase): # This will fail with NameError if new_annotation() is called incorrectly objset = self.check_recipe_spdx( "base-files", - "{DEPLOY_DIR_SPDX}/{MACHINE_ARCH}/recipes/recipe-base-files.spdx.json", + "{DEPLOY_DIR_SPDX}/{MACHINE_ARCH}/builds/build-base-files.spdx.json", extraconf=textwrap.dedent( f"""\ ANNOTATION1 = "{ANNOTATION_VAR1}" @@ -360,8 +365,8 @@ class SPDX30Check(SPDX3CheckBase, OESelftestTestCase): def test_kernel_config_spdx(self): kernel_recipe = get_bb_var("PREFERRED_PROVIDER_virtual/kernel") - spdx_file = f"recipe-{kernel_recipe}.spdx.json" - spdx_path = f"{{DEPLOY_DIR_SPDX}}/{{SSTATE_PKGARCH}}/recipes/{spdx_file}" + spdx_file = f"build-{kernel_recipe}.spdx.json" + spdx_path = f"{{DEPLOY_DIR_SPDX}}/{{SSTATE_PKGARCH}}/builds/{spdx_file}" # Make sure kernel is configured first bitbake(f"-c configure {kernel_recipe}") @@ -392,7 +397,7 @@ class SPDX30Check(SPDX3CheckBase, OESelftestTestCase): def test_packageconfig_spdx(self): objset = self.check_recipe_spdx( "tar", - "{DEPLOY_DIR_SPDX}/{SSTATE_PKGARCH}/recipes/recipe-tar.spdx.json", + "{DEPLOY_DIR_SPDX}/{SSTATE_PKGARCH}/builds/build-tar.spdx.json", extraconf="""\ SPDX_INCLUDE_PACKAGECONFIG = "1" """, From patchwork Tue Mar 3 00:43:51 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joshua Watt X-Patchwork-Id: 82297 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 9B5F5EB3647 for ; Tue, 3 Mar 2026 00:46:03 +0000 (UTC) Received: from mail-oi1-f177.google.com (mail-oi1-f177.google.com [209.85.167.177]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.7688.1772498758133687117 for ; Mon, 02 Mar 2026 16:45:58 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20230601 header.b=JlGwoM8v; spf=pass (domain: gmail.com, ip: 209.85.167.177, mailfrom: jpewhacker@gmail.com) Received: by mail-oi1-f177.google.com with SMTP id 5614622812f47-4638a18efc2so3795492b6e.3 for ; Mon, 02 Mar 2026 16:45:58 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772498757; x=1773103557; 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=0AODhThGJ5yZROpI3OOikCCsiX15Pqojv0hH5uqF+es=; b=JlGwoM8vC8hF9o3eAf8oCsaJpiOowwe5Th+lwpvxIbUzFHMJu33pBOqRumCmgwpETo /8X71/VzxrbGUH+MGxLrucxpb5wMvRZ4PSbOFWZSrtku9vI/Rn52TcQPNRCEvotQT0RC vBnZ5o64W2Q8fYL5YkShHVBQxO3GA4DD+PD8t33en8OFsmNi9j3xaFMk0j1LHCjNaRB7 +8NtVuWlVWV/oHmcedCgF4Eb3thtDNrYHv/pzr6rRavc+q9s2Zd9jyUIIujtDbl7Jeio 7uBCHOdq8d6fpnIjn0wGE5GVjX25axgEXbVcsYEDRHT+1oZE+fXkd+q8aV/1nyKNcZeR EjvA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772498757; x=1773103557; 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=0AODhThGJ5yZROpI3OOikCCsiX15Pqojv0hH5uqF+es=; b=HF+CMHxAftCnVFNZdASuqAKJ1ZIUN1sf5n8Xsz1O9T4Et3RZlYPgqrZp0DtCiB4F1j j/ms2mYIqqozYpys0rRcJUN+KLyQZqmIlCNBD/BHA83Onj6WrsjbBuGXzRGCYrT419hQ y1URYMwt4Gj4NwzpIm+8oKACw63CQ2I0+GHDySuMEaQJHirvreqtWSfAy2MYYdS/E0KF fDiFKcuvjW0Qxz3oIibVMjLuK9c+KjG0RZwKzsXpzFHAT4kopq8xm3um0CHg1bEiTUL7 TogN2Cmfymt4mphZvTe845XIKajoUTnIwn8kI2xA7bWRHW0ROVgLQm2F+l6fGTsiv3Nj buYw== X-Gm-Message-State: AOJu0YxqDeQ3Ru0b9//rS3umqd3bQrj9VZlUVYmRhFcV72K/WYRUjHly cXkXbWiXZHSoceAQ+ummGxD0b5kvYARh7qxbdBxGO8HLQelJNALz4siw/CPszQ== X-Gm-Gg: ATEYQzzO5LyGG5qmMbbd1X2JaqSxmz2bPwggjheGobcJNvgCMleoULORo1nrhz5lh7j YDmpZ/TuwFrBhCagmHxgtYtbiPHNlWQNgNmcNBkJgOYlutii0+fMyDBqU+ftpOqJz0pFNrW2fuv HdW0tJzrbtatjRVRu2B+h3/xvT0x5bksmgI8m9ct9PnB9X3H51RB2d/zK2982X+9KuTNmT5zM9x gNC/2Nnt8jXku+mIb1k4U4h57m8Z1I45lma4wwFdO3GvIrB6jsJ9GeTxtP6qSR1wgWc9sDz1MHF QUK7dthR8QQVGsQzobdlBnNenbV/svZUr9GG6KtWYEJWTkF+Ck39d4nIqdA8vwg6nZprOZcwauu 4cN3rA40HV0RaAyY9+0pK8wlj0xE0ZFCDzDXnUF8WHjufI2sydq3DCCgvaA/WLWShQ+UfnKK3Ua SDV+5S5pyQFP3cPSJmOdCc5Tk/GwG2fjg= X-Received: by 2002:a05:6808:e8b:b0:462:d1c1:6de2 with SMTP id 5614622812f47-464be9e125bmr7527336b6e.26.1772498757251; Mon, 02 Mar 2026 16:45:57 -0800 (PST) Received: from localhost.localdomain ([2601:282:4200:11c0::ba6c]) by smtp.gmail.com with ESMTPSA id 5614622812f47-464bb59b66fsm8637446b6e.10.2026.03.02.16.45.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 02 Mar 2026 16:45:56 -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 v4 4/9] spdx3: Add recipe SBoM task Date: Mon, 2 Mar 2026 17:43:51 -0700 Message-ID: <20260303004550.650726-5-JPEWhacker@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260303004550.650726-1-JPEWhacker@gmail.com> References: <20260226173930.2847872-1-JPEWhacker@gmail.com> <20260303004550.650726-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 ; Tue, 03 Mar 2026 00:46:03 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/232227 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 ``` Signed-off-by: Joshua Watt --- meta/classes/create-spdx-3.0.bbclass | 32 +++++++++++++++++++ meta/classes/spdx-common.bbclass | 1 + meta/conf/distro/include/maintainers.inc | 1 + meta/lib/oe/spdx30_tasks.py | 10 ++++++ meta/lib/oeqa/selftest/cases/spdx.py | 10 ++++++ .../meta/meta-world-recipe-sbom.bb | 29 +++++++++++++++++ 6 files changed, 83 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 672ca27cd0..c3ea95b8bc 100644 --- a/meta/classes/create-spdx-3.0.bbclass +++ b/meta/classes/create-spdx-3.0.bbclass @@ -142,6 +142,10 @@ SPDX_PACKAGE_URLS[doc] = "A space separated list of Package URLs (purls) for \ Override this variable to replace the default, otherwise append or prepend \ to add additional purls." +SPDX_RECIPE_SBOM_NAME ?= "${PN}-recipe-sbom" +SPDX_RECIPE_SBOM_NAME[doc] = "The name of output recipe SBoM when using \ + create_recipe_sbom" + IMAGE_CLASSES:append = " create-spdx-image-3.0" SDK_CLASSES += "create-spdx-sdk-3.0" @@ -240,6 +244,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 3c239a718b..abf2332bee 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/conf/distro/include/maintainers.inc b/meta/conf/distro/include/maintainers.inc index b5ab35d92a..5bea863798 100644 --- a/meta/conf/distro/include/maintainers.inc +++ b/meta/conf/distro/include/maintainers.inc @@ -532,6 +532,7 @@ RECIPE_MAINTAINER:pn-meta-go-toolchain = "Richard Purdie X-Patchwork-Id: 82299 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 B1BD3EB364A for ; Tue, 3 Mar 2026 00:46:03 +0000 (UTC) Received: from mail-oi1-f181.google.com (mail-oi1-f181.google.com [209.85.167.181]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.7690.1772498758917666909 for ; Mon, 02 Mar 2026 16:45:59 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20230601 header.b=GxXeN2zh; spf=pass (domain: gmail.com, ip: 209.85.167.181, mailfrom: jpewhacker@gmail.com) Received: by mail-oi1-f181.google.com with SMTP id 5614622812f47-463a94f8475so4161298b6e.0 for ; Mon, 02 Mar 2026 16:45:58 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772498758; x=1773103558; 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=+wYSZS61IRGHmLD9OmiNc9hgO2b0xZj5mQwHOFCwfH8=; b=GxXeN2zhYUanMH4PMpgqfjmznqJ4WXNlbawysJk8jSh90Ra1PkrJTpAl8o6IEhRqB6 /L3vhESiwHq9NOhqCYPq/3nnouUCnuNu2aZed2QsD4u2xrl15VN4pUkuP+PeQ4mTG1jB gxj6ywNqfDC4T/cWnDg+4VdY5WFTTfupZjzw2ZKU4WDtTt42VXWFRgA0WRa/bTxefVRN cKxsI7hZqdze9zDSjxpq8sl1wncQnUntXLMcRdidYJIe4JMGctXi2xVYylb3V2pFK437 qzq7xT0uFhPjpBXZRiZhsMc441zxWoABT45OzK2br/apQtc2CaHWfxuE5c/pTEoJLvb6 qKxg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772498758; x=1773103558; 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=+wYSZS61IRGHmLD9OmiNc9hgO2b0xZj5mQwHOFCwfH8=; b=CRdgFoJNwbI2Var6CPqZRwPR/PnmnJAkw1EviRhOltwRscoTQNDUyPafWSVp5ppL9I /Si7doEwwDI9fdRVnHq2wN24+DRbP/k6kttQlirrrygjoIoFK1JwHT8NLlNqjCHwz4Zm Iq8ldVAgJq8QrDKXZY4zHYb1Xc1uDdWlZmlGZmkO09Wf06ZiubVKkiasK72cbd5apbx5 aQKpChEggQRFVSKnYCBOF2guSbUywfdfCljk6ZYteRhX822M4TOj8CHNS7rJdwp4C+Lg eY97m3Gxysqpyf4uQx1eI9x/TCZ22sgka1bCQHQHxDroTa4DrFlzK0wFBg2Zdlhn+1v2 EM7A== X-Gm-Message-State: AOJu0YyRTcZ8kNnpI3MNCPvDgWr8cfey7yCPBKv9pKbsm8e/WO0sr+Mj MZo25Y8kXh80WBYntaDxaFDg93kQLcBLmk+oFjQhkmnAcHq7al5TIVdjwZPF2g== X-Gm-Gg: ATEYQzwhLcsrUFD3x1+smELkLYBlkzeFpzKqdbNWEBH9qnFYbt49RYek5O4GoZ6hOzB KERP9Ft/QPqD44QjWUeY9/otypIS60R/PU8aSlCiToLZgL9ikQqFQPmaPkmOH2of27bkloWxYUC 15+ETdj1JgNS05D1gE5sloVIVAiqqQo1BQisgYegDjPx89XJ9wyohuB7wSkuEq+S/AHuzelqIuT QDx+9g3SfPMdUOFGvL7yYnCPcfBMefHJyvtOw5at/keMwifRiW9a8a9mnvEIfqUmECuI1VhFSMO k+uSjgxXt+tBMa1R//q/FYXLHQVpwiYRQHPFp0UFe6lj0UAEl6RdiqewHln/eXrxYkbzfBa0Z7J 5S/AJMmP4vQbVoA77XatPkdctmN+M6MftvRzbO9MMhOpTbZLt/ND1MYzLSUj6aQfz6AxsZnWss6 VAHkRbDLmHpOqz2S2SaDfv X-Received: by 2002:a05:6808:1388:b0:45f:7ac:12d9 with SMTP id 5614622812f47-4650c626decmr239628b6e.6.1772498758030; Mon, 02 Mar 2026 16:45:58 -0800 (PST) Received: from localhost.localdomain ([2601:282:4200:11c0::ba6c]) by smtp.gmail.com with ESMTPSA id 5614622812f47-464bb59b66fsm8637446b6e.10.2026.03.02.16.45.57 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 02 Mar 2026 16:45:57 -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 v4 5/9] spdx3: Add is-native property Date: Mon, 2 Mar 2026 17:43:52 -0700 Message-ID: <20260303004550.650726-6-JPEWhacker@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260303004550.650726-1-JPEWhacker@gmail.com> References: <20260226173930.2847872-1-JPEWhacker@gmail.com> <20260303004550.650726-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 ; Tue, 03 Mar 2026 00:46:03 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/232228 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 b6c917045e..a8fffbb085 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 Tue Mar 3 00:43:53 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joshua Watt X-Patchwork-Id: 82302 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 C37E5EB364F for ; Tue, 3 Mar 2026 00:46:03 +0000 (UTC) Received: from mail-oi1-f176.google.com (mail-oi1-f176.google.com [209.85.167.176]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.7687.1772498759822960365 for ; Mon, 02 Mar 2026 16:45:59 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20230601 header.b=b77G0mO6; spf=pass (domain: gmail.com, ip: 209.85.167.176, mailfrom: jpewhacker@gmail.com) Received: by mail-oi1-f176.google.com with SMTP id 5614622812f47-4639279c7a6so1833982b6e.1 for ; Mon, 02 Mar 2026 16:45:59 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772498759; x=1773103559; 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=GygUv99XXYXKewxHRq+dVr3zF/qZkskkMg0YO5XWbQU=; b=b77G0mO6iqsHuQUZusyjtA5KiZLIlvcd7AlhO7pDrFudLW7rijLfkMYCXgmhfyaSmM 85kFpqfJWzTZHnE2Nc0lwtLzGHouMmc8OC7MCMAxZ1TCRoNhceVevF6TF9zTbdXFqKFo qT6IyXMUXrUGGhOrUpOIYT3seYTmQ0TdgUW/JciOfCoAwcePiIn6q5I/d7Umj9wptARv tB7ukmoMbPp5B+E1YdHnga+/Frh/hxhDNVIQzMgEyqatInIxaahMA9uje+hkibKGHS9m KFwpCHIuyxvtAIOBOS4i9uCIxIppFy3D7LMYKLLM89CMmB/+AaDNmsVfQoa/i89ddWTD AuMg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772498759; x=1773103559; 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=GygUv99XXYXKewxHRq+dVr3zF/qZkskkMg0YO5XWbQU=; b=XNZuRwcYyB8J9e3YmCx5GjTO+CehPhVOR1xb/iFuGU0/2tgsViD0T5aYPdJCvdu06c NoILh8uDGB5XY7KDvwOKDwjjIEXVwrSnqina6z3Wggwd50p4Ygs1boqZaR1hwuqM0h0/ MU89QPT4nZgOEglHsNApx0ZTQBPJFOGgDDDW23FMpX5H42nMSyg41EdW2yeWRyuCDd00 CyNDthAkzvdmraHxL55XFajl84K5z6ZPmSa7awZGpc8aAmPAB+2iLRfL9dL0fe0pmoFh /1x6CK1cGHI8G3/SioDZomdaBpMryKvMN2/rISCyP571AcYZ8PzJwvkc2G5aaiOY/n58 RymA== X-Gm-Message-State: AOJu0YzjlH6p37lsiijOlB0MmUeiy3LUB3a2SA2Y2Q8L++Jg8e7bu7rc pjSJBCyio2px2kVQu/vDb/kXh2NcBNG+fvlsmJ7Qw/I7LnYCJpD2+DEQESO4KQ== X-Gm-Gg: ATEYQzzWaUxyeH8PZkawwY4vNOe6Ex0CV+Suj81hjGLwZGkFrmmmNHIglUzwuU60fNM qXsW+TQKzJfLjQNzUpnKLjKU1JyWNp/bDZHn9aqQJPkZlwZuOPDyzfGgx1TbvppwS3ngWFe21vf rMYa2S9S3RFdXLwfFVLR0rTppI5XAy9JWssoZVYIbZHlLpqjIW0ilJo9v4f34q81lEiEugJ3J72 mh8244ZYPio06WZI7CQCzZZvQ/5FNq9mAGFW2d8QnCjJbVfNC7xzpZF3DxHv9X6FxCKool/Vx5E UisLZCgNyOnR/kg9dzh0sDdIxWDwtUMXJA5bQhOMEWPWJRweS6H4KW8IRGRqtBwsshViYWN6A8E m2N7slfFPdIpiGgP4n6Ic6Vt7N/BFVkhAwSTod8PY/fD7r030jc66ZyYq4SbV5tGUp5zRbdzcaj WDCuzudwj+QoUPZ9OxnTG/ X-Received: by 2002:a05:6808:e8e:b0:45c:9a93:1b9f with SMTP id 5614622812f47-464bec1d91dmr6841201b6e.53.1772498758828; Mon, 02 Mar 2026 16:45:58 -0800 (PST) Received: from localhost.localdomain ([2601:282:4200:11c0::ba6c]) by smtp.gmail.com with ESMTPSA id 5614622812f47-464bb59b66fsm8637446b6e.10.2026.03.02.16.45.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 02 Mar 2026 16:45:58 -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 v4 6/9] spdx30: Include patch file information in VEX Date: Mon, 2 Mar 2026 17:43:53 -0700 Message-ID: <20260303004550.650726-7-JPEWhacker@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260303004550.650726-1-JPEWhacker@gmail.com> References: <20260226173930.2847872-1-JPEWhacker@gmail.com> <20260303004550.650726-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 ; Tue, 03 Mar 2026 00:46:03 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/232229 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 a8fffbb085..aec47d4f81 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 Tue Mar 3 00:43:54 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joshua Watt X-Patchwork-Id: 82301 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 C2A6FEB364E for ; Tue, 3 Mar 2026 00:46:03 +0000 (UTC) Received: from mail-oi1-f182.google.com (mail-oi1-f182.google.com [209.85.167.182]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.7689.1772498760545146402 for ; Mon, 02 Mar 2026 16:46:00 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20230601 header.b=B6u+v2rP; spf=pass (domain: gmail.com, ip: 209.85.167.182, mailfrom: jpewhacker@gmail.com) Received: by mail-oi1-f182.google.com with SMTP id 5614622812f47-45f09874c4cso3775433b6e.3 for ; Mon, 02 Mar 2026 16:46:00 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772498759; x=1773103559; 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=B6u+v2rPF22gYNeJglrL/k+gq9iyfdiro4MfRfPYxyMHvXXU96SLXMNGdMUcqkdh0W OtT9dbwF71E7J4pk2qoP9dGhU4KOvyoFEE9IdEP0fpESaJAy4KYqLcc6I+kciJvFgyJX Bv+z2zw8goJNpkTbWR1q+DEbSwRahf3sZ8Z3bspN54twJdP036g4siOXJTf5/quZI3Ol x36tdVUPVSWluP92N5c+zmVKxHofbrynNIH5QX0ywYlXPaPul1uPaVVkNzAu4lZW70BP De3KZCxJmVFLVw94PlQu1KIu/ApK7uiNuiMsdtxmZd+S07eBbyygn5GsvCcMT+wvGh+P aPvw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772498759; x=1773103559; 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=pOOsbRPgpWropCut7SbJCXlcztDDpswSeI+Hc1c5C8oqAVEndLj/3k+tYNjf+doOk7 J5CYvWHkLHcukccctgH6WzjaiUYf7LBrVDISCE5BCedJZdOkwFKIsds9JhLV1CNQbY+w EM2Fk3rX5hSOtt21B9MTxSKh+udiPobJ4P/KCagHu8/f4HDZq+FH3cVPN98iJED3KFQd ouUCp+MEaQ52sgzdrnV39xa3gfLGBddw07pr/P+330LKSiN4UYYG0wh+TB3HxZef5SH7 arAMte19B7tQIEoITnAlyXp0JZqmA0Bl/5lcnFLb+PyWOXPWnQUjlGSA+ClME19auEad +CHQ== X-Gm-Message-State: AOJu0Yz/SfGG9nB8Bi+j3yHdUcdmoMhvDS9udDJ8CAPgRE6esU/1rlZb QhdF+r4X1UnggemjnDiosrvOlQetkMS2UV/ZcKsAz+76nim9fBRDVJ3C18MTQg== X-Gm-Gg: ATEYQzzUA9igtsKdsqsYSZzVbbbHChykY432GsMwLiLe1VjjJyDeStS6bYvHiewsax2 PTP4gm2Y/YZeuT+ZanKiHA1VN7US72pWIE4HE3RtbvMBq7cmny2CFmSWlDNIPNNHS2Zm5ZGD81z mGb6nyoWe64AOncDwoqo5WDenaWLgoSIWed2HfddlF4vDw6ifAxHCXYBtGWlIK8u/+cNbb8myyv DQH8S4ASN0ml/KutGsgKKJ5B6BlebayLB2gGhMuHBhidzRywGN7SGytvUoNCcaACJWRX3asTkjk SCdJAvUK466k7olUdmGuXlXfZTgNOl0WJOnBzReRqXp9xHV3XeQXysc93/tOrkYhqaadq2MAN34 Ox0enKe7pqSoxMM6uhV1ZX94q66X8PXBP9aQzRbgA3LT5ntqGazaR3EltpAqLBv/Dxi1K+YHlKh ojZ50HqzPCoij+xe758O9a X-Received: by 2002:a05:6808:171a:b0:460:fce5:2fcf with SMTP id 5614622812f47-464beb43d2dmr7246701b6e.37.1772498759630; Mon, 02 Mar 2026 16:45:59 -0800 (PST) Received: from localhost.localdomain ([2601:282:4200:11c0::ba6c]) by smtp.gmail.com with ESMTPSA id 5614622812f47-464bb59b66fsm8637446b6e.10.2026.03.02.16.45.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 02 Mar 2026 16:45:59 -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 v4 7/9] spdx: De-duplicate CreationInfo Date: Mon, 2 Mar 2026 17:43:54 -0700 Message-ID: <20260303004550.650726-8-JPEWhacker@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260303004550.650726-1-JPEWhacker@gmail.com> References: <20260226173930.2847872-1-JPEWhacker@gmail.com> <20260303004550.650726-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 ; Tue, 03 Mar 2026 00:46:03 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/232230 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 Tue Mar 3 00:43:55 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joshua Watt X-Patchwork-Id: 82300 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 D00D4EB3652 for ; Tue, 3 Mar 2026 00:46:03 +0000 (UTC) Received: from mail-oi1-f173.google.com (mail-oi1-f173.google.com [209.85.167.173]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.7693.1772498761309840376 for ; Mon, 02 Mar 2026 16:46:01 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20230601 header.b=Wpg9qWdB; spf=pass (domain: gmail.com, ip: 209.85.167.173, mailfrom: jpewhacker@gmail.com) Received: by mail-oi1-f173.google.com with SMTP id 5614622812f47-464ba2bb3aeso4012552b6e.1 for ; Mon, 02 Mar 2026 16:46:01 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772498760; x=1773103560; 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=Wpg9qWdBIUqb28o5fmhdYIWUd5axE3xKqTXAgedjcbSaMib3FRsppxgh4sYoEn2EdX /fChspOznfo413Tq57sGx1uH0vqVsUYrc7dbT6GSm9+cmxlPZdmwThegAjHk5eHY/w7D iJHyQU6NvhqcPiyH7bCCllG/exINi1gcY6+bfrfrk6tMWjpkgIx9UAH+C+gTcV76NGio 9UHCKMQi5oNG9juMgpK2FYcnLDM7WzradD3C4vB30iLZjXKMuIrMj9uhGvekCEby/9ih OrbU4ZiGXE4zUHh2Oss2FpeHp9BOWRmGbnA1SYXG/yVGGxeyrQwZR73JSC9svNwDqP3k 2gqA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772498760; x=1773103560; 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=uiFLoiO5RTYqcltMQbouVlSP1LKietxfH0OQFGIoQVChVsilLzSKDk3YSJrC7k3rpv SiAL0rV4fSTKZi93Zvvip7ReQJantEqZBOes/oxCsoqe/HeoWU10q1wK6bF3mkk5GvWV 6GiPfbYeYZLcENpEEaKK5gj1z9eoK0TmzmSHeCoRAh/g1SSe/45Xgp6uLO2jzamgRDeE mhfU1MZQ6iE2TDy12dse/XrxqjHWpjZejQWPmVxNdnzpgNnNrRPJKniNwWkWXyBHLy2F S7LoOeUIYN/W9t9NvQ9E7QkMvd2a61uADGDeNWowa9Uaf4s1RVR1dxcTPmlcyYexG64g RT7Q== X-Gm-Message-State: AOJu0YzUzk7AI2Qz8nnPznpXYGsEeVbx9uAmvEPaezwWk1oFwJNy0vOq ZdGb9EWwaO32XMpQ8XWfmYlSzUH7UEPgk06AFNvyV7iUtFUgN+Z+21bJNzOIeA== X-Gm-Gg: ATEYQzzleM9stCAp0pVn0oxOJsQaSqwa6hrZWdi3F8IKgupq2uA0sGJ4ZDi8sS4APFD KOUDsn6f+JIZTkXMZw+2L//CiG7tYTKpNNW7C/4JBYI/fAwnS4M9ryCkTupKC6cI71yDQGEi1V3 ePECkMg7Z35PWduCujVhotY+/q6J93sJ75GoEoi98KDKLln/U5jxqYwkjXVfC9QG6glIgj1/c+x kGagKzyJzOWgRcVlLB/JfS44M+gF/gRq2UbYoRYMdUFTzfxj6hs5mobikLzVQNvdm8D1B6HyTub gvV3fhbLEvPZ7+xmBbiP/B7ISZL/35RogFCcHDqWgN6OAomqy9JBdbGO4ePS9iEYCOc3LXbPbIC p0/03PKrI00yQH/vEMQICTkm8/njl+uLsK04YsaKbPkUxlk0l6OAuYtHY12LGg8KFKJmIZaiQDw qEAQxh7rpS+URjPytVVDyl X-Received: by 2002:a05:6808:2383:b0:455:eba2:9efa with SMTP id 5614622812f47-4650c625651mr186337b6e.4.1772498760426; Mon, 02 Mar 2026 16:46:00 -0800 (PST) Received: from localhost.localdomain ([2601:282:4200:11c0::ba6c]) by smtp.gmail.com with ESMTPSA id 5614622812f47-464bb59b66fsm8637446b6e.10.2026.03.02.16.45.59 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 02 Mar 2026 16:46:00 -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 v4 8/9] spdx_common: Check for dependent task in task flags Date: Mon, 2 Mar 2026 17:43:55 -0700 Message-ID: <20260303004550.650726-9-JPEWhacker@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260303004550.650726-1-JPEWhacker@gmail.com> References: <20260226173930.2847872-1-JPEWhacker@gmail.com> <20260303004550.650726-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 ; Tue, 03 Mar 2026 00:46:03 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/232231 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 From patchwork Tue Mar 3 00:43:56 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joshua Watt X-Patchwork-Id: 82304 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 D0528EB3653 for ; Tue, 3 Mar 2026 00:46:03 +0000 (UTC) Received: from mail-oi1-f177.google.com (mail-oi1-f177.google.com [209.85.167.177]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.7691.1772498762122881880 for ; Mon, 02 Mar 2026 16:46:02 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20230601 header.b=hzKU6Yt7; spf=pass (domain: gmail.com, ip: 209.85.167.177, mailfrom: jpewhacker@gmail.com) Received: by mail-oi1-f177.google.com with SMTP id 5614622812f47-463208653d6so4079259b6e.3 for ; Mon, 02 Mar 2026 16:46:02 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772498761; x=1773103561; 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=9Wsvm7UZ+qXi5zyTV0UmCwyCSQ0eIKoDgVpDnS6yewE=; b=hzKU6Yt78mphUB5bkpnXjI8hYiF87Y+PZv24YnPIDz7S3uk+2v9DZdmRRJLt+MKKqE fElmsIP/Rv7ZKJ39tc0I2vQWWUzGEJaM2PpOLTH9v/d7V/aupVdcMmDmUjLrvdgv9loX 4zrsd0gPXfc/qljz/IvNCeXWu2ih4L3Pn+8r/oLb2ZdOirx1cZxmtRLlx8cb65XnN3pc fz/+GrgEKpOUtaIXwOg8MzOByycuEMUvMOMHyucHlsfIkElS2vllPblGXvwmfSYUdhv1 H8vKI0oUxZ9Xo3UNp0Ked9RvsQlCePX6CAsMts6ySH++Vfm9I0hkXdY9RQQDEjIeJWFW kkzA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772498761; x=1773103561; 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=9Wsvm7UZ+qXi5zyTV0UmCwyCSQ0eIKoDgVpDnS6yewE=; b=n+D7r/Y8ijT5rVdk5HKVSIig2Rs003OwMaZlSHKGshTyz0wJqwozFtd+RbEYrl3TeV VZTg/Kr7DYppFefxUeSsd4JK3+DNcM58Nwyndvs+5pDPFJid0TXbBVCWr3dMISSv5m+X i1Cjnkvtv1MpMh7MfyE7doCvo12x1g2dHESa35OUlaqfPSTfujc7p09HVBdKvTAGD++3 gVml6b5QCSLoATKIk+3iFFeUudLkG3TsN+iiSKfKB4F/PqaOBEtrVcHliVIZoV+ARZE0 b+iJ8zUxVWwGui7F1LrdkcpEUL7tElkkvBqSeEZDjZjVe4F7GYJp+Q2JfA6GmsCa0EM7 5GfQ== X-Gm-Message-State: AOJu0Yxbu9dTB3NP3qOCbTKFDFHOn/wmUWMzRuW/2KEW2gqiKPMjIAz7 slSyukjqCx+XYKEdUcYYKkCdFEZycZDZkf0a1TeqRKx+r1WebLJOFNICNB8ISA== X-Gm-Gg: ATEYQzz6hgzUrbyKp8Q4wfQevxKcKOaUKALjS0e3xwsYXV9PnOOCzLtHy7pWidwPFmx Jb0cfQ17CHWCbVNn2QG2mCTY1uve32BYYzn3lmmmcN719xWxjj7c3EnBJ8jVfW5RUB08Krs5scQ 6ursvTf6RrwPp1xQ5gTx/zKiM6eWHblF/chvqTrVoy14uELw0E069wbvh0aCHK7hwJT4nmd6jGt wsIPI4ImtH78hIW+24d73Ra/dNdeEjwO9JDInWrpEHETqEJgmypTz1RFlxDYF9H3Yh7iHovqTsk OvtG38MU9s3Uh3CZUEg9/RKkLmCsijNOQWAT8TLSuguieogYZ5u+c53xPawDAe3D4c9RoDAlP17 yCdW4Rd5C1rmBkmP61sahLTWeLk1A+8HVuYwe1jXfEGdShtSPbjqyNAM9gjQy0ljTXE2XIyyqTl pOIO6258o6LuFu9f8rZaiM2lGpdB+ClOs= X-Received: by 2002:a05:6808:67c5:b0:459:a26c:2c3c with SMTP id 5614622812f47-464be9cadbbmr6981087b6e.26.1772498761144; Mon, 02 Mar 2026 16:46:01 -0800 (PST) Received: from localhost.localdomain ([2601:282:4200:11c0::ba6c]) by smtp.gmail.com with ESMTPSA id 5614622812f47-464bb59b66fsm8637446b6e.10.2026.03.02.16.46.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 02 Mar 2026 16:46:00 -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 v4 9/9] spdx30: Skip install package CVE information Date: Mon, 2 Mar 2026 17:43:56 -0700 Message-ID: <20260303004550.650726-10-JPEWhacker@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260303004550.650726-1-JPEWhacker@gmail.com> References: <20260226173930.2847872-1-JPEWhacker@gmail.com> <20260303004550.650726-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 ; Tue, 03 Mar 2026 00:46:03 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/232232 Skips adding the install package CVE information by default. This information grows exponentially, since it ends up be N_CVES * N_PACKAGES. The CVE information for a given installed package can be determined by following the "generates" link between the install package and the recipe and looking at the CVE information for the recipe, meaning that the CVE information is only included once in the SPDX document. If users still need the legacy method of including CVE information for each package, then then can set SPDX_PACKAGE_INCLUDE_VEX = "1" Signed-off-by: Joshua Watt --- meta/classes/create-spdx-3.0.bbclass | 11 ++++++++ meta/lib/oe/spdx30_tasks.py | 39 ++++++++++++++-------------- meta/lib/oeqa/selftest/cases/spdx.py | 12 +++++++++ 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/meta/classes/create-spdx-3.0.bbclass b/meta/classes/create-spdx-3.0.bbclass index c3ea95b8bc..88b7ef9f42 100644 --- a/meta/classes/create-spdx-3.0.bbclass +++ b/meta/classes/create-spdx-3.0.bbclass @@ -45,6 +45,17 @@ SPDX_INCLUDE_VEX[doc] = "Controls what VEX information is in the output. Set to including those already fixed upstream (warning: This can be large and \ slow)." +SPDX_PACKAGE_INCLUDE_VEX ?= "0" +SPDX_PACKAGE_INCLUDE_VEX[doc] = "Link VEX information to the binary package outputs. \ + Normally, VEX information is only linked to the common recipe that `generates` the \ + binary packages, but setting this to '1' will cause it to also be linked into the \ + generated binary packages. This is off by default because linking the VEX data to \ + each package causes the SPDX output to grow very large, and the same information \ + can be determined by following the `generates` relationship back to the recipe. \ + Before recipe packages were introduced, this was the only way VEX data was \ + expressed; you may need to enable this if your downstream tools do not \ + understand how to trace back to the recipe to find VEX information." + SPDX_INCLUDE_TIMESTAMPS ?= "0" SPDX_INCLUDE_TIMESTAMPS[doc] = "Include time stamps in SPDX output. This is \ useful if you want to know when artifacts were produced and when builds \ diff --git a/meta/lib/oe/spdx30_tasks.py b/meta/lib/oe/spdx30_tasks.py index aec47d4f81..887fac813a 100644 --- a/meta/lib/oe/spdx30_tasks.py +++ b/meta/lib/oe/spdx30_tasks.py @@ -771,27 +771,28 @@ 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(): + if (d.getVar("SPDX_PACKAGE_INCLUDE_VEX") or "") == "1": for rel in recipe_objset.foreach_filter( - oe.spdx30.security_VexVulnAssessmentRelationship, - from_=cve, + oe.spdx30.Relationship, + relationshipType=oe.spdx30.RelationshipType.hasAssociatedVulnerability, ): - 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) + for cve in rel.to: + vex_statements[cve] = [] + vex_patches[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) + 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 diff --git a/meta/lib/oeqa/selftest/cases/spdx.py b/meta/lib/oeqa/selftest/cases/spdx.py index efee0214fc..f1ea2694cf 100644 --- a/meta/lib/oeqa/selftest/cases/spdx.py +++ b/meta/lib/oeqa/selftest/cases/spdx.py @@ -429,3 +429,15 @@ class SPDX30Check(SPDX3CheckBase, OESelftestTestCase): value, ["enabled", "disabled"], f"Unexpected PACKAGECONFIG value '{value}' for {key}" ) + + def test_package_vex(self): + objset = self.check_recipe_spdx( + "core-image-minimal", + "{DEPLOY_DIR_IMAGE}/core-image-minimal-{MACHINE}.rootfs.spdx.json", + extraconf="""\ + SPDX_PACKAGE_INCLUDE_VEX = "1" + """, + ) + + # Document should be fully linked + self.check_objset_missing_ids(objset)