public inbox for openembedded-core@lists.openembedded.org
 help / color / mirror / Atom feed
* [PATCH v2] spdx30_tasks: Add concluded license support with SPDX_CONCLUDED_LICENSE
@ 2026-01-06 11:25 stondo
  2026-01-06 22:03 ` Joshua Watt
                   ` (2 more replies)
  0 siblings, 3 replies; 7+ messages in thread
From: stondo @ 2026-01-06 11:25 UTC (permalink / raw)
  To: openembedded-core
  Cc: stefano.tondo.ext, peter.marko, adrian.freihofer, JPEWhacker

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

The hasConcludedLicense relationship is ONLY added to the SBOM when
SPDX_CONCLUDED_LICENSE is explicitly set. When unset or empty, no
concluded license is included in the SBOM, correctly indicating that
no license analysis was performed (per SPDX semantics).

When differences from the declared LICENSE 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 | 14 ++++++++++++++
 meta/lib/oe/spdx30_tasks.py      | 18 ++++++++++++++++++
 2 files changed, 32 insertions(+)

diff --git a/meta/classes/spdx-common.bbclass b/meta/classes/spdx-common.bbclass
index ca0416d1c7..504e6fba45 100644
--- a/meta/classes/spdx-common.bbclass
+++ b/meta/classes/spdx-common.bbclass
@@ -36,6 +36,20 @@ 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 explicit license analysis \
+    (manual review or external scanning tools) has been performed and a license \
+    conclusion has been reached. When unset or empty, no concluded license is \
+    included in the SBOM, indicating that no license analysis was performed. \
+    When differences from the declared LICENSE 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..885b9c5549 100644
--- a/meta/lib/oe/spdx30_tasks.py
+++ b/meta/lib/oe/spdx30_tasks.py
@@ -712,6 +712,24 @@ def create_spdx(d):
                 oe.spdx30.RelationshipType.hasDeclaredLicense,
                 [oe.sbom30.get_element_link_id(package_spdx_license)],
             )
+            
+            # Add concluded license relationship if manually set
+            # Only add when license analysis has been explicitly performed
+            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
+                
+                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()
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH v2] spdx30_tasks: Add concluded license support with SPDX_CONCLUDED_LICENSE
  2026-01-06 11:25 [PATCH v2] spdx30_tasks: Add concluded license support with SPDX_CONCLUDED_LICENSE stondo
@ 2026-01-06 22:03 ` Joshua Watt
  2026-01-07  9:17 ` [PATCH v3] " stondo
  2026-01-07 18:15 ` [PATCH v4] " stondo
  2 siblings, 0 replies; 7+ messages in thread
From: Joshua Watt @ 2026-01-06 22:03 UTC (permalink / raw)
  To: stondo; +Cc: openembedded-core, stefano.tondo.ext, peter.marko,
	adrian.freihofer

On Tue, Jan 6, 2026 at 4:26 AM <stondo@gmail.com> wrote:
>
> 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
>
> The hasConcludedLicense relationship is ONLY added to the SBOM when
> SPDX_CONCLUDED_LICENSE is explicitly set. When unset or empty, no
> concluded license is included in the SBOM, correctly indicating that
> no license analysis was performed (per SPDX semantics).
>
> When differences from the declared LICENSE 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 | 14 ++++++++++++++
>  meta/lib/oe/spdx30_tasks.py      | 18 ++++++++++++++++++
>  2 files changed, 32 insertions(+)
>
> diff --git a/meta/classes/spdx-common.bbclass b/meta/classes/spdx-common.bbclass
> index ca0416d1c7..504e6fba45 100644
> --- a/meta/classes/spdx-common.bbclass
> +++ b/meta/classes/spdx-common.bbclass
> @@ -36,6 +36,20 @@ 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 explicit license analysis \
> +    (manual review or external scanning tools) has been performed and a license \
> +    conclusion has been reached. When unset or empty, no concluded license is \
> +    included in the SBOM, indicating that no license analysis was performed. \
> +    When differences from the declared LICENSE 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..885b9c5549 100644
> --- a/meta/lib/oe/spdx30_tasks.py
> +++ b/meta/lib/oe/spdx30_tasks.py
> @@ -712,6 +712,24 @@ def create_spdx(d):
>                  oe.spdx30.RelationshipType.hasDeclaredLicense,
>                  [oe.sbom30.get_element_link_id(package_spdx_license)],
>              )
> +
> +            # Add concluded license relationship if manually set
> +            # Only add when license analysis has been explicitly performed
> +            concluded_license_str = d.getVar("SPDX_CONCLUDED_LICENSE")

Should also check SPDX_CONCLUDED_LICENSE:${PN}

> +            if concluded_license_str:
> +                # Use explicitly set concluded license
> +                if concluded_license_str != package_license and concluded_license_str != d.getVar("LICENSE"):

I missed this last time, but this looks wrong to me. The check that
the concluded license matches the package makes sense, but the check
if it matches LICENSE doesn't, since the 'else' sets it to package
license, which means if if the concluded license doesn't match the
package license, but it _does_ match LICENSE, it will be set to the
package license.

Anyway, this check isn't even necessary. If you dig down into
add_license_expression() -> objset.new_licenses_expression(), you can
see that it has code to de-duplicate the license expressions. That
means that all you need to do is:

if concluded_license_str:
    concluded_spdx_license = add_license_expression(
        d, build_objset, concluded_license_str, license_data
    )
    pkg_objset.new_relationship(
        [spdx_package],
        oe.spdx30.RelationshipType.hasConcludedLicense,
        [oe.sbom30.get_element_link_id(concluded_spdx_license)],
    )

And it _should_ be automatically de-duplicated with any other existing license.

> +                    concluded_spdx_license = add_license_expression(
> +                        d, build_objset, concluded_license_str, license_data
> +                    )
> +                else:
> +                    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()
> --
> 2.52.0
>


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v3] spdx30_tasks: Add concluded license support with SPDX_CONCLUDED_LICENSE
  2026-01-06 11:25 [PATCH v2] spdx30_tasks: Add concluded license support with SPDX_CONCLUDED_LICENSE stondo
  2026-01-06 22:03 ` Joshua Watt
@ 2026-01-07  9:17 ` stondo
  2026-01-07 18:15 ` [PATCH v4] " stondo
  2 siblings, 0 replies; 7+ messages in thread
From: stondo @ 2026-01-07  9:17 UTC (permalink / raw)
  To: openembedded-core
  Cc: stefano.tondo.ext, peter.marko, adrian.freihofer, JPEWhacker

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

The hasConcludedLicense relationship is ONLY added to the SBOM when
SPDX_CONCLUDED_LICENSE is explicitly set. When unset or empty, no
concluded license is included in the SBOM, correctly indicating that
no license analysis was performed (per SPDX semantics).

When differences from the declared LICENSE 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

The implementation checks both package-specific overrides
(SPDX_CONCLUDED_LICENSE:${PN}) and the global variable, allowing
per-package license conclusions when needed.

The concluded license expression is automatically de-duplicated by
add_license_expression() to avoid redundant license objects in the SBOM.

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"
  SPDX_CONCLUDED_LICENSE:${PN} = "MIT & Apache-2.0"

Signed-off-by: Stefano Tondo <stefano.tondo.ext@siemens.com>
---
 meta/classes/spdx-common.bbclass | 16 ++++++++++++++++
 meta/lib/oe/spdx30_tasks.py      | 14 ++++++++++++++
 2 files changed, 30 insertions(+)

diff --git a/meta/classes/spdx-common.bbclass b/meta/classes/spdx-common.bbclass
index ca0416d1c7..3110230c9e 100644
--- a/meta/classes/spdx-common.bbclass
+++ b/meta/classes/spdx-common.bbclass
@@ -36,6 +36,22 @@ 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 explicit license analysis \
+    (manual review or external scanning tools) has been performed and a license \
+    conclusion has been reached. When unset or empty, no concluded license is \
+    included in the SBOM, indicating that no license analysis was performed. \
+    When differences from the declared LICENSE 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. \
+    Supports package-specific overrides via SPDX_CONCLUDED_LICENSE:${PN}. \
+    This allows tracking license analysis results in SBOM while maintaining recipe \
+    LICENSE field for build compatibility. \
+    Example: SPDX_CONCLUDED_LICENSE = 'MIT & Apache-2.0' or \
+    SPDX_CONCLUDED_LICENSE:${PN} = '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..b099fb201e 100644
--- a/meta/lib/oe/spdx30_tasks.py
+++ b/meta/lib/oe/spdx30_tasks.py
@@ -712,6 +712,20 @@ def create_spdx(d):
                 oe.spdx30.RelationshipType.hasDeclaredLicense,
                 [oe.sbom30.get_element_link_id(package_spdx_license)],
             )
+            
+            # 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")
+            if concluded_license_str:
+                concluded_spdx_license = add_license_expression(
+                    d, build_objset, concluded_license_str, license_data
+                )
+                
+                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()
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v4] spdx30_tasks: Add concluded license support with SPDX_CONCLUDED_LICENSE
  2026-01-06 11:25 [PATCH v2] spdx30_tasks: Add concluded license support with SPDX_CONCLUDED_LICENSE stondo
  2026-01-06 22:03 ` Joshua Watt
  2026-01-07  9:17 ` [PATCH v3] " stondo
@ 2026-01-07 18:15 ` stondo
  2026-01-07 19:40   ` Joshua Watt
  2026-02-10 12:19   ` [OE-core] " Ross Burton
  2 siblings, 2 replies; 7+ messages in thread
From: stondo @ 2026-01-07 18:15 UTC (permalink / raw)
  To: openembedded-core
  Cc: stondo, stefano.tondo.ext, peter.marko, adrian.freihofer,
	JPEWhacker

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

The hasConcludedLicense relationship is ONLY added to the SBOM when
SPDX_CONCLUDED_LICENSE is explicitly set. When unset or empty, no
concluded license is included in the SBOM, correctly indicating that
no license analysis was performed (per SPDX semantics).

When differences from the declared LICENSE 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

The implementation checks both package-specific overrides
(SPDX_CONCLUDED_LICENSE:${PN}) and the global variable, allowing
per-package license conclusions when needed.

The concluded license expression is automatically de-duplicated by
add_license_expression() to avoid redundant license objects in the SBOM.

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"
  SPDX_CONCLUDED_LICENSE:${PN} = "MIT & Apache-2.0"

Signed-off-by: Stefano Tondo <stefano.tondo.ext@siemens.com>
---
 meta/classes/spdx-common.bbclass | 16 ++++++++++++++++
 meta/lib/oe/spdx30_tasks.py      | 18 ++++++++++++++++--
 2 files changed, 32 insertions(+), 2 deletions(-)

diff --git a/meta/classes/spdx-common.bbclass b/meta/classes/spdx-common.bbclass
index ca0416d1c7..3110230c9e 100644
--- a/meta/classes/spdx-common.bbclass
+++ b/meta/classes/spdx-common.bbclass
@@ -36,6 +36,22 @@ 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 explicit license analysis \
+    (manual review or external scanning tools) has been performed and a license \
+    conclusion has been reached. When unset or empty, no concluded license is \
+    included in the SBOM, indicating that no license analysis was performed. \
+    When differences from the declared LICENSE 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. \
+    Supports package-specific overrides via SPDX_CONCLUDED_LICENSE:${PN}. \
+    This allows tracking license analysis results in SBOM while maintaining recipe \
+    LICENSE field for build compatibility. \
+    Example: SPDX_CONCLUDED_LICENSE = 'MIT & Apache-2.0' or \
+    SPDX_CONCLUDED_LICENSE:${PN} = '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..a99b017c26 100644
--- a/meta/lib/oe/spdx30_tasks.py
+++ b/meta/lib/oe/spdx30_tasks.py
@@ -636,7 +636,7 @@ def create_spdx(d):
             set_var_field(
                 "HOMEPAGE", spdx_package, "software_homePage", package=package
             )
-            
+
             # Add summary with fallback to DESCRIPTION
             summary = None
             if package:
@@ -651,7 +651,7 @@ def create_spdx(d):
                 summary = f"Package {package or d.getVar('PN')}"
             if summary:
                 spdx_package.summary = summary
-            
+
             set_var_field("DESCRIPTION", spdx_package, "description", package=package)
 
             if d.getVar("SPDX_PACKAGE_URL:%s" % package) or d.getVar("SPDX_PACKAGE_URL"):
@@ -713,6 +713,20 @@ def create_spdx(d):
                 [oe.sbom30.get_element_link_id(package_spdx_license)],
             )
 
+            # 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")
+            if concluded_license_str:
+                concluded_spdx_license = add_license_expression(
+                    d, build_objset, concluded_license_str, license_data
+                )
+
+                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()
             for status, cves in cve_by_status.items():
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH v4] spdx30_tasks: Add concluded license support with SPDX_CONCLUDED_LICENSE
  2026-01-07 18:15 ` [PATCH v4] " stondo
@ 2026-01-07 19:40   ` Joshua Watt
  2026-02-10 12:19   ` [OE-core] " Ross Burton
  1 sibling, 0 replies; 7+ messages in thread
From: Joshua Watt @ 2026-01-07 19:40 UTC (permalink / raw)
  To: stondo; +Cc: openembedded-core, stefano.tondo.ext, peter.marko,
	adrian.freihofer

On Wed, Jan 7, 2026 at 11:15 AM <stondo@gmail.com> wrote:
>
> 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
>
> The hasConcludedLicense relationship is ONLY added to the SBOM when
> SPDX_CONCLUDED_LICENSE is explicitly set. When unset or empty, no
> concluded license is included in the SBOM, correctly indicating that
> no license analysis was performed (per SPDX semantics).
>
> When differences from the declared LICENSE 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
>
> The implementation checks both package-specific overrides
> (SPDX_CONCLUDED_LICENSE:${PN}) and the global variable, allowing
> per-package license conclusions when needed.
>
> The concluded license expression is automatically de-duplicated by
> add_license_expression() to avoid redundant license objects in the SBOM.
>
> 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"
>   SPDX_CONCLUDED_LICENSE:${PN} = "MIT & Apache-2.0"
>
> Signed-off-by: Stefano Tondo <stefano.tondo.ext@siemens.com>

LGTM, Thanks

Reviewed-by: Joshua Watt <JPEWhacker@gmail.com>

> ---
>  meta/classes/spdx-common.bbclass | 16 ++++++++++++++++
>  meta/lib/oe/spdx30_tasks.py      | 18 ++++++++++++++++--
>  2 files changed, 32 insertions(+), 2 deletions(-)
>
> diff --git a/meta/classes/spdx-common.bbclass b/meta/classes/spdx-common.bbclass
> index ca0416d1c7..3110230c9e 100644
> --- a/meta/classes/spdx-common.bbclass
> +++ b/meta/classes/spdx-common.bbclass
> @@ -36,6 +36,22 @@ 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 explicit license analysis \
> +    (manual review or external scanning tools) has been performed and a license \
> +    conclusion has been reached. When unset or empty, no concluded license is \
> +    included in the SBOM, indicating that no license analysis was performed. \
> +    When differences from the declared LICENSE 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. \
> +    Supports package-specific overrides via SPDX_CONCLUDED_LICENSE:${PN}. \
> +    This allows tracking license analysis results in SBOM while maintaining recipe \
> +    LICENSE field for build compatibility. \
> +    Example: SPDX_CONCLUDED_LICENSE = 'MIT & Apache-2.0' or \
> +    SPDX_CONCLUDED_LICENSE:${PN} = '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..a99b017c26 100644
> --- a/meta/lib/oe/spdx30_tasks.py
> +++ b/meta/lib/oe/spdx30_tasks.py
> @@ -636,7 +636,7 @@ def create_spdx(d):
>              set_var_field(
>                  "HOMEPAGE", spdx_package, "software_homePage", package=package
>              )
> -
> +
>              # Add summary with fallback to DESCRIPTION
>              summary = None
>              if package:
> @@ -651,7 +651,7 @@ def create_spdx(d):
>                  summary = f"Package {package or d.getVar('PN')}"
>              if summary:
>                  spdx_package.summary = summary
> -
> +
>              set_var_field("DESCRIPTION", spdx_package, "description", package=package)
>
>              if d.getVar("SPDX_PACKAGE_URL:%s" % package) or d.getVar("SPDX_PACKAGE_URL"):
> @@ -713,6 +713,20 @@ def create_spdx(d):
>                  [oe.sbom30.get_element_link_id(package_spdx_license)],
>              )
>
> +            # 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")
> +            if concluded_license_str:
> +                concluded_spdx_license = add_license_expression(
> +                    d, build_objset, concluded_license_str, license_data
> +                )
> +
> +                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()
>              for status, cves in cve_by_status.items():
> --
> 2.52.0
>


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [OE-core] [PATCH v4] spdx30_tasks: Add concluded license support with SPDX_CONCLUDED_LICENSE
  2026-01-07 18:15 ` [PATCH v4] " stondo
  2026-01-07 19:40   ` Joshua Watt
@ 2026-02-10 12:19   ` Ross Burton
  2026-02-16 20:51     ` Tondo, Stefano
  1 sibling, 1 reply; 7+ messages in thread
From: Ross Burton @ 2026-02-10 12:19 UTC (permalink / raw)
  To: stondo@gmail.com
  Cc: openembedded-core@lists.openembedded.org,
	stefano.tondo.ext@siemens.com, peter.marko@siemens.com,
	adrian.freihofer@siemens.com, Joshua Watt

Hi Stefano,

> On 7 Jan 2026, at 18:15, Stefano Tondo via lists.openembedded.org <stondo=gmail.com@lists.openembedded.org> wrote:
> 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
> 
> The hasConcludedLicense relationship is ONLY added to the SBOM when
> SPDX_CONCLUDED_LICENSE is explicitly set. When unset or empty, no
> concluded license is included in the SBOM, correctly indicating that
> no license analysis was performed (per SPDX semantics).

Could you add a test case to oeqa to this, so that we know the conditional behaviour doesn’t change or break?

Thanks
Ross

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [OE-core] [PATCH v4] spdx30_tasks: Add concluded license support with SPDX_CONCLUDED_LICENSE
  2026-02-10 12:19   ` [OE-core] " Ross Burton
@ 2026-02-16 20:51     ` Tondo, Stefano
  0 siblings, 0 replies; 7+ messages in thread
From: Tondo, Stefano @ 2026-02-16 20:51 UTC (permalink / raw)
  To: Ross Burton, stondo@gmail.com
  Cc: openembedded-core@lists.openembedded.org, Marko, Peter,
	Freihofer, Adrian, Joshua Watt

[-- Attachment #1: Type: text/plain, Size: 7767 bytes --]

From 4b9338d0083d1b79319a82e9e6858c553ea3111c Mon Sep 17 00:00:00 2001
From: Stefano Tondo <stefano.tondo.ext@siemens.com>
Date: Mon, 16 Feb 2026 21:40:14 +0100
Subject: [OE-core][PATCH] oeqa/selftest: Add test for SPDX_CONCLUDED_LICENSE
 behavior

Add a selftest for the SPDX_CONCLUDED_LICENSE variable (introduced in
commit bb21c6a429) to verify the conditional behavior doesn't change
or break, as requested during review.

The test verifies three scenarios:
1. Global SPDX_CONCLUDED_LICENSE creates a hasConcludedLicense
   relationship on the package
2. Package-specific override (SPDX_CONCLUDED_LICENSE:<pkg>) takes
   precedence over the global value
3. When unset, no hasConcludedLicense relationship is added

Signed-off-by: Stefano Tondo <stefano.tondo.ext@siemens.com>
---
 meta/lib/oeqa/selftest/cases/spdx.py | 112 +++++++++++++++++++++++++++
 1 file changed, 112 insertions(+)

diff --git a/meta/lib/oeqa/selftest/cases/spdx.py b/meta/lib/oeqa/selftest/cases/spdx.py
index 41ef52fce1..13386ef648 100644
--- a/meta/lib/oeqa/selftest/cases/spdx.py
+++ b/meta/lib/oeqa/selftest/cases/spdx.py
@@ -146,6 +146,118 @@ class SPDX30Check(SPDX3CheckBase, OESelftestTestCase):
             "{DEPLOY_DIR_SPDX}/{MACHINE_ARCH}/packages/package-base-files.spdx.json",
         )

+    def test_concluded_license(self):
+        """
+        Test that SPDX_CONCLUDED_LICENSE creates a hasConcludedLicense
+        relationship with correct conditional behavior:
+
+        1. When SPDX_CONCLUDED_LICENSE is set, a hasConcludedLicense
+           relationship is added to the package
+        2. Package-specific overrides (SPDX_CONCLUDED_LICENSE:<pkg>)
+           take precedence over the global value
+        3. When unset, no hasConcludedLicense relationship is present
+        """
+        # Test 1: Verify hasConcludedLicense is created when variable is set
+        objset = self.check_recipe_spdx(
+            "base-files",
+            "{DEPLOY_DIR_SPDX}/{MACHINE_ARCH}/packages/package-base-files.spdx.json",
+            extraconf="""\
+                SPDX_CONCLUDED_LICENSE = "MIT"
+                """,
+        )
+
+        # Find base-files package
+        for pkg in objset.foreach_type(oe.spdx30.software_Package):
+            if "base-files" in pkg.name:
+                break
+        else:
+            self.assertTrue(False, "Unable to find base-files package")
+
+        # Verify hasConcludedLicense relationship exists
+        for rel in objset.foreach_type(oe.spdx30.Relationship):
+            if (
+                rel.relationshipType
+                == oe.spdx30.RelationshipType.hasConcludedLicense
+                and any(pkg._id == f._id for f in rel.from_)
+            ):
+                self.assertGreater(
+                    len(rel.to),
+                    0,
+                    "hasConcludedLicense should reference a license",
+                )
+                break
+        else:
+            self.assertTrue(
+                False,
+                "SPDX_CONCLUDED_LICENSE should create hasConcludedLicense relationship",
+            )
+
+        # Test 2: Verify package-specific override takes precedence
+        objset = self.check_recipe_spdx(
+            "base-files",
+            "{DEPLOY_DIR_SPDX}/{MACHINE_ARCH}/packages/package-base-files.spdx.json",
+            extraconf="""\
+                SPDX_CONCLUDED_LICENSE = "GPL-2.0-only"
+                SPDX_CONCLUDED_LICENSE:base-files = "BSD-3-Clause"
+                """,
+        )
+
+        for pkg in objset.foreach_type(oe.spdx30.software_Package):
+            if "base-files" in pkg.name:
+                break
+        else:
+            self.assertTrue(False, "Unable to find base-files package")
+
+        for rel in objset.foreach_type(oe.spdx30.Relationship):
+            if (
+                rel.relationshipType
+                == oe.spdx30.RelationshipType.hasConcludedLicense
+                and any(pkg._id == f._id for f in rel.from_)
+            ):
+                # Verify the concluded license is the package-specific one
+                for to_id in rel.to:
+                    for lic in objset.foreach_type(
+                        oe.spdx30.simplelicensing_AnyLicenseInfo
+                    ):
+                        if lic._id == to_id._id and hasattr(
+                            lic, "simplelicensing_licenseExpression"
+                        ):
+                            self.assertIn(
+                                "BSD-3-Clause",
+                                lic.simplelicensing_licenseExpression,
+                                "Package-specific override should take precedence",
+                            )
+                            break
+                break
+        else:
+            self.assertTrue(
+                False,
+                "Package-specific SPDX_CONCLUDED_LICENSE should create relationship",
+            )
+
+        # Test 3: Verify no hasConcludedLicense when variable is unset
+        objset = self.check_recipe_spdx(
+            "base-files",
+            "{DEPLOY_DIR_SPDX}/{MACHINE_ARCH}/packages/package-base-files.spdx.json",
+        )
+
+        for pkg in objset.foreach_type(oe.spdx30.software_Package):
+            if "base-files" in pkg.name:
+                break
+        else:
+            self.assertTrue(False, "Unable to find base-files package")
+
+        for rel in objset.foreach_type(oe.spdx30.Relationship):
+            if (
+                rel.relationshipType
+                == oe.spdx30.RelationshipType.hasConcludedLicense
+                and any(pkg._id == f._id for f in rel.from_)
+            ):
+                self.assertTrue(
+                    False,
+                    "No hasConcludedLicense should exist when SPDX_CONCLUDED_LICENSE is unset",
+                )
+
     def test_gcc_include_source(self):
         objset = self.check_recipe_spdx(
             "gcc",
--
2.53.0
________________________________
From: Ross Burton <Ross.Burton@arm.com>
Sent: Tuesday, February 10, 2026 13:19
To: stondo@gmail.com <stondo@gmail.com>
Cc: openembedded-core@lists.openembedded.org <openembedded-core@lists.openembedded.org>; Tondo, Stefano (ext) (SI B PRO AUT PD ZUG SW 2) <stefano.tondo.ext@siemens.com>; Marko, Peter (FT D EU SK BFS1) <Peter.Marko@siemens.com>; Freihofer, Adrian (SI B PRO TI EAC CCP) <adrian.freihofer@siemens.com>; Joshua Watt <JPEWhacker@gmail.com>
Subject: Re: [OE-core] [PATCH v4] spdx30_tasks: Add concluded license support with SPDX_CONCLUDED_LICENSE

Hi Stefano,

> On 7 Jan 2026, at 18:15, Stefano Tondo via lists.openembedded.org <stondo=gmail.com@lists.openembedded.org> wrote:
> 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
>
> The hasConcludedLicense relationship is ONLY added to the SBOM when
> SPDX_CONCLUDED_LICENSE is explicitly set. When unset or empty, no
> concluded license is included in the SBOM, correctly indicating that
> no license analysis was performed (per SPDX semantics).

Could you add a test case to oeqa to this, so that we know the conditional behaviour doesn’t change or break?

Thanks
Ross

[-- Attachment #2: Type: text/html, Size: 34585 bytes --]

^ permalink raw reply related	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-02-16 21:52 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-06 11:25 [PATCH v2] spdx30_tasks: Add concluded license support with SPDX_CONCLUDED_LICENSE stondo
2026-01-06 22:03 ` Joshua Watt
2026-01-07  9:17 ` [PATCH v3] " stondo
2026-01-07 18:15 ` [PATCH v4] " stondo
2026-01-07 19:40   ` Joshua Watt
2026-02-10 12:19   ` [OE-core] " Ross Burton
2026-02-16 20:51     ` Tondo, Stefano

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox