Skip to content

fix(spdx): Remap Yocto hasConcludedLicense to hasDeclaredLicense#11721

Open
willebra wants to merge 1 commit into
oss-review-toolkit:mainfrom
willebra:main
Open

fix(spdx): Remap Yocto hasConcludedLicense to hasDeclaredLicense#11721
willebra wants to merge 1 commit into
oss-review-toolkit:mainfrom
willebra:main

Conversation

@willebra

Copy link
Copy Markdown

Yocto Linux generates SPDX 3.0.1 files where the recipe's LICENSE variable is expressed via hasConcludedLicense relationships. This is semantically incorrect: the LICENSE variable is a declaration by the upstream author, not a conclusion from downstream license analysis. As a result, ORT leaves declaredLicenses empty for all packages and only populates concludedLicense, which affects policy evaluation and reporting.

The fix detects the Yocto pattern by the presence of build-scope LifecycleScopedRelationships (build_Build --[hasOutput/hasInput, scope=build]--> package). When detected, copy all HAS_CONCLUDED_LICENSE entries in the license map to HAS_DECLARED_LICENSE so that both install and source packages get declaredLicenses populated.

Context

Observed while analysing a real Yocto 5.0 build with ORT Server: all 2000+ packages reported empty declaredLicenses despite the SPDX file containing full license information. The updated expected output file shows packages now carry non-empty declared_licenses alongside concluded_license.

The correct long-term fix would be for Yocto/OE-Core to use hasDeclaredLicense instead of hasConcludedLicense for recipe
license declarations. A bug report against OE-Core is considered, but it may take time: OE-Core development is in Yocto 5.3 and 6.0, and this use case is in Yocto 5.0, which would require both a patch of Yocto 5.3 and thereafter backporting.

Yocto Linux generates SPDX 3.0.1 files where the recipe's LICENSE
variable is expressed via hasConcludedLicense relationships. This is
semantically incorrect: the LICENSE variable is a declaration by the
upstream author, not a conclusion from downstream license analysis. As a
result, ORT leaves declaredLicenses empty for all packages and only
populates concludedLicense, which affects policy evaluation and
reporting.

Detect the Yocto pattern by the presence of build-scope
LifecycleScopedRelationships (build_Build --[hasOutput/hasInput,
scope=build]--> package). When detected, copy all HAS_CONCLUDED_LICENSE
entries in the license map to HAS_DECLARED_LICENSE so that both install
and source packages get declaredLicenses populated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Martin von Willebrand <martin.vonwillebrand@doubleopen.io>
@willebra willebra requested a review from a team as a code owner April 20, 2026 19:01
// LifecycleScopedRelationships (build_Build --[hasOutput/hasInput, scope=build]--> pkg)
// and, when found, copy every HAS_CONCLUDED_LICENSE entry to HAS_DECLARED_LICENSE so that
// ORT populates declaredLicenses for both install and source packages.
val isYoctoDocument = relationships.filterIsInstance<LifecycleScopedRelationship>().any {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry, but I don't think adding this amount of code for a rather fragile heuristic to work around an upstream bug is feasible. I'd rather have a plugin option added that the user needs to explicitly enable, also because we don't know which Yocto version is going to fix this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, shouldn't the concluded license then in exchange be omitted? I.e. wouldn't the workaround be better done at the place where licenses are assigned to the ORT data model? See

declaredLicenses = declaredLicenses,
concludedLicense = if (concludedLicenses.size > 1) {
logger.warn { "Multiple concluded licenses found for package '$name', using only the first one." }
concludedLicenses.first().toSpdx()
} else {
concludedLicenses.singleOrNull()?.toSpdxOrNull()
},

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry, but I don't think adding this amount of code for a rather fragile heuristic to work around an upstream bug is feasible.

I see. It's your job to be the guardian of that, so I'll be happy to accommodate.

Also, shouldn't the concluded license then in exchange be omitted?
I thought of this also as one option, and I don't have an objection mapping it instaed of and not in addition to.

If I understand correctly, you would prefer an option that works independently of the Yocto (or any spdx source) version - no fragile heuristic - and instructs the recording of concluded licenses as declared licenses instead of concluded licenses.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, you would prefer an option that works independently of the Yocto (or any spdx source) version - no fragile heuristic - and instructs the recording of concluded licenses as declared licenses instead of concluded licenses.

Correct. As you correctly argue that "conclusion[s] [should] from downstream license analysis", I don't see how it makes sense to keep those concluded licenses if they're in fact declared ones.

However, I'm wondering whether we're overlooking something / doing something wrong during Yocto SPDX generation. (Mis-)using declared as concluded licenses seem like such a basic error that I cannot believe it's unintentional. I think we should reach out to the Yocto folks to clarify.

@willebra willebra Apr 23, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SPDX generation is very simple, you just trigger a switch from 0 to 1. Another switch to make it pretty. Can't think it could be done differently.

There seems to be quite a bit of new things in the latest dev branch: https://docs.yoctoproject.org/dev/dev-manual/sbom.html. None of these are in 5.0.x, and none of them address this.

I can reach out to Yocto, I did ask them about purls already. As you see from the docs, they have purls in the latest & greatest 5.3.x/dev, but I did not yet get any answer on whether that would be backported to 5.0, or whether they would want such a backport contribution to be made.

The thing is, even if they change the approach in this, it will be changed in dev, and then there is the backport question. I think this will take at least a month, if everything goes well, but could take far longer. So we should do this independently, I think. And ask on the Openembedded-core mailing list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants