On Tue, Jan 6, 2026 at 4:26 AM <[email protected]> wrote:
>
> From: Stefano Tondo <[email protected]>
>
> 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 <[email protected]>
> ---
> 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
>
-=-=-=-=-=-=-=-=-=-=-=-
Links: You receive all messages sent to this group.
View/Reply Online (#228934):
https://lists.openembedded.org/g/openembedded-core/message/228934
Mute This Topic: https://lists.openembedded.org/mt/117103414/21656
Group Owner: [email protected]
Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub
[[email protected]]
-=-=-=-=-=-=-=-=-=-=-=-