diff mbox series

[2/2] spdx30_tasks: Add concluded license support with SPDX_CONCLUDED_LICENSE

Message ID 20251218120139.104155-3-stondo@gmail.com
State New
Headers show
Series spdx30: Add summary field and concluded license support | expand

Commit Message

Stefano Tondo Dec. 18, 2025, 12:01 p.m. UTC
From: Stefano Tondo <stefano.tondo.ext@siemens.com>

Add hasConcludedLicense relationship to SBOM packages with support for
manual license conclusion override via SPDX_CONCLUDED_LICENSE variable.

The concluded license represents the license determination after manual
or external license analysis. This should be set manually in recipes or
layers when:

1. Manual license review identifies differences from the declared LICENSE
2. External license scanning tools detect additional license information
3. Legal review concludes a different license applies

By default, concluded license equals declared license (indicating no
separate license analysis was performed). When differences are found,
users should:

1. Preferably: Correct the LICENSE field in the recipe and contribute
   the fix upstream to OpenEmbedded
2. Alternatively: Set SPDX_CONCLUDED_LICENSE locally in your layer when
   upstream contribution is not immediately possible or when the license
   conclusion is environment-specific

This variable allows tracking license analysis results in the SBOM while
maintaining the recipe LICENSE field for build system compatibility.

The variable is initialized in spdx-common.bbclass with comprehensive
documentation explaining its purpose, usage guidelines, and examples.

Example usage in recipe or layer:
  SPDX_CONCLUDED_LICENSE = "MIT & Apache-2.0"

Signed-off-by: Stefano Tondo <stefano.tondo.ext@siemens.com>
---
 meta/classes/spdx-common.bbclass | 13 +++++++++++++
 meta/lib/oe/spdx30_tasks.py      | 21 +++++++++++++++++++++
 2 files changed, 34 insertions(+)
diff mbox series

Patch

diff --git a/meta/classes/spdx-common.bbclass b/meta/classes/spdx-common.bbclass
index ca0416d1c7..3ca4c70cc0 100644
--- a/meta/classes/spdx-common.bbclass
+++ b/meta/classes/spdx-common.bbclass
@@ -36,6 +36,19 @@  SPDX_LICENSES ??= "${COREBASE}/meta/files/spdx-licenses.json"
 
 SPDX_CUSTOM_ANNOTATION_VARS ??= ""
 
+SPDX_CONCLUDED_LICENSE ??= ""
+SPDX_CONCLUDED_LICENSE[doc] = "The license concluded by manual or external \
+    license analysis. This should only be set when license analysis (manual review \
+    or external scanning tools) identifies differences from the declared LICENSE. \
+    When unset or empty, the concluded license defaults to the declared license, \
+    indicating no separate analysis was performed. When differences are found, the \
+    preferred approach is to correct the LICENSE field in the recipe and contribute \
+    the fix upstream to OpenEmbedded. Use this variable locally only when upstream \
+    contribution is not immediately possible or when the license conclusion is \
+    environment-specific. This allows tracking license analysis results in SBOM \
+    while maintaining recipe LICENSE field for build compatibility. \
+    Example: SPDX_CONCLUDED_LICENSE = 'MIT & Apache-2.0'"
+
 SPDX_MULTILIB_SSTATE_ARCHS ??= "${SSTATE_ARCHS}"
 
 python () {
diff --git a/meta/lib/oe/spdx30_tasks.py b/meta/lib/oe/spdx30_tasks.py
index 286a08ed9b..84d70f6f72 100644
--- a/meta/lib/oe/spdx30_tasks.py
+++ b/meta/lib/oe/spdx30_tasks.py
@@ -712,6 +712,27 @@  def create_spdx(d):
                 oe.spdx30.RelationshipType.hasDeclaredLicense,
                 [oe.sbom30.get_element_link_id(package_spdx_license)],
             )
+            
+            # Add concluded license relationship
+            # Use SPDX_CONCLUDED_LICENSE if set, otherwise default to declared license
+            concluded_license_str = d.getVar("SPDX_CONCLUDED_LICENSE")
+            if concluded_license_str:
+                # Use explicitly set concluded license
+                if concluded_license_str != package_license and concluded_license_str != d.getVar("LICENSE"):
+                    concluded_spdx_license = add_license_expression(
+                        d, build_objset, concluded_license_str, license_data
+                    )
+                else:
+                    concluded_spdx_license = package_spdx_license
+            else:
+                # Default: concluded = declared (no analysis performed)
+                concluded_spdx_license = package_spdx_license
+            
+            pkg_objset.new_relationship(
+                [spdx_package],
+                oe.spdx30.RelationshipType.hasConcludedLicense,
+                [oe.sbom30.get_element_link_id(concluded_spdx_license)],
+            )
 
             # NOTE: CVE Elements live in the recipe collection
             all_cves = set()