From 9d44733a32efd4b850c501e737cd2141a52f7e35 Mon Sep 17 00:00:00 2001 From: Frank Viernau Date: Thu, 27 Nov 2025 21:17:26 +0100 Subject: [PATCH 1/4] refactor(evaluator)!: Turn `licenseSource` of violations to a `Set` Prepare for combining rule violations that stem from the same license and package, but from different licenses sources. Signed-off-by: Frank Viernau --- evaluator/src/main/kotlin/Rule.kt | 2 +- evaluator/src/test/kotlin/EvaluatorTest.kt | 8 ++++---- evaluator/src/test/kotlin/RuleTest.kt | 7 ++++--- model/src/main/kotlin/RuleViolation.kt | 4 ++-- model/src/test/kotlin/OrtResultTest.kt | 8 ++++---- .../test/kotlin/config/RuleViolationResolutionTest.kt | 2 +- .../evaluator/src/main/kotlin/EvaluateCommand.kt | 6 +----- ...d-model-reporter-test-deduplicate-expected-output.yml | 9 ++++++--- .../evaluated-model-reporter-test-expected-output.json | 6 +++--- .../evaluated-model-reporter-test-expected-output.yml | 9 ++++++--- .../src/funTest/resources/reporter-test-input.yml | 6 +++--- .../src/main/kotlin/EvaluatedModelMapper.kt | 2 +- .../src/main/kotlin/EvaluatedRuleViolation.kt | 2 +- .../src/funTest/resources/reporter-test-input.yml | 6 +++--- .../src/funTest/resources/reporter-test-input.yml | 6 +++--- .../static-html/src/main/kotlin/StaticHtmlReporter.kt | 2 +- 16 files changed, 44 insertions(+), 41 deletions(-) diff --git a/evaluator/src/main/kotlin/Rule.kt b/evaluator/src/main/kotlin/Rule.kt index 0fa6ed9306ec3..2325e29e72a71 100644 --- a/evaluator/src/main/kotlin/Rule.kt +++ b/evaluator/src/main/kotlin/Rule.kt @@ -139,7 +139,7 @@ abstract class Rule( rule = name, pkg = pkgId, license = license, - licenseSource = licenseSource, + licenseSources = setOfNotNull(licenseSource), message = message, howToFix = howToFix ) diff --git a/evaluator/src/test/kotlin/EvaluatorTest.kt b/evaluator/src/test/kotlin/EvaluatorTest.kt index 5ef930b01c8f0..6730ba3278061 100644 --- a/evaluator/src/test/kotlin/EvaluatorTest.kt +++ b/evaluator/src/test/kotlin/EvaluatorTest.kt @@ -78,7 +78,7 @@ class EvaluatorTest : WordSpec({ rule = "rule 1", pkg = Identifier("type:namespace:name:1.0"), license = SpdxLicenseIdExpression("license-1"), - licenseSource = LicenseSource.DETECTED, + licenseSources = setOf(LicenseSource.DETECTED), severity = Severity.ERROR, message = "message 1", howToFix = "how to fix 1" @@ -88,7 +88,7 @@ class EvaluatorTest : WordSpec({ rule = "rule 2", pkg = Identifier("type:namespace:name:2.0"), license = SpdxLicenseIdExpression("license-2"), - licenseSource = LicenseSource.DECLARED, + licenseSources = setOf(LicenseSource.DECLARED), severity = Severity.WARNING, message = "message 2", howToFix = "how to fix 2" @@ -102,7 +102,7 @@ class EvaluatorTest : WordSpec({ rule shouldBe "rule 1" pkg shouldBe Identifier("type:namespace:name:1.0") license shouldBe "license-1".toSpdx() - licenseSource shouldBe LicenseSource.DETECTED + licenseSources should containExactlyInAnyOrder(LicenseSource.DETECTED) severity shouldBe Severity.ERROR message shouldBe "message 1" howToFix shouldBe "how to fix 1" @@ -112,7 +112,7 @@ class EvaluatorTest : WordSpec({ rule shouldBe "rule 2" pkg shouldBe Identifier("type:namespace:name:2.0") license shouldBe "license-2".toSpdx() - licenseSource shouldBe LicenseSource.DECLARED + licenseSources should containExactlyInAnyOrder(LicenseSource.DECLARED) severity shouldBe Severity.WARNING message shouldBe "message 2" howToFix shouldBe "how to fix 2" diff --git a/evaluator/src/test/kotlin/RuleTest.kt b/evaluator/src/test/kotlin/RuleTest.kt index b913531a9de71..bcdf0145bed59 100644 --- a/evaluator/src/test/kotlin/RuleTest.kt +++ b/evaluator/src/test/kotlin/RuleTest.kt @@ -20,6 +20,7 @@ package org.ossreviewtoolkit.evaluator import io.kotest.core.spec.style.WordSpec +import io.kotest.matchers.collections.containExactlyInAnyOrder import io.kotest.matchers.collections.haveSize import io.kotest.matchers.should import io.kotest.matchers.shouldBe @@ -55,7 +56,7 @@ class RuleTest : WordSpec() { violation.rule shouldBe rule.name violation.pkg shouldBe id violation.license shouldBe license - violation.licenseSource shouldBe licenseSource + violation.licenseSources should containExactlyInAnyOrder(licenseSource) violation.severity shouldBe Severity.HINT violation.message shouldBe message violation.howToFix shouldBe howToFix @@ -74,7 +75,7 @@ class RuleTest : WordSpec() { violation.rule shouldBe rule.name violation.pkg shouldBe id violation.license shouldBe license - violation.licenseSource shouldBe licenseSource + violation.licenseSources should containExactlyInAnyOrder(licenseSource) violation.severity shouldBe Severity.WARNING violation.message shouldBe message violation.howToFix shouldBe howToFix @@ -93,7 +94,7 @@ class RuleTest : WordSpec() { violation.rule shouldBe rule.name violation.pkg shouldBe id violation.license shouldBe license - violation.licenseSource shouldBe licenseSource + violation.licenseSources should containExactlyInAnyOrder(licenseSource) violation.severity shouldBe Severity.ERROR violation.message shouldBe message violation.howToFix shouldBe howToFix diff --git a/model/src/main/kotlin/RuleViolation.kt b/model/src/main/kotlin/RuleViolation.kt index 6883fdf291d04..ea42862b54a31 100644 --- a/model/src/main/kotlin/RuleViolation.kt +++ b/model/src/main/kotlin/RuleViolation.kt @@ -38,9 +38,9 @@ data class RuleViolation( val license: SpdxSingleLicenseExpression?, /** - * The [source][LicenseSource] of the [license]. Can be null if the rule does not work on licenses. + * The [sources][licenseSources] of the [license]. Can be empty if the rule does not work on licenses. */ - val licenseSource: LicenseSource?, + val licenseSources: Set, /** * The severity of the rule violation. diff --git a/model/src/test/kotlin/OrtResultTest.kt b/model/src/test/kotlin/OrtResultTest.kt index e374c807094b1..9b9d5a8fd375f 100644 --- a/model/src/test/kotlin/OrtResultTest.kt +++ b/model/src/test/kotlin/OrtResultTest.kt @@ -512,7 +512,7 @@ class OrtResultTest : WordSpec({ rule = "rule id", pkg = Identifier("Maven", "org.ossreviewtoolkit", "resolved-violation", "0.8.15"), license = null, - licenseSource = null, + licenseSources = emptySet(), severity = Severity.HINT, message = "Rule violation message to resolve", howToFix = "" @@ -534,7 +534,7 @@ class OrtResultTest : WordSpec({ rule = "Resolved rule violation", pkg = Identifier("Maven", "org.ossreviewtoolkit", "resolved-violation", "0.8.15"), license = null, - licenseSource = null, + licenseSources = emptySet(), severity = Severity.ERROR, message = "Rule violation message to resolve", howToFix = "" @@ -543,7 +543,7 @@ class OrtResultTest : WordSpec({ rule = "Rule violation without resolution", pkg = Identifier("Maven", "com.example", "package-without-resolution", "1.0.0"), license = null, - licenseSource = null, + licenseSources = emptySet(), severity = Severity.WARNING, message = "Message without any resolution", howToFix = "" @@ -552,7 +552,7 @@ class OrtResultTest : WordSpec({ rule = "Rule violation below minSeverity", pkg = Identifier("Maven", "com.example", "violation-below-threshold", "3.14"), license = null, - licenseSource = null, + licenseSources = emptySet(), severity = Severity.HINT, message = "Message without any resolution", howToFix = "" diff --git a/model/src/test/kotlin/config/RuleViolationResolutionTest.kt b/model/src/test/kotlin/config/RuleViolationResolutionTest.kt index 5b20b6736c12f..ade437ecb30a7 100644 --- a/model/src/test/kotlin/config/RuleViolationResolutionTest.kt +++ b/model/src/test/kotlin/config/RuleViolationResolutionTest.kt @@ -66,7 +66,7 @@ private fun ruleViolation(message: String) = rule = "", pkg = null, license = null, - licenseSource = null, + licenseSources = emptySet(), severity = Severity.ERROR, message = message, howToFix = "" diff --git a/plugins/commands/evaluator/src/main/kotlin/EvaluateCommand.kt b/plugins/commands/evaluator/src/main/kotlin/EvaluateCommand.kt index 3c82600604890..224dafbea4d74 100644 --- a/plugins/commands/evaluator/src/main/kotlin/EvaluateCommand.kt +++ b/plugins/commands/evaluator/src/main/kotlin/EvaluateCommand.kt @@ -375,11 +375,7 @@ private fun RuleViolation.format() = license?.let { license -> append(license) - licenseSource?.let { source -> - append(" (") - append(source) - append(")") - } + licenseSources.takeUnless { it.isEmpty() }?.joinToString(prefix = "(", postfix = ")") { it.toString() } append(" - ") } diff --git a/plugins/reporters/evaluated-model/src/funTest/resources/evaluated-model-reporter-test-deduplicate-expected-output.yml b/plugins/reporters/evaluated-model/src/funTest/resources/evaluated-model-reporter-test-deduplicate-expected-output.yml index 32f1672d38deb..dfd515f5c305b 100644 --- a/plugins/reporters/evaluated-model/src/funTest/resources/evaluated-model-reporter-test-deduplicate-expected-output.yml +++ b/plugins/reporters/evaluated-model/src/funTest/resources/evaluated-model-reporter-test-deduplicate-expected-output.yml @@ -1070,7 +1070,8 @@ rule_violations: rule: "rule 1" pkg: 3 license: 9 - license_source: "DETECTED" + license_sources: + - "DETECTED" severity: "ERROR" message: "EPL-1.0 error" how_to_fix: "* *Step 1*\n* __Step 2__\n* ***Step 3***\n```\nSome long text verify\ @@ -1079,7 +1080,8 @@ rule_violations: rule: "rule 2" pkg: 2 license: 3 - license_source: "DECLARED" + license_sources: + - "DECLARED" severity: "HINT" message: "Apache-2.0 hint" how_to_fix: "* *Step 1*\n* __Step 2__\n* ***Step 3***\n```\nSome long text verify\ @@ -1090,7 +1092,8 @@ rule_violations: rule: "rule 3" pkg: 8 license: 16 - license_source: "CONCLUDED" + license_sources: + - "CONCLUDED" severity: "WARNING" message: "BSD-3-Clause warning" how_to_fix: "* *Step 1*\n* __Step 2__\n* ***Step 3***\n```\nSome long text verify\ diff --git a/plugins/reporters/evaluated-model/src/funTest/resources/evaluated-model-reporter-test-expected-output.json b/plugins/reporters/evaluated-model/src/funTest/resources/evaluated-model-reporter-test-expected-output.json index 56e4e607635be..3d7eadd3954f5 100644 --- a/plugins/reporters/evaluated-model/src/funTest/resources/evaluated-model-reporter-test-expected-output.json +++ b/plugins/reporters/evaluated-model/src/funTest/resources/evaluated-model-reporter-test-expected-output.json @@ -1157,7 +1157,7 @@ "rule" : "rule 1", "pkg" : 3, "license" : 9, - "license_source" : "DETECTED", + "license_sources" : [ "DETECTED" ], "severity" : "ERROR", "message" : "EPL-1.0 error", "how_to_fix" : "* *Step 1*\n* __Step 2__\n* ***Step 3***\n```\nSome long text verify that overflow:scroll is working as expected.\n```" @@ -1166,7 +1166,7 @@ "rule" : "rule 2", "pkg" : 2, "license" : 3, - "license_source" : "DECLARED", + "license_sources" : [ "DECLARED" ], "severity" : "HINT", "message" : "Apache-2.0 hint", "how_to_fix" : "* *Step 1*\n* __Step 2__\n* ***Step 3***\n```\nSome long text verify that overflow:scroll is working as expected.\n```", @@ -1176,7 +1176,7 @@ "rule" : "rule 3", "pkg" : 8, "license" : 16, - "license_source" : "CONCLUDED", + "license_sources" : [ "CONCLUDED" ], "severity" : "WARNING", "message" : "BSD-3-Clause warning", "how_to_fix" : "* *Step 1*\n* __Step 2__\n* ***Step 3***\n```\nSome long text verify that overflow:scroll is working as expected.\n```" diff --git a/plugins/reporters/evaluated-model/src/funTest/resources/evaluated-model-reporter-test-expected-output.yml b/plugins/reporters/evaluated-model/src/funTest/resources/evaluated-model-reporter-test-expected-output.yml index 32f1672d38deb..dfd515f5c305b 100644 --- a/plugins/reporters/evaluated-model/src/funTest/resources/evaluated-model-reporter-test-expected-output.yml +++ b/plugins/reporters/evaluated-model/src/funTest/resources/evaluated-model-reporter-test-expected-output.yml @@ -1070,7 +1070,8 @@ rule_violations: rule: "rule 1" pkg: 3 license: 9 - license_source: "DETECTED" + license_sources: + - "DETECTED" severity: "ERROR" message: "EPL-1.0 error" how_to_fix: "* *Step 1*\n* __Step 2__\n* ***Step 3***\n```\nSome long text verify\ @@ -1079,7 +1080,8 @@ rule_violations: rule: "rule 2" pkg: 2 license: 3 - license_source: "DECLARED" + license_sources: + - "DECLARED" severity: "HINT" message: "Apache-2.0 hint" how_to_fix: "* *Step 1*\n* __Step 2__\n* ***Step 3***\n```\nSome long text verify\ @@ -1090,7 +1092,8 @@ rule_violations: rule: "rule 3" pkg: 8 license: 16 - license_source: "CONCLUDED" + license_sources: + - "CONCLUDED" severity: "WARNING" message: "BSD-3-Clause warning" how_to_fix: "* *Step 1*\n* __Step 2__\n* ***Step 3***\n```\nSome long text verify\ diff --git a/plugins/reporters/evaluated-model/src/funTest/resources/reporter-test-input.yml b/plugins/reporters/evaluated-model/src/funTest/resources/reporter-test-input.yml index 120ffd0c7ee2b..5c8e17b0d4cc9 100644 --- a/plugins/reporters/evaluated-model/src/funTest/resources/reporter-test-input.yml +++ b/plugins/reporters/evaluated-model/src/funTest/resources/reporter-test-input.yml @@ -820,7 +820,7 @@ evaluator: - rule: "rule 1" pkg: "Ant:junit:junit:4.12" license: "EPL-1.0" - license_source: "DETECTED" + license_sources: ["DETECTED"] severity: "ERROR" message: "EPL-1.0 error" how_to_fix: "* *Step 1*\n* __Step 2__\n* ***Step 3***\n```\nSome long text verify\ @@ -828,7 +828,7 @@ evaluator: - rule: "rule 2" pkg: "Maven:org.apache.commons:commons-text:1.1" license: "Apache-2.0" - license_source: "DECLARED" + license_sources: ["DECLARED"] severity: "HINT" message: "Apache-2.0 hint" how_to_fix: "* *Step 1*\n* __Step 2__\n* ***Step 3***\n```\nSome long text verify\ @@ -836,7 +836,7 @@ evaluator: - rule: "rule 3" pkg: "Maven:org.hamcrest:hamcrest-core:1.3" license: "BSD-3-Clause" - license_source: "CONCLUDED" + license_sources: ["CONCLUDED"] severity: "WARNING" message: "BSD-3-Clause warning" how_to_fix: "* *Step 1*\n* __Step 2__\n* ***Step 3***\n```\nSome long text verify\ diff --git a/plugins/reporters/evaluated-model/src/main/kotlin/EvaluatedModelMapper.kt b/plugins/reporters/evaluated-model/src/main/kotlin/EvaluatedModelMapper.kt index 06cc775a95821..b0f8b9378d638 100644 --- a/plugins/reporters/evaluated-model/src/main/kotlin/EvaluatedModelMapper.kt +++ b/plugins/reporters/evaluated-model/src/main/kotlin/EvaluatedModelMapper.kt @@ -393,7 +393,7 @@ internal class EvaluatedModelMapper(private val input: ReporterInput) { rule = ruleViolation.rule, pkg = pkg, license = license, - licenseSource = ruleViolation.licenseSource, + licenseSources = ruleViolation.licenseSources, severity = ruleViolation.severity, message = ruleViolation.message, howToFix = ruleViolation.howToFix, diff --git a/plugins/reporters/evaluated-model/src/main/kotlin/EvaluatedRuleViolation.kt b/plugins/reporters/evaluated-model/src/main/kotlin/EvaluatedRuleViolation.kt index a6eae9be7fd19..d4a9fd553814d 100644 --- a/plugins/reporters/evaluated-model/src/main/kotlin/EvaluatedRuleViolation.kt +++ b/plugins/reporters/evaluated-model/src/main/kotlin/EvaluatedRuleViolation.kt @@ -36,7 +36,7 @@ data class EvaluatedRuleViolation( @JsonInclude(JsonInclude.Include.NON_NULL) val license: LicenseId?, @JsonInclude(JsonInclude.Include.NON_NULL) - val licenseSource: LicenseSource?, + val licenseSources: Set, val severity: Severity, @JsonInclude(JsonInclude.Include.NON_EMPTY) val message: String, diff --git a/plugins/reporters/opossum/src/funTest/resources/reporter-test-input.yml b/plugins/reporters/opossum/src/funTest/resources/reporter-test-input.yml index a8a381826d239..b9acaec21a04d 100644 --- a/plugins/reporters/opossum/src/funTest/resources/reporter-test-input.yml +++ b/plugins/reporters/opossum/src/funTest/resources/reporter-test-input.yml @@ -814,7 +814,7 @@ evaluator: - rule: "rule 1" pkg: "Ant:junit:junit:4.12" license: "EPL-1.0" - license_source: "DETECTED" + license_sources: ["DETECTED"] severity: "ERROR" message: "EPL-1.0 error" how_to_fix: "* *Step 1*\n* __Step 2__\n* ***Step 3***\n```\nSome long text verify\ @@ -822,7 +822,7 @@ evaluator: - rule: "rule 2" pkg: "Maven:org.apache.commons:commons-text:1.1" license: "Apache-2.0" - license_source: "DECLARED" + license_sources: ["DECLARED"] severity: "HINT" message: "Apache-2.0 hint" how_to_fix: "* *Step 1*\n* __Step 2__\n* ***Step 3***\n```\nSome long text verify\ @@ -830,7 +830,7 @@ evaluator: - rule: "rule 3" pkg: "Maven:org.hamcrest:hamcrest-core:1.3" license: "BSD-3-Clause" - license_source: "CONCLUDED" + license_sources: ["CONCLUDED"] severity: "WARNING" message: "BSD-3-Clause warning" how_to_fix: "* *Step 1*\n* __Step 2__\n* ***Step 3***\n```\nSome long text verify\ diff --git a/plugins/reporters/static-html/src/funTest/resources/reporter-test-input.yml b/plugins/reporters/static-html/src/funTest/resources/reporter-test-input.yml index 9383c8c118961..b282c64da35ce 100644 --- a/plugins/reporters/static-html/src/funTest/resources/reporter-test-input.yml +++ b/plugins/reporters/static-html/src/funTest/resources/reporter-test-input.yml @@ -817,7 +817,7 @@ evaluator: - rule: "rule 1" pkg: "Ant:junit:junit:4.12" license: "EPL-1.0" - license_source: "DETECTED" + license_sources: ["DETECTED"] severity: "ERROR" message: "EPL-1.0 error" how_to_fix: "* *Step 1*\n* __Step 2__\n* ***Step 3***\n```\nSome long text verify\ @@ -825,7 +825,7 @@ evaluator: - rule: "rule 2" pkg: "Maven:org.apache.commons:commons-text:1.1" license: "Apache-2.0" - license_source: "DECLARED" + license_sources: ["DECLARED"] severity: "HINT" message: "Apache-2.0 hint" how_to_fix: "* *Step 1*\n* __Step 2__\n* ***Step 3***\n```\nSome long text verify\ @@ -833,7 +833,7 @@ evaluator: - rule: "rule 3" pkg: "Maven:org.hamcrest:hamcrest-core:1.3" license: "BSD-3-Clause" - license_source: "CONCLUDED" + license_sources: ["CONCLUDED"] severity: "WARNING" message: "BSD-3-Clause warning" how_to_fix: "* *Step 1*\n* __Step 2__\n* ***Step 3***\n```\nSome long text verify\ diff --git a/plugins/reporters/static-html/src/main/kotlin/StaticHtmlReporter.kt b/plugins/reporters/static-html/src/main/kotlin/StaticHtmlReporter.kt index 1b82207aeafc0..03f6cad30115b 100644 --- a/plugins/reporters/static-html/src/main/kotlin/StaticHtmlReporter.kt +++ b/plugins/reporters/static-html/src/main/kotlin/StaticHtmlReporter.kt @@ -320,7 +320,7 @@ class StaticHtmlReporter(override val descriptor: PluginDescriptor = StaticHtmlR td { +(ruleViolation.violation.pkg?.toCoordinates() ?: "-") } td { +if (ruleViolation.violation.license != null) { - "${ruleViolation.violation.licenseSource}: ${ruleViolation.violation.license}" + "${ruleViolation.violation.licenseSources.joinToString()}: ${ruleViolation.violation.license}" } else { "-" } From 2110a1c335ffdde039d8298074762cc46fed457c Mon Sep 17 00:00:00 2001 From: Frank Viernau Date: Thu, 27 Nov 2025 21:43:39 +0100 Subject: [PATCH 2/4] refactor(evaluator)!: Allow multiple license sources in a `LicenseRule` Prepare for an upcoming change. Signed-off-by: Frank Viernau --- evaluator/src/main/kotlin/OrtResultRule.kt | 8 ++-- evaluator/src/main/kotlin/PackageRule.kt | 41 +++++++++++++------- evaluator/src/main/kotlin/Rule.kt | 16 ++++---- evaluator/src/test/kotlin/PackageRuleTest.kt | 2 +- evaluator/src/test/kotlin/RuleTest.kt | 14 +++---- 5 files changed, 47 insertions(+), 34 deletions(-) diff --git a/evaluator/src/main/kotlin/OrtResultRule.kt b/evaluator/src/main/kotlin/OrtResultRule.kt index bddef827f2f45..51ac6301f8f70 100644 --- a/evaluator/src/main/kotlin/OrtResultRule.kt +++ b/evaluator/src/main/kotlin/OrtResultRule.kt @@ -43,7 +43,7 @@ open class OrtResultRule( severity = severity, pkgId = null, license = null, - licenseSource = null, + licenseSources = emptySet(), message = message, howToFix = howToFix ) @@ -52,7 +52,7 @@ open class OrtResultRule( hint( pkgId = null, license = null, - licenseSource = null, + licenseSources = emptySet(), message = message, howToFix = howToFix ) @@ -61,7 +61,7 @@ open class OrtResultRule( warning( pkgId = null, license = null, - licenseSource = null, + licenseSources = emptySet(), message = message, howToFix = howToFix ) @@ -70,7 +70,7 @@ open class OrtResultRule( error( pkgId = null, license = null, - licenseSource = null, + licenseSources = emptySet(), message = message, howToFix = howToFix ) diff --git a/evaluator/src/main/kotlin/PackageRule.kt b/evaluator/src/main/kotlin/PackageRule.kt index be71da3029451..f54aedfd50970 100644 --- a/evaluator/src/main/kotlin/PackageRule.kt +++ b/evaluator/src/main/kotlin/PackageRule.kt @@ -207,28 +207,28 @@ open class PackageRule( .applyChoices(ruleSet.ortResult.getPackageLicenseChoices(pkg.metadata.id), licenseView) .applyChoices(ruleSet.ortResult.getRepositoryLicenseChoices(), licenseView).forEach { resolvedLicense -> resolvedLicense.sources.forEach { licenseSource -> - licenseRules += LicenseRule(name, resolvedLicense, licenseSource).apply(block) + licenseRules += LicenseRule(name, resolvedLicense, setOf(licenseSource)).apply(block) } } } fun issue(severity: Severity, message: String, howToFix: String) = - issue(severity, pkg.metadata.id, null, null, message, howToFix) + issue(severity, pkg.metadata.id, null, emptySet(), message, howToFix) /** * Add a [hint][Severity.HINT] to the list of [violations]. */ - fun hint(message: String, howToFix: String) = hint(pkg.metadata.id, null, null, message, howToFix) + fun hint(message: String, howToFix: String) = hint(pkg.metadata.id, null, emptySet(), message, howToFix) /** * Add a [warning][Severity.WARNING] to the list of [violations]. */ - fun warning(message: String, howToFix: String) = warning(pkg.metadata.id, null, null, message, howToFix) + fun warning(message: String, howToFix: String) = warning(pkg.metadata.id, null, emptySet(), message, howToFix) /** * Add an [error][Severity.ERROR] to the list of [violations]. */ - fun error(message: String, howToFix: String) = error(pkg.metadata.id, null, null, message, howToFix) + fun error(message: String, howToFix: String) = error(pkg.metadata.id, null, emptySet(), message, howToFix) /** * A [Rule] to check a single license of the [package][pkg]. @@ -242,10 +242,21 @@ open class PackageRule( val resolvedLicense: ResolvedLicense, /** - * The source of the license. + * The license sources to evaluate the license for. Must not be empty and contained in the [resolvedLicense]. */ - val licenseSource: LicenseSource + val licenseSources: Set ) : Rule(ruleSet, name) { + init { + require(licenseSources.isNotEmpty()) { + "The given license sources must not be empty." + } + + val invalidLicenseSources = (licenseSources - resolvedLicense.sources) + require(invalidLicenseSources.isEmpty()) { + "The license sources $invalidLicenseSources are not part of the resolved license." + } + } + /** * A shortcut for the [license][ResolvedLicense.license] in [resolvedLicense]. */ @@ -257,11 +268,11 @@ open class PackageRule( */ fun pkg() = pkg - override val description = "\tEvaluating license rule '$name' for $licenseSource license " + + override val description = "\tEvaluating license rule '$name' for $licenseSources license " + "'${resolvedLicense.license}'." override fun issueSource() = - "$name - ${pkg.metadata.id.toCoordinates()} - ${resolvedLicense.license} ($licenseSource)" + "$name - ${pkg.metadata.id.toCoordinates()} - ${resolvedLicense.license} ($licenseSources})" /** * A [RuleMatcher] that checks if a [detected][LicenseSource.DETECTED] license is @@ -271,7 +282,8 @@ open class PackageRule( object : RuleMatcher { override val description = "isDetectedExcluded($license)" - override fun matches() = licenseSource == LicenseSource.DETECTED && resolvedLicense.isDetectedExcluded + override fun matches() = + licenseSources.singleOrNull() == LicenseSource.DETECTED && resolvedLicense.isDetectedExcluded } /** @@ -290,22 +302,23 @@ open class PackageRule( } fun issue(severity: Severity, message: String, howToFix: String) = - issue(severity, pkg.metadata.id, license, licenseSource, message, howToFix) + issue(severity, pkg.metadata.id, license, licenseSources, message, howToFix) /** * Add a [hint][Severity.HINT] to the list of [violations]. */ - fun hint(message: String, howToFix: String) = hint(pkg.metadata.id, license, licenseSource, message, howToFix) + fun hint(message: String, howToFix: String) = hint(pkg.metadata.id, license, licenseSources, message, howToFix) /** * Add a [warning][Severity.WARNING] to the list of [violations]. */ fun warning(message: String, howToFix: String) = - warning(pkg.metadata.id, license, licenseSource, message, howToFix) + warning(pkg.metadata.id, license, licenseSources, message, howToFix) /** * Add an [error][Severity.ERROR] to the list of [violations]. */ - fun error(message: String, howToFix: String) = error(pkg.metadata.id, license, licenseSource, message, howToFix) + fun error(message: String, howToFix: String) = + error(pkg.metadata.id, license, licenseSources, message, howToFix) } } diff --git a/evaluator/src/main/kotlin/Rule.kt b/evaluator/src/main/kotlin/Rule.kt index 2325e29e72a71..6443cfcea596b 100644 --- a/evaluator/src/main/kotlin/Rule.kt +++ b/evaluator/src/main/kotlin/Rule.kt @@ -130,7 +130,7 @@ abstract class Rule( severity: Severity, pkgId: Identifier?, license: SpdxSingleLicenseExpression?, - licenseSource: LicenseSource?, + licenseSources: Set, message: String, howToFix: String ) { @@ -139,7 +139,7 @@ abstract class Rule( rule = name, pkg = pkgId, license = license, - licenseSources = setOfNotNull(licenseSource), + licenseSources = licenseSources, message = message, howToFix = howToFix ) @@ -151,10 +151,10 @@ abstract class Rule( fun hint( pkgId: Identifier?, license: SpdxSingleLicenseExpression?, - licenseSource: LicenseSource?, + licenseSources: Set, message: String, howToFix: String - ) = issue(Severity.HINT, pkgId, license, licenseSource, message, howToFix) + ) = issue(Severity.HINT, pkgId, license, licenseSources, message, howToFix) /** * Add a [warning][Severity.WARNING] to the list of [violations]. @@ -162,10 +162,10 @@ abstract class Rule( fun warning( pkgId: Identifier?, license: SpdxSingleLicenseExpression?, - licenseSource: LicenseSource?, + licenseSources: Set, message: String, howToFix: String - ) = issue(Severity.WARNING, pkgId, license, licenseSource, message, howToFix) + ) = issue(Severity.WARNING, pkgId, license, licenseSources, message, howToFix) /** * Add an [error][Severity.ERROR] to the list of [violations]. @@ -173,10 +173,10 @@ abstract class Rule( fun error( pkgId: Identifier?, license: SpdxSingleLicenseExpression?, - licenseSource: LicenseSource?, + licenseSources: Set, message: String, howToFix: String - ) = issue(Severity.ERROR, pkgId, license, licenseSource, message, howToFix) + ) = issue(Severity.ERROR, pkgId, license, licenseSources, message, howToFix) /** * A DSL helper class, providing convenience functions for adding [RuleMatcher]s to this rule. diff --git a/evaluator/src/test/kotlin/PackageRuleTest.kt b/evaluator/src/test/kotlin/PackageRuleTest.kt index 4fe93ae3399b6..d308a74095c92 100644 --- a/evaluator/src/test/kotlin/PackageRuleTest.kt +++ b/evaluator/src/test/kotlin/PackageRuleTest.kt @@ -56,7 +56,7 @@ class PackageRuleTest : WordSpec() { originalExpressions = setOf(ResolvedOriginalExpression(license, licenseSource)), locations = emptySet() ), - licenseSource = licenseSource + licenseSources = setOf(licenseSource) ) init { diff --git a/evaluator/src/test/kotlin/RuleTest.kt b/evaluator/src/test/kotlin/RuleTest.kt index bcdf0145bed59..93dc890e5cd33 100644 --- a/evaluator/src/test/kotlin/RuleTest.kt +++ b/evaluator/src/test/kotlin/RuleTest.kt @@ -34,7 +34,7 @@ class RuleTest : WordSpec() { private val ruleSet = ruleSet(ortResult) private val id = Identifier("type:namespace:name:version") private val license = SpdxLicenseIdExpression("license") - private val licenseSource = LicenseSource.DECLARED + private val licenseSources = setOf(LicenseSource.DECLARED) private val message = "violation message" private val howToFix = "how to fix" @@ -49,14 +49,14 @@ class RuleTest : WordSpec() { "add an issue with the correct severity" { val rule = createRule() - rule.hint(id, license, licenseSource, message, howToFix) + rule.hint(id, license, licenseSources, message, howToFix) rule.violations should haveSize(1) rule.violations.first().let { violation -> violation.rule shouldBe rule.name violation.pkg shouldBe id violation.license shouldBe license - violation.licenseSources should containExactlyInAnyOrder(licenseSource) + violation.licenseSources should containExactlyInAnyOrder(licenseSources) violation.severity shouldBe Severity.HINT violation.message shouldBe message violation.howToFix shouldBe howToFix @@ -68,14 +68,14 @@ class RuleTest : WordSpec() { "add an issue with the correct severity" { val rule = createRule() - rule.warning(id, license, licenseSource, message, howToFix) + rule.warning(id, license, licenseSources, message, howToFix) rule.violations should haveSize(1) rule.violations.first().let { violation -> violation.rule shouldBe rule.name violation.pkg shouldBe id violation.license shouldBe license - violation.licenseSources should containExactlyInAnyOrder(licenseSource) + violation.licenseSources should containExactlyInAnyOrder(licenseSources) violation.severity shouldBe Severity.WARNING violation.message shouldBe message violation.howToFix shouldBe howToFix @@ -87,14 +87,14 @@ class RuleTest : WordSpec() { "add an issue with the correct severity" { val rule = createRule() - rule.error(id, license, licenseSource, message, howToFix) + rule.error(id, license, licenseSources, message, howToFix) rule.violations should haveSize(1) rule.violations.first().let { violation -> violation.rule shouldBe rule.name violation.pkg shouldBe id violation.license shouldBe license - violation.licenseSources should containExactlyInAnyOrder(licenseSource) + violation.licenseSources should containExactlyInAnyOrder(licenseSources) violation.severity shouldBe Severity.ERROR violation.message shouldBe message violation.howToFix shouldBe howToFix From ba712c3325f971e00935e42e7a750c8a85c370ee Mon Sep 17 00:00:00 2001 From: Frank Viernau Date: Thu, 27 Nov 2025 21:50:47 +0100 Subject: [PATCH 3/4] feat(evaluator): Allow to evaluate license sources together While it is valuable to be able to evaluate the license rule seperately per license source, some users do desire to evaluate only once per license for all sources. Introduce a toggle, which allows for that while keeping the old behavior as default. Signed-off-by: Frank Viernau --- evaluator/src/main/kotlin/PackageRule.kt | 17 ++++++++++++++--- evaluator/src/main/kotlin/Rule.kt | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/evaluator/src/main/kotlin/PackageRule.kt b/evaluator/src/main/kotlin/PackageRule.kt index f54aedfd50970..2babf591ae78a 100644 --- a/evaluator/src/main/kotlin/PackageRule.kt +++ b/evaluator/src/main/kotlin/PackageRule.kt @@ -202,14 +202,25 @@ open class PackageRule( /** * A DSL function to configure a [LicenseRule] and add it to this rule. */ - fun licenseRule(name: String, licenseView: LicenseView, block: LicenseRule.() -> Unit) { - resolvedLicenseInfo.filter(licenseView, filterSources = true) + fun licenseRule( + name: String, + licenseView: LicenseView, + separateEvaluationPerSource: Boolean = true, + block: LicenseRule.() -> Unit + ) { + val effectiveResolvedLicenseInfo = resolvedLicenseInfo.filter(licenseView, filterSources = true) .applyChoices(ruleSet.ortResult.getPackageLicenseChoices(pkg.metadata.id), licenseView) - .applyChoices(ruleSet.ortResult.getRepositoryLicenseChoices(), licenseView).forEach { resolvedLicense -> + .applyChoices(ruleSet.ortResult.getRepositoryLicenseChoices(), licenseView) + + effectiveResolvedLicenseInfo.forEach { resolvedLicense -> + if (separateEvaluationPerSource) { resolvedLicense.sources.forEach { licenseSource -> licenseRules += LicenseRule(name, resolvedLicense, setOf(licenseSource)).apply(block) } + } else { + licenseRules += LicenseRule(name, resolvedLicense, resolvedLicense.sources).apply(block) } + } } fun issue(severity: Severity, message: String, howToFix: String) = diff --git a/evaluator/src/main/kotlin/Rule.kt b/evaluator/src/main/kotlin/Rule.kt index 6443cfcea596b..609f9bd3bc454 100644 --- a/evaluator/src/main/kotlin/Rule.kt +++ b/evaluator/src/main/kotlin/Rule.kt @@ -123,7 +123,7 @@ abstract class Rule( /** * Add an issue of the given [severity] for [pkgId] to the list of violations. Optionally, the offending [license] - * and its [source][licenseSource] can be specified. The [message] further explains the violation itself and + * and its [sources][licenseSources] can be specified. The [message] further explains the violation itself and * [howToFix] explains how it can be fixed. */ fun issue( From 145b88f34d122b280975c60dbbf43b89555a4671 Mon Sep 17 00:00:00 2001 From: Frank Viernau Date: Fri, 28 Nov 2025 10:26:34 +0100 Subject: [PATCH 4/4] feat(evaluator): Add API's for backwards compatibility Ensure the previous change is backwards compatible. Signed-off-by: Frank Viernau --- evaluator/src/main/kotlin/PackageRule.kt | 10 +++++ evaluator/src/main/kotlin/Rule.kt | 49 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/evaluator/src/main/kotlin/PackageRule.kt b/evaluator/src/main/kotlin/PackageRule.kt index 2babf591ae78a..cd131fcdd0ab3 100644 --- a/evaluator/src/main/kotlin/PackageRule.kt +++ b/evaluator/src/main/kotlin/PackageRule.kt @@ -268,6 +268,16 @@ open class PackageRule( } } + /** Backwards compatibility */ + @Suppress("unused") // This is intended to be used by rule implementations. + val licenseSource by lazy { + require(licenseSources.size == 1) { + "The license source is ambiguous. Please use the licenseSources property instead." + } + + licenseSources.single() + } + /** * A shortcut for the [license][ResolvedLicense.license] in [resolvedLicense]. */ diff --git a/evaluator/src/main/kotlin/Rule.kt b/evaluator/src/main/kotlin/Rule.kt index 609f9bd3bc454..645b4a49b214d 100644 --- a/evaluator/src/main/kotlin/Rule.kt +++ b/evaluator/src/main/kotlin/Rule.kt @@ -197,3 +197,52 @@ abstract class Rule( } } } + +/** + * Backward compatibility for [Rule.issue()]. + */ +@Suppress("unused") // This is intended to be used by rule implementations. +fun Rule.issue( + severity: Severity, + pkgId: Identifier?, + license: SpdxSingleLicenseExpression?, + licenseSource: LicenseSource?, + message: String, + howToFix: String +) = issue(severity, pkgId, license, setOfNotNull(licenseSource), message, howToFix) + +/** + * Backward compatibility for [Rule.hint()]. + */ +@Suppress("unused") // This is intended to be used by rule implementations. +fun Rule.hint( + pkgId: Identifier?, + license: SpdxSingleLicenseExpression?, + licenseSource: LicenseSource?, + message: String, + howToFix: String +) = hint(pkgId, license, setOfNotNull(licenseSource), message, howToFix) + +/** + * Backward compatibility for [Rule.warning()]. + */ +@Suppress("unused") // This is intended to be used by rule implementations. +fun Rule.warning( + pkgId: Identifier?, + license: SpdxSingleLicenseExpression?, + licenseSource: LicenseSource?, + message: String, + howToFix: String +) = warning(pkgId, license, setOfNotNull(licenseSource), message, howToFix) + +/** + * Backward compatibility for [Rule.error()]. + */ +@Suppress("unused") // This is intended to be used by rule implementations. +fun Rule.error( + pkgId: Identifier?, + license: SpdxSingleLicenseExpression?, + licenseSource: LicenseSource?, + message: String, + howToFix: String +) = error(pkgId, license, setOfNotNull(licenseSource), message, howToFix)