Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions evaluator/src/main/kotlin/OrtResultRule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ open class OrtResultRule(
severity = severity,
pkgId = null,
license = null,
licenseSource = null,
licenseSources = emptySet(),
message = message,
howToFix = howToFix
)
Expand All @@ -52,7 +52,7 @@ open class OrtResultRule(
hint(
pkgId = null,
license = null,
licenseSource = null,
licenseSources = emptySet(),
message = message,
howToFix = howToFix
)
Expand All @@ -61,7 +61,7 @@ open class OrtResultRule(
warning(
pkgId = null,
license = null,
licenseSource = null,
licenseSources = emptySet(),
message = message,
howToFix = howToFix
)
Expand All @@ -70,7 +70,7 @@ open class OrtResultRule(
error(
pkgId = null,
license = null,
licenseSource = null,
licenseSources = emptySet(),
message = message,
howToFix = howToFix
)
Expand Down
68 changes: 51 additions & 17 deletions evaluator/src/main/kotlin/PackageRule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -202,33 +202,44 @@ 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(
Copy link
Member

Choose a reason for hiding this comment

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

Commit message nit: "for all sources"

name: String,
licenseView: LicenseView,
separateEvaluationPerSource: Boolean = true,
Copy link
Member

Choose a reason for hiding this comment

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

Do we even need this configurability? Why not make the new the default, and require rule users to manually create multiple violations from this rule (one violation per source) if needed?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is needed for:

  1. Backwards compatibility
  2. I believe separate violations per source to be very valuable and should be still supported in
    the same simple way as before.

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, licenseSource).apply(block)
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) =
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].
Expand All @@ -242,10 +253,31 @@ 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].
Copy link
Member

@sschuberth sschuberth Nov 28, 2025

Choose a reason for hiding this comment

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

Nit: I'm unsure about the "to evaluate the license for" part. No one forces the rule to actually look at / evaluate the license sources. I think it should rather say "The sources where this particular license was found".

Copy link
Member Author

Choose a reason for hiding this comment

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

I believe "The sources where this particular license was found" is mis-leading:

  1. The above resolvedLicense.source is actually the set of sources
    where the license was found.
  2. And licenseSources in fact are the
    sources which shall be considered by the license rule.

Does this make more sense now?

Copy link
Member

Choose a reason for hiding this comment

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

I'm still confused about the difference / different purpose of resolvedLicense.sources and licenseSources. Probably that distinction is not new, but I became more aware of the similarities now that both are sets.

So, why / under what circumstances can licenseSources be different from / a subset of resolvedLicense.sources?

*/
val licenseSource: LicenseSource
val licenseSources: Set<LicenseSource>
) : 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."
}
}

/** Backwards compatibility */
Copy link
Member

Choose a reason for hiding this comment

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

Personally, I would be fine with omitting this and having a breaking change, and clearly documenting the migration path as part of the release notes.

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you mean only this property, or the entire commit?

(Would you also be ok, with marking it as deprected now, and removing it later? This would allow a smooth transition outside of the critical path of upgrading ORT.)

Copy link
Member

Choose a reason for hiding this comment

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

Do you mean only this property, or the entire commit?

I was referring to the entire commit.

Would you also be ok, with marking it as deprected now, and removing it later?

Yes. I was just trying to say that this additional work would not be required from my side.

Copy link
Member Author

Choose a reason for hiding this comment

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

I ended up not deprecating this property, because the separate per source evaluation / single source per license rule or violation is still a valid (and in my opinion even the preferred) use case. In that case using that property is a bit simpler.

Copy link
Member

Choose a reason for hiding this comment

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

Agree with @fviernau we don't want to get rid of per license source evaluator per rule we just want to offer ORT user more power/flexibility to define rules that match their organization's open source policies.

@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].
*/
Expand All @@ -257,11 +289,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
Expand All @@ -271,7 +303,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
}

/**
Expand All @@ -290,22 +323,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)
}
}
67 changes: 58 additions & 9 deletions evaluator/src/main/kotlin/Rule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,14 @@ 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(
severity: Severity,
pkgId: Identifier?,
license: SpdxSingleLicenseExpression?,
licenseSource: LicenseSource?,
licenseSources: Set<LicenseSource>,
message: String,
howToFix: String
) {
Expand All @@ -139,7 +139,7 @@ abstract class Rule(
rule = name,
pkg = pkgId,
license = license,
licenseSource = licenseSource,
licenseSources = licenseSources,
message = message,
howToFix = howToFix
)
Expand All @@ -151,32 +151,32 @@ abstract class Rule(
fun hint(
pkgId: Identifier?,
license: SpdxSingleLicenseExpression?,
licenseSource: LicenseSource?,
licenseSources: Set<LicenseSource>,
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].
*/
fun warning(
pkgId: Identifier?,
license: SpdxSingleLicenseExpression?,
licenseSource: LicenseSource?,
licenseSources: Set<LicenseSource>,
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].
*/
fun error(
pkgId: Identifier?,
license: SpdxSingleLicenseExpression?,
licenseSource: LicenseSource?,
licenseSources: Set<LicenseSource>,
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.
Expand All @@ -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
) = warning(pkgId, license, setOfNotNull(licenseSource), message, howToFix)
8 changes: 4 additions & 4 deletions evaluator/src/test/kotlin/EvaluatorTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion evaluator/src/test/kotlin/PackageRuleTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class PackageRuleTest : WordSpec() {
originalExpressions = setOf(ResolvedOriginalExpression(license, licenseSource)),
locations = emptySet()
),
licenseSource = licenseSource
licenseSources = setOf(licenseSource)
)

init {
Expand Down
Loading
Loading