From 69ff88e5fc11fa217bd93e2e6744e56942c8c8b3 Mon Sep 17 00:00:00 2001 From: Stephanie Neubauer Date: Mon, 22 Mar 2021 15:27:15 +0100 Subject: [PATCH 1/6] ResolvedLicenseConfiguration: Add repository license choices These are meant for license choices that apply to all packages in the repository. Signed-off-by: Stephanie Neubauer --- .../src/main/kotlin/config/LicenseChoices.kt | 20 +++++++++++- .../config/RepositoryConfigurationTest.kt | 31 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/model/src/main/kotlin/config/LicenseChoices.kt b/model/src/main/kotlin/config/LicenseChoices.kt index 421d3d077ae04..d58d5d3a7396e 100644 --- a/model/src/main/kotlin/config/LicenseChoices.kt +++ b/model/src/main/kotlin/config/LicenseChoices.kt @@ -24,16 +24,34 @@ import com.fasterxml.jackson.annotation.JsonInclude import org.ossreviewtoolkit.model.Identifier import org.ossreviewtoolkit.spdx.model.LicenseChoice +import org.ossreviewtoolkit.utils.ORT_REPO_CONFIG_FILENAME /** * The license choices configured for a repository. */ data class LicenseChoices( + /** + * [LicenseChoice]s that are applied to all packages in the repository. + * Since the [LicenseChoice] is applied to each package that offers this license as a choice, [LicenseChoice.given] + * can not be null. This helps only applying the choice to a wanted [LicenseChoice.given] as opposed to all + * licenses with that choice, which could lead to unwanted applied choices. + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + val repositoryLicenseChoices: List = emptyList(), + @JsonInclude(JsonInclude.Include.NON_EMPTY) val packageLicenseChoices: List = emptyList() ) { @JsonIgnore - fun isEmpty() = packageLicenseChoices.isEmpty() + fun isEmpty() = packageLicenseChoices.isEmpty() && repositoryLicenseChoices.isEmpty() + + init { + val choicesWithoutGiven = repositoryLicenseChoices.filter { it.given == null } + require(choicesWithoutGiven.isEmpty()) { + "LicenseChoices ${choicesWithoutGiven.joinToString()} defined in $ORT_REPO_CONFIG_FILENAME are missing " + + "the 'given' expression." + } + } } /** diff --git a/model/src/test/kotlin/config/RepositoryConfigurationTest.kt b/model/src/test/kotlin/config/RepositoryConfigurationTest.kt index 4ae08b7542547..cb38a84484821 100644 --- a/model/src/test/kotlin/config/RepositoryConfigurationTest.kt +++ b/model/src/test/kotlin/config/RepositoryConfigurationTest.kt @@ -19,12 +19,16 @@ package org.ossreviewtoolkit.model.config +import com.fasterxml.jackson.databind.exc.ValueInstantiationException import com.fasterxml.jackson.module.kotlin.readValue +import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.WordSpec import io.kotest.matchers.collections.haveSize import io.kotest.matchers.should import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.string.shouldNotContain import org.ossreviewtoolkit.model.Identifier import org.ossreviewtoolkit.model.yamlMapper @@ -45,6 +49,23 @@ class RepositoryConfigurationTest : WordSpec({ config.excludes.paths[0].matches("android/project1/build.gradle") shouldBe true } + "throw ValueInstantiationException if no given is supplied for repository_license_choices" { + val configuration = """ + license_choices: + repository_license_choices: + - given: Apache-2.0 or GPL-2.0-only + choice: GPL-2.0-only + - choice: MIT + """.trimIndent() + + val exception = shouldThrow { + yamlMapper.readValue(configuration) + } + + exception.message shouldContain "problem: LicenseChoices LicenseChoice(given=null, choice=MIT)" + exception.message shouldNotContain "GPL-2.0-only" + } + "be deserializable" { val configuration = """ excludes: @@ -70,6 +91,9 @@ class RepositoryConfigurationTest : WordSpec({ reason: "INEFFECTIVE_VULNERABILITY" comment: "vulnerability comment" license_choices: + repository_license_choices: + - given: Apache-2.0 or GPL-2.0-only + choice: GPL-2.0-only package_license_choices: - package_id: "Maven:com.example:lib:0.0.1" license_choices: @@ -120,6 +144,13 @@ class RepositoryConfigurationTest : WordSpec({ comment shouldBe "vulnerability comment" } + val repositoryLicenseChoices = repositoryConfiguration.licenseChoices.repositoryLicenseChoices + repositoryLicenseChoices should haveSize(1) + with(repositoryLicenseChoices.first()) { + given shouldBe "Apache-2.0 or GPL-2.0-only".toSpdx() + choice shouldBe "GPL-2.0-only".toSpdx() + } + val packageLicenseChoices = repositoryConfiguration.licenseChoices.packageLicenseChoices packageLicenseChoices should haveSize(1) with(packageLicenseChoices.first()) { From eafe2efecbb6f33e6949a6931e5da84c23cab160 Mon Sep 17 00:00:00 2001 From: Stephanie Neubauer Date: Tue, 23 Mar 2021 11:02:25 +0100 Subject: [PATCH 2/6] RuleSetTest: Fix rule matcher of license choice test Since for a license choice only the concluded is relevant, the license view is changed to use `ONLY_CONCLUDED`. Signed-off-by: Stephanie Neubauer --- evaluator/src/test/kotlin/RuleSetTest.kt | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/evaluator/src/test/kotlin/RuleSetTest.kt b/evaluator/src/test/kotlin/RuleSetTest.kt index 146f1fad244b4..4192b4312f394 100644 --- a/evaluator/src/test/kotlin/RuleSetTest.kt +++ b/evaluator/src/test/kotlin/RuleSetTest.kt @@ -1,5 +1,6 @@ /* * Copyright (C) 2017-2020 HERE Europe B.V. + * Copyright (C) 2021 Bosch.IO GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +25,7 @@ import io.kotest.matchers.collections.haveSize import io.kotest.matchers.should import org.ossreviewtoolkit.model.licenses.LicenseView +import org.ossreviewtoolkit.spdx.SpdxExpression import org.ossreviewtoolkit.spdx.toSpdx class RuleSetTest : WordSpec() { @@ -125,13 +127,9 @@ class RuleSetTest : WordSpec() { "add no license errors if license is removed by license choice" { val ruleSet = ruleSet(ortResult) { dependencyRule("test") { - licenseRule("test", LicenseView.CONCLUDED_OR_DECLARED_AND_DETECTED) { + licenseRule("test", LicenseView.ONLY_CONCLUDED) { require { - object : RuleMatcher { - override val description = "containsLicense(license)" - - override fun matches() = license == "LicenseRef-b".toSpdx() - } + +containsLicense("LicenseRef-b".toSpdx()) } error(errorMessage, howToFix) @@ -139,8 +137,15 @@ class RuleSetTest : WordSpec() { } } - ruleSet.violations should haveSize(5) + ruleSet.violations should haveSize(1) } } } + + private fun PackageRule.LicenseRule.containsLicense(expression: SpdxExpression) = + object : RuleMatcher { + override val description = "containsLicense(license)" + + override fun matches() = license == expression + } } From 779ceda50b1e7eac33b36a93ad24455884eeb958 Mon Sep 17 00:00:00 2001 From: Stephanie Neubauer Date: Tue, 23 Mar 2021 12:05:42 +0100 Subject: [PATCH 3/6] PackageRule: Apply repository license choices The order of applying the license choices from the different sources ensures that the more precise scope is applied first (=prioritized). Signed-off-by: Stephanie Neubauer --- evaluator/src/main/kotlin/PackageRule.kt | 3 ++- evaluator/src/test/kotlin/RuleSetTest.kt | 18 +++++++++++++++++- evaluator/src/test/kotlin/TestData.kt | 8 +++++++- model/src/main/kotlin/OrtResult.kt | 7 +++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/evaluator/src/main/kotlin/PackageRule.kt b/evaluator/src/main/kotlin/PackageRule.kt index 30ab16ec3cf86..3e4e7c735c05c 100644 --- a/evaluator/src/main/kotlin/PackageRule.kt +++ b/evaluator/src/main/kotlin/PackageRule.kt @@ -136,7 +136,8 @@ open class PackageRule( */ fun licenseRule(name: String, licenseView: LicenseView, block: LicenseRule.() -> Unit) { resolvedLicenseInfo.filter(licenseView, filterSources = true) - .applyChoices(ruleSet.ortResult.getLicenseChoices(pkg.id)).forEach { resolvedLicense -> + .applyChoices(ruleSet.ortResult.getLicenseChoices(pkg.id)) + .applyChoices(ruleSet.ortResult.getRepositoryLicenseChoices()).forEach { resolvedLicense -> resolvedLicense.sources.forEach { licenseSource -> licenseRules += LicenseRule(name, resolvedLicense, licenseSource).apply(block) } diff --git a/evaluator/src/test/kotlin/RuleSetTest.kt b/evaluator/src/test/kotlin/RuleSetTest.kt index 4192b4312f394..4dbdb6aed96fd 100644 --- a/evaluator/src/test/kotlin/RuleSetTest.kt +++ b/evaluator/src/test/kotlin/RuleSetTest.kt @@ -124,7 +124,7 @@ class RuleSetTest : WordSpec() { ruleSet.violations should haveSize(4) } - "add no license errors if license is removed by license choice" { + "add no license errors if license is removed by package license choice in the correct order" { val ruleSet = ruleSet(ortResult) { dependencyRule("test") { licenseRule("test", LicenseView.ONLY_CONCLUDED) { @@ -139,6 +139,22 @@ class RuleSetTest : WordSpec() { ruleSet.violations should haveSize(1) } + + "add no license errors if license is removed by repository license choice" { + val ruleSet = ruleSet(ortResult) { + dependencyRule("test") { + licenseRule("test", LicenseView.ONLY_CONCLUDED) { + require { + +containsLicense("LicenseRef-c".toSpdx()) + } + + error(errorMessage, howToFix) + } + } + } + + ruleSet.violations should haveSize(0) + } } } diff --git a/evaluator/src/test/kotlin/TestData.kt b/evaluator/src/test/kotlin/TestData.kt index fda59da804513..4eb7ea371cdca 100644 --- a/evaluator/src/test/kotlin/TestData.kt +++ b/evaluator/src/test/kotlin/TestData.kt @@ -54,7 +54,7 @@ import org.ossreviewtoolkit.spdx.toSpdx import org.ossreviewtoolkit.utils.DeclaredLicenseProcessor import org.ossreviewtoolkit.utils.Environment -val concludedLicense = "LicenseRef-a OR LicenseRef-b".toSpdx() +val concludedLicense = "LicenseRef-a OR LicenseRef-b OR LicenseRef-c or LicenseRef-d".toSpdx() val declaredLicenses = sortedSetOf("Apache-2.0", "MIT") val declaredLicensesProcessed = DeclaredLicenseProcessor.process(declaredLicenses) @@ -167,6 +167,12 @@ val ortResult = OrtResult( ) ), licenseChoices = LicenseChoices( + repositoryLicenseChoices = listOf( + // This license choice will not be applied to "only-concluded-license" since the package license + // choice takes precedence. + LicenseChoice("LicenseRef-a OR LicenseRef-b".toSpdx(), "LicenseRef-b".toSpdx()), + LicenseChoice("LicenseRef-c OR LicenseRef-d".toSpdx(), "LicenseRef-d".toSpdx()) + ), packageLicenseChoices = listOf( PackageLicenseChoice( packageId = Identifier("Maven:org.ossreviewtoolkit:package-with-only-concluded-license:1.0"), diff --git a/model/src/main/kotlin/OrtResult.kt b/model/src/main/kotlin/OrtResult.kt index 9ef08f245f417..67504e8f2a52a 100644 --- a/model/src/main/kotlin/OrtResult.kt +++ b/model/src/main/kotlin/OrtResult.kt @@ -392,6 +392,13 @@ data class OrtResult( fun getLicenseChoices(id: Identifier): List = repository.config.licenseChoices.packageLicenseChoices.find { it.packageId == id }?.licenseChoices.orEmpty() + /** + * Return all [LicenseChoice]s applicable for the scope of the whole [repository]. + */ + @JsonIgnore + fun getRepositoryLicenseChoices(): List = + repository.config.licenseChoices.repositoryLicenseChoices + /** * Return the list of [AdvisorResult]s for the given [id]. */ From dd0b2ae7aab86e7494049195ea922c884c0b69cd Mon Sep 17 00:00:00 2001 From: Stephanie Neubauer Date: Tue, 23 Mar 2021 15:44:12 +0100 Subject: [PATCH 4/6] ResolvedLicenseInfo: Allow multiple license choices in effective license Enable to use license choices from different sources (e.g. global and package specific) in a chosen order. Signed-off-by: Stephanie Neubauer --- .../kotlin/licenses/ResolvedLicenseInfo.kt | 7 ++++--- .../kotlin/licenses/ResolvedLicenseInfoTest.kt | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/model/src/main/kotlin/licenses/ResolvedLicenseInfo.kt b/model/src/main/kotlin/licenses/ResolvedLicenseInfo.kt index 8ad87cafc0af8..c77272eda5092 100644 --- a/model/src/main/kotlin/licenses/ResolvedLicenseInfo.kt +++ b/model/src/main/kotlin/licenses/ResolvedLicenseInfo.kt @@ -69,16 +69,17 @@ data class ResolvedLicenseInfo( /** * Return the effective [SpdxExpression] of this [ResolvedLicenseInfo] based on their [licenses] filtered by the * [licenseView] and the applied [licenseChoices]. Effective, in this context, refers to an [SpdxExpression] that - * can be used as a final license of this [ResolvedLicenseInfo]. + * can be used as a final license of this [ResolvedLicenseInfo]. [licenseChoices] will be applied in the order they + * are given to the function. */ - fun effectiveLicense(licenseView: LicenseView, licenseChoices: List = emptyList()): SpdxExpression? { + fun effectiveLicense(licenseView: LicenseView, vararg licenseChoices: List): SpdxExpression? { val resolvedLicenseInfo = filter(licenseView, filterSources = true) return resolvedLicenseInfo.licenses.flatMap { it.originalExpressions.values } .flatten() .toSet() .reduceOrNull(SpdxExpression::and) - ?.applyChoices(licenseChoices) + ?.applyChoices(licenseChoices.asList().flatten()) ?.validChoices() ?.reduceOrNull(SpdxExpression::or) } diff --git a/model/src/test/kotlin/licenses/ResolvedLicenseInfoTest.kt b/model/src/test/kotlin/licenses/ResolvedLicenseInfoTest.kt index 92d2cfce9d1e4..b2aadb0a40d40 100644 --- a/model/src/test/kotlin/licenses/ResolvedLicenseInfoTest.kt +++ b/model/src/test/kotlin/licenses/ResolvedLicenseInfoTest.kt @@ -108,6 +108,24 @@ class ResolvedLicenseInfoTest : WordSpec() { effectiveLicense shouldBe mit.toSpdx() } + + "apply package and repository license choice for LicenseView.ONLY_CONCLUDED in the correct order" { + val repositoryChoices = listOf( + LicenseChoice("$apache OR $mit".toSpdx(), mit.toSpdx()), + LicenseChoice("$bsd OR $gpl".toSpdx(), bsd.toSpdx()) + ) + val packageChoices = listOf( + LicenseChoice("$apache OR $mit".toSpdx(), apache.toSpdx()) + ) + + val effectiveLicense = createResolvedLicenseInfo().effectiveLicense( + LicenseView.ALL, + packageChoices, + repositoryChoices + ) + + effectiveLicense shouldBe "$apache and ($mit or $gpl) and $bsd".toSpdx() + } } "applyChoices(licenseChoices)" should { From db9cfa52f7ec5c3f88156e0034eeae6c0da865e3 Mon Sep 17 00:00:00 2001 From: Stephanie Neubauer Date: Tue, 23 Mar 2021 15:48:07 +0100 Subject: [PATCH 5/6] ReportTableModelMapper: Apply repoLicenseChoices to effectiveLicense Signed-off-by: Stephanie Neubauer --- ...d-model-reporter-test-expected-output.json | 144 +++++++++++++----- ...ed-model-reporter-test-expected-output.yml | 138 ++++++++++++----- ...ic-html-reporter-test-expected-output.html | 44 +++++- .../static-html-reporter-test-input.yml | 43 ++++++ .../kotlin/utils/ReportTableModelMapper.kt | 3 +- 5 files changed, 292 insertions(+), 80 deletions(-) diff --git a/reporter/src/funTest/assets/evaluated-model-reporter-test-expected-output.json b/reporter/src/funTest/assets/evaluated-model-reporter-test-expected-output.json index e7ffc2ff66735..f4db96d28c8ff 100644 --- a/reporter/src/funTest/assets/evaluated-model-reporter-test-expected-output.json +++ b/reporter/src/funTest/assets/evaluated-model-reporter-test-expected-output.json @@ -55,15 +55,21 @@ "id" : "EPL-1.0" }, { "_id" : 11, - "id" : "MPL 2.0 or EPL 1.0" + "id" : "GPL-2.0-only OR MIT" }, { "_id" : 12, - "id" : "MPL-2.0" + "id" : "GPL-2.0-only" }, { "_id" : 13, - "id" : "Apache License, Version 2.0" + "id" : "MPL 2.0 or EPL 1.0" }, { "_id" : 14, + "id" : "MPL-2.0" + }, { + "_id" : 15, + "id" : "Apache License, Version 2.0" + }, { + "_id" : 16, "id" : "New BSD License" } ], "scopes" : [ { @@ -467,14 +473,64 @@ "scope_excludes" : [ 0 ] }, { "_id" : 3, + "id" : "Maven:com.foobar:foobar:1.0", + "is_project" : false, + "definition_file_path" : "", + "purl" : "pkg:maven/com.foobar/foobar@1.0", + "declared_licenses" : [ 11 ], + "declared_licenses_processed" : { + "spdx_expression" : "GPL-2.0-only OR MIT", + "mapped_licenses" : [ 12, 2 ] + }, + "concluded_license" : "GPL-2.0-only OR MIT", + "binary_artifact" : { + "url" : "", + "hash" : { + "value" : "", + "algorithm" : "" + } + }, + "source_artifact" : { + "url" : "", + "hash" : { + "value" : "", + "algorithm" : "" + } + }, + "vcs" : { + "type" : "", + "url" : "", + "revision" : "", + "path" : "" + }, + "vcs_processed" : { + "type" : "", + "url" : "", + "revision" : "", + "path" : "" + }, + "curations" : [ { + "base" : { }, + "curation" : { + "concluded_license" : "GPL-2.0-only OR MIT", + "comment" : "Foobar is an imaginary dependency and offers a license choice" + } + } ], + "paths" : [ 1 ], + "levels" : [ 1 ], + "scopes" : [ 1 ], + "is_excluded" : true, + "scope_excludes" : [ 0 ] + }, { + "_id" : 4, "id" : "Maven:com.h2database:h2:1.4.200", "is_project" : false, "definition_file_path" : "", "purl" : "pkg:maven/com.h2database/h2@1.4.200", - "declared_licenses" : [ 11 ], + "declared_licenses" : [ 13 ], "declared_licenses_processed" : { "spdx_expression" : "MPL-2.0 OR EPL-1.0", - "mapped_licenses" : [ 12, 10 ] + "mapped_licenses" : [ 14, 10 ] }, "concluded_license" : "MPL-2.0 OR EPL-1.0", "description" : "H2 Database Engine", @@ -512,19 +568,19 @@ "comment" : "H2 database offers a license choice" } } ], - "paths" : [ 1 ], + "paths" : [ 2 ], "levels" : [ 1 ], "scopes" : [ 1 ], "scan_results" : [ 3 ], "is_excluded" : true, "scope_excludes" : [ 0 ] }, { - "_id" : 4, + "_id" : 5, "id" : "Maven:org.apache.commons:commons-lang3:3.5", "is_project" : false, "definition_file_path" : "", "purl" : "pkg:maven/org.apache.commons/commons-lang3@3.5", - "declared_licenses" : [ 13 ], + "declared_licenses" : [ 15 ], "declared_licenses_processed" : { "spdx_expression" : "Apache-2.0", "mapped_licenses" : [ 8 ] @@ -557,18 +613,18 @@ "revision" : "", "path" : "" }, - "paths" : [ 2, 3 ], + "paths" : [ 3, 4 ], "levels" : [ 1 ], "scopes" : [ 0, 1 ], "scan_results" : [ 4 ], "is_excluded" : false }, { - "_id" : 5, + "_id" : 6, "id" : "Maven:org.apache.commons:commons-text:1.1", "is_project" : false, "definition_file_path" : "", "purl" : "pkg:maven/org.apache.commons/commons-text@1.1", - "declared_licenses" : [ 13 ], + "declared_licenses" : [ 15 ], "declared_licenses_processed" : { "spdx_expression" : "Apache-2.0", "mapped_licenses" : [ 8 ] @@ -601,18 +657,18 @@ "revision" : "", "path" : "" }, - "paths" : [ 4, 5 ], + "paths" : [ 5, 6 ], "levels" : [ 0 ], "scopes" : [ 0, 1 ], "scan_results" : [ 5 ], "is_excluded" : false }, { - "_id" : 6, + "_id" : 7, "id" : "Maven:org.hamcrest:hamcrest-core:1.3", "is_project" : false, "definition_file_path" : "", "purl" : "pkg:maven/org.hamcrest/hamcrest-core@1.3", - "declared_licenses" : [ 14 ], + "declared_licenses" : [ 16 ], "declared_licenses_processed" : { "spdx_expression" : "BSD-3-Clause", "mapped_licenses" : [ 1 ] @@ -645,7 +701,7 @@ "revision" : "", "path" : "" }, - "paths" : [ 6 ], + "paths" : [ 7 ], "levels" : [ 1 ], "scopes" : [ 1 ], "scan_results" : [ 6 ], @@ -668,31 +724,37 @@ "_id" : 2, "pkg" : 4, "project" : 0, - "scope" : 0, - "path" : [ 5 ] + "scope" : 1, + "path" : [ 2 ] }, { "_id" : 3, - "pkg" : 4, + "pkg" : 5, "project" : 0, - "scope" : 1, - "path" : [ 5 ] + "scope" : 0, + "path" : [ 6 ] }, { "_id" : 4, "pkg" : 5, "project" : 0, - "scope" : 0, - "path" : [ ] + "scope" : 1, + "path" : [ 6 ] }, { "_id" : 5, - "pkg" : 5, + "pkg" : 6, "project" : 0, - "scope" : 1, + "scope" : 0, "path" : [ ] }, { "_id" : 6, "pkg" : 6, "project" : 0, "scope" : 1, + "path" : [ ] + }, { + "_id" : 7, + "pkg" : 7, + "project" : 0, + "scope" : 1, "path" : [ 2 ] } ], "dependency_trees" : [ { @@ -704,11 +766,11 @@ "children" : [ { "key" : 2, "linkage" : "DYNAMIC", - "pkg" : 5, + "pkg" : 6, "children" : [ { "key" : 3, "linkage" : "DYNAMIC", - "pkg" : 4 + "pkg" : 5 } ] } ] }, { @@ -726,21 +788,25 @@ }, { "key" : 7, "linkage" : "DYNAMIC", - "pkg" : 6 + "pkg" : 4 + }, { + "key" : 8, + "linkage" : "DYNAMIC", + "pkg" : 7 } ] }, { - "key" : 8, + "key" : 9, "linkage" : "DYNAMIC", - "pkg" : 5, + "pkg" : 6, "children" : [ { - "key" : 9, + "key" : 10, "linkage" : "DYNAMIC", - "pkg" : 4 + "pkg" : 5 } ] } ] } ] }, { - "key" : 10, + "key" : 11, "pkg" : 1, "path_excludes" : [ 0 ] } ], @@ -762,7 +828,7 @@ }, { "_id" : 1, "rule" : "rule 2", - "pkg" : 5, + "pkg" : 6, "license" : 8, "license_source" : "DECLARED", "severity" : "HINT", @@ -772,7 +838,7 @@ }, { "_id" : 2, "rule" : "rule 3", - "pkg" : 6, + "pkg" : 7, "license" : 1, "license_source" : "CONCLUDED", "severity" : "WARNING", @@ -794,7 +860,7 @@ "included_projects" : 1, "excluded_projects" : 1, "included_packages" : 2, - "excludes_packages" : 3, + "excludes_packages" : 4, "total_tree_depth" : 2, "included_tree_depth" : 2, "included_scopes" : [ "compile" ], @@ -806,7 +872,9 @@ "BSD-3-Clause" : 1, "CC-BY-NC-3.0" : 1, "EPL-1.0" : 2, + "GPL-2.0-only" : 1, "GPL-3.0-only WITH GCC-exception-3.1" : 1, + "MIT" : 1, "MPL-2.0" : 1 }, "detected" : { @@ -849,6 +917,10 @@ "rule_violations" : [ 0 ] }, "license_choices" : { + "repository_license_choices" : [ { + "given" : "GPL-2.0-only OR MIT", + "choice" : "MIT" + } ], "package_license_choices" : [ { "package_id" : "Maven:com.h2database:h2:1.4.200", "license_choices" : [ { @@ -859,7 +931,7 @@ } } }, - "repository_configuration" : "---\nexcludes:\n paths:\n - pattern: \"sub/module/project/build.gradle\"\n reason: \"EXAMPLE_OF\"\n comment: \"The project is an example.\"\n - pattern: \"**.java\"\n reason: \"EXAMPLE_OF\"\n comment: \"These are example files.\"\n scopes:\n - pattern: \"testCompile\"\n reason: \"TEST_DEPENDENCY_OF\"\n comment: \"The scope only contains test dependencies.\"\nresolutions:\n rule_violations:\n - message: \"Apache-2.0 hint\"\n reason: \"CANT_FIX_EXCEPTION\"\n comment: \"Apache-2 is not an issue.\"\nlicense_choices:\n package_license_choices:\n - package_id: \"Maven:com.h2database:h2:1.4.200\"\n license_choices:\n - given: \"MPL-2.0 OR EPL-1.0\"\n choice: \"MPL-2.0\"\n", + "repository_configuration" : "---\nexcludes:\n paths:\n - pattern: \"sub/module/project/build.gradle\"\n reason: \"EXAMPLE_OF\"\n comment: \"The project is an example.\"\n - pattern: \"**.java\"\n reason: \"EXAMPLE_OF\"\n comment: \"These are example files.\"\n scopes:\n - pattern: \"testCompile\"\n reason: \"TEST_DEPENDENCY_OF\"\n comment: \"The scope only contains test dependencies.\"\nresolutions:\n rule_violations:\n - message: \"Apache-2.0 hint\"\n reason: \"CANT_FIX_EXCEPTION\"\n comment: \"Apache-2 is not an issue.\"\nlicense_choices:\n repository_license_choices:\n - given: \"GPL-2.0-only OR MIT\"\n choice: \"MIT\"\n package_license_choices:\n - package_id: \"Maven:com.h2database:h2:1.4.200\"\n license_choices:\n - given: \"MPL-2.0 OR EPL-1.0\"\n choice: \"MPL-2.0\"\n", "labels" : { "job_parameters.JOB_PARAM_1" : "label job param 1", "job_parameters.JOB_PARAM_2" : "label job param 2", diff --git a/reporter/src/funTest/assets/evaluated-model-reporter-test-expected-output.yml b/reporter/src/funTest/assets/evaluated-model-reporter-test-expected-output.yml index 827306843ff9e..b32d64e02a9ae 100644 --- a/reporter/src/funTest/assets/evaluated-model-reporter-test-expected-output.yml +++ b/reporter/src/funTest/assets/evaluated-model-reporter-test-expected-output.yml @@ -40,12 +40,16 @@ licenses: - _id: 10 id: "EPL-1.0" - _id: 11 - id: "MPL 2.0 or EPL 1.0" + id: "GPL-2.0-only OR MIT" - _id: 12 - id: "MPL-2.0" + id: "GPL-2.0-only" - _id: 13 - id: "Apache License, Version 2.0" + id: "MPL 2.0 or EPL 1.0" - _id: 14 + id: "MPL-2.0" +- _id: 15 + id: "Apache License, Version 2.0" +- _id: 16 id: "New BSD License" scopes: - _id: 0 @@ -408,16 +412,63 @@ packages: scope_excludes: - 0 - _id: 3 + id: "Maven:com.foobar:foobar:1.0" + is_project: false + definition_file_path: "" + purl: "pkg:maven/com.foobar/foobar@1.0" + declared_licenses: + - 11 + declared_licenses_processed: + spdx_expression: "GPL-2.0-only OR MIT" + mapped_licenses: + - 12 + - 2 + concluded_license: "GPL-2.0-only OR MIT" + binary_artifact: + url: "" + hash: + value: "" + algorithm: "" + source_artifact: + url: "" + hash: + value: "" + algorithm: "" + vcs: + type: "" + url: "" + revision: "" + path: "" + vcs_processed: + type: "" + url: "" + revision: "" + path: "" + curations: + - base: {} + curation: + concluded_license: "GPL-2.0-only OR MIT" + comment: "Foobar is an imaginary dependency and offers a license choice" + paths: + - 1 + levels: + - 1 + scopes: + - 1 + is_excluded: true + scope_excludes: + - 0 +- _id: 4 id: "Maven:com.h2database:h2:1.4.200" is_project: false definition_file_path: "" purl: "pkg:maven/com.h2database/h2@1.4.200" declared_licenses: - - 11 + - 13 declared_licenses_processed: spdx_expression: "MPL-2.0 OR EPL-1.0" mapped_licenses: - - 12 + - 14 - 10 concluded_license: "MPL-2.0 OR EPL-1.0" description: "H2 Database Engine" @@ -448,7 +499,7 @@ packages: concluded_license: "MPL-2.0 OR EPL-1.0" comment: "H2 database offers a license choice" paths: - - 1 + - 2 levels: - 1 scopes: @@ -458,13 +509,13 @@ packages: is_excluded: true scope_excludes: - 0 -- _id: 4 +- _id: 5 id: "Maven:org.apache.commons:commons-lang3:3.5" is_project: false definition_file_path: "" purl: "pkg:maven/org.apache.commons/commons-lang3@3.5" declared_licenses: - - 13 + - 15 declared_licenses_processed: spdx_expression: "Apache-2.0" mapped_licenses: @@ -494,8 +545,8 @@ packages: revision: "" path: "" paths: - - 2 - 3 + - 4 levels: - 1 scopes: @@ -504,13 +555,13 @@ packages: scan_results: - 4 is_excluded: false -- _id: 5 +- _id: 6 id: "Maven:org.apache.commons:commons-text:1.1" is_project: false definition_file_path: "" purl: "pkg:maven/org.apache.commons/commons-text@1.1" declared_licenses: - - 13 + - 15 declared_licenses_processed: spdx_expression: "Apache-2.0" mapped_licenses: @@ -539,8 +590,8 @@ packages: revision: "" path: "" paths: - - 4 - 5 + - 6 levels: - 0 scopes: @@ -549,13 +600,13 @@ packages: scan_results: - 5 is_excluded: false -- _id: 6 +- _id: 7 id: "Maven:org.hamcrest:hamcrest-core:1.3" is_project: false definition_file_path: "" purl: "pkg:maven/org.hamcrest/hamcrest-core@1.3" declared_licenses: - - 14 + - 16 declared_licenses_processed: spdx_expression: "BSD-3-Clause" mapped_licenses: @@ -585,7 +636,7 @@ packages: revision: "" path: "" paths: - - 6 + - 7 levels: - 1 scopes: @@ -610,29 +661,35 @@ paths: - _id: 2 pkg: 4 project: 0 - scope: 0 + scope: 1 path: - - 5 + - 2 - _id: 3 - pkg: 4 + pkg: 5 project: 0 - scope: 1 + scope: 0 path: - - 5 + - 6 - _id: 4 pkg: 5 project: 0 - scope: 0 - path: [] + scope: 1 + path: + - 6 - _id: 5 - pkg: 5 + pkg: 6 project: 0 - scope: 1 + scope: 0 path: [] - _id: 6 pkg: 6 project: 0 scope: 1 + path: [] +- _id: 7 + pkg: 7 + project: 0 + scope: 1 path: - 2 dependency_trees: @@ -644,11 +701,11 @@ dependency_trees: children: - key: 2 linkage: "DYNAMIC" - pkg: 5 + pkg: 6 children: - key: 3 linkage: "DYNAMIC" - pkg: 4 + pkg: 5 - key: 4 scope: 1 scope_excludes: @@ -663,15 +720,18 @@ dependency_trees: pkg: 3 - key: 7 linkage: "DYNAMIC" - pkg: 6 - - key: 8 + pkg: 4 + - key: 8 + linkage: "DYNAMIC" + pkg: 7 + - key: 9 linkage: "DYNAMIC" - pkg: 5 + pkg: 6 children: - - key: 9 + - key: 10 linkage: "DYNAMIC" - pkg: 4 -- key: 10 + pkg: 5 +- key: 11 pkg: 1 path_excludes: - 0 @@ -692,7 +752,7 @@ rule_violations: \ that overflow:scroll is working as expected.\n```" - _id: 1 rule: "rule 2" - pkg: 5 + pkg: 6 license: 8 license_source: "DECLARED" severity: "HINT" @@ -703,7 +763,7 @@ rule_violations: - 0 - _id: 2 rule: "rule 3" - pkg: 6 + pkg: 7 license: 1 license_source: "CONCLUDED" severity: "WARNING" @@ -723,7 +783,7 @@ statistics: included_projects: 1 excluded_projects: 1 included_packages: 2 - excludes_packages: 3 + excludes_packages: 4 total_tree_depth: 2 included_tree_depth: 2 included_scopes: @@ -736,7 +796,9 @@ statistics: BSD-3-Clause: 1 CC-BY-NC-3.0: 1 EPL-1.0: 2 + GPL-2.0-only: 1 GPL-3.0-only WITH GCC-exception-3.1: 1 + MIT: 1 MPL-2.0: 1 detected: Apache-2.0: 1 @@ -773,6 +835,9 @@ repository: rule_violations: - 0 license_choices: + repository_license_choices: + - given: "GPL-2.0-only OR MIT" + choice: "MIT" package_license_choices: - package_id: "Maven:com.h2database:h2:1.4.200" license_choices: @@ -784,7 +849,8 @@ repository_configuration: "---\nexcludes:\n paths:\n - pattern: \"sub/module/p \n scopes:\n - pattern: \"testCompile\"\n reason: \"TEST_DEPENDENCY_OF\"\n\ \ comment: \"The scope only contains test dependencies.\"\nresolutions:\n rule_violations:\n\ \ - message: \"Apache-2.0 hint\"\n reason: \"CANT_FIX_EXCEPTION\"\n comment:\ - \ \"Apache-2 is not an issue.\"\nlicense_choices:\n package_license_choices:\n\ + \ \"Apache-2 is not an issue.\"\nlicense_choices:\n repository_license_choices:\n\ + \ - given: \"GPL-2.0-only OR MIT\"\n choice: \"MIT\"\n package_license_choices:\n\ \ - package_id: \"Maven:com.h2database:h2:1.4.200\"\n license_choices:\n \ \ - given: \"MPL-2.0 OR EPL-1.0\"\n choice: \"MPL-2.0\"\n" labels: diff --git a/reporter/src/funTest/assets/static-html-reporter-test-expected-output.html b/reporter/src/funTest/assets/static-html-reporter-test-expected-output.html index 4dabec88bf178..f5cca1458b9c4 100644 --- a/reporter/src/funTest/assets/static-html-reporter-test-expected-output.html +++ b/reporter/src/funTest/assets/static-html-reporter-test-expected-output.html @@ -657,7 +657,34 @@

Packages

- 3Maven:com.h2database:h2:1.4.200 + 3Maven:com.foobar:foobar:1.0 +
    +
  • testCompile
    Excluded: TEST_DEPENDENCY_OF - The scope only contains test dependencies.
    +
  • +
+ Concluded License: +
+
GPL-2.0-only OR MIT
+
+ Declared Licenses: +
+
+
GPL-2.0-only
+
MIT
+
+
+ Effective License: +
+
MIT
+
+ +
    + +
      + + + + 4Maven:com.h2database:h2:1.4.200
      • testCompile
        Excluded: TEST_DEPENDENCY_OF - The scope only contains test dependencies.
      • @@ -683,8 +710,8 @@

        Packages

          - - 4Maven:org.apache.commons:commons-lang3:3.5 + + 5Maven:org.apache.commons:commons-lang3:3.5
          • compile
          • testCompile
            Excluded: TEST_DEPENDENCY_OF - The scope only contains test dependencies.
            @@ -706,8 +733,8 @@

            Packages

              - - 5Maven:org.apache.commons:commons-text:1.1 + + 6Maven:org.apache.commons:commons-text:1.1
              • compile
              • testCompile
                Excluded: TEST_DEPENDENCY_OF - The scope only contains test dependencies.
                @@ -729,8 +756,8 @@

                Packages

                  - - 6Maven:org.hamcrest:hamcrest-core:1.3 + + 7Maven:org.hamcrest:hamcrest-core:1.3
                  • testCompile
                    Excluded: TEST_DEPENDENCY_OF - The scope only contains test dependencies.
                  • @@ -833,6 +860,9 @@

                    Repository Configuration

                    reason: "CANT_FIX_EXCEPTION" comment: "Apache-2 is not an issue." license_choices: + repository_license_choices: + - given: "GPL-2.0-only OR MIT" + choice: "MIT" package_license_choices: - package_id: "Maven:com.h2database:h2:1.4.200" license_choices: diff --git a/reporter/src/funTest/assets/static-html-reporter-test-input.yml b/reporter/src/funTest/assets/static-html-reporter-test-input.yml index c04d1f60c664c..603702412c0c7 100644 --- a/reporter/src/funTest/assets/static-html-reporter-test-input.yml +++ b/reporter/src/funTest/assets/static-html-reporter-test-input.yml @@ -35,6 +35,9 @@ repository: reason: "CANT_FIX_EXCEPTION" comment: "Apache-2 is not an issue." license_choices: + repository_license_choices: + - given: "GPL-2.0-only OR MIT" + choice: "MIT" package_license_choices: - package_id: "Maven:com.h2database:h2:1.4.200" license_choices: @@ -81,6 +84,7 @@ analyzer: dependencies: - id: "Maven:org.hamcrest:hamcrest-core:1.3" - id: "Maven:com.h2database:h2:1.4.200" + - id: "Maven:com.foobar:foobar:1.0" - id: "Maven:org.apache.commons:commons-text:1.1" dependencies: - id: "Maven:org.apache.commons:commons-lang3:3.5" @@ -235,6 +239,45 @@ analyzer: revision: "" path: "" curations: [] + - package: + id: "Maven:com.foobar:foobar:1.0" + purl: "pkg:maven/com.foobar/foobar@1.0" + authors: + - "Jane Doe" + declared_licenses: + - "GPL-2.0-only OR MIT" + declared_licenses_processed: + spdx_expression: "GPL-2.0-only OR MIT" + mapped: + GPL-2.0-only OR MIT: "GPL-2.0-only OR MIT" + concluded_license: "GPL-2.0-only OR MIT" + description: "" + homepage_url: "" + binary_artifact: + url: "" + hash: + value: "" + algorithm: "" + source_artifact: + url: "" + hash: + value: "" + algorithm: "" + vcs: + type: "" + url: "" + revision: "" + path: "" + vcs_processed: + type: "" + url: "" + revision: "" + path: "" + curations: + - base: { } + curation: + concluded_license: "GPL-2.0-only OR MIT" + comment: "Foobar is an imaginary dependency and offers a license choice" - package: id: "Maven:com.h2database:h2:1.4.200" purl: "pkg:maven/com.h2database/h2@1.4.200" diff --git a/reporter/src/main/kotlin/utils/ReportTableModelMapper.kt b/reporter/src/main/kotlin/utils/ReportTableModelMapper.kt index da7bf9082da2b..aaac23fea2eae 100644 --- a/reporter/src/main/kotlin/utils/ReportTableModelMapper.kt +++ b/reporter/src/main/kotlin/utils/ReportTableModelMapper.kt @@ -162,7 +162,8 @@ class ReportTableModelMapper( detectedLicenses = detectedLicenses, effectiveLicense = resolvedLicenseInfo.effectiveLicense( LicenseView.CONCLUDED_OR_DECLARED_AND_DETECTED, - ortResult.getLicenseChoices(id) + ortResult.getLicenseChoices(id), + ortResult.getRepositoryLicenseChoices() ), analyzerIssues = analyzerIssues.map { it.toResolvableIssue() }, scanIssues = scanIssues.map { it.toResolvableIssue() } From 2577355d2376aeef835d18123afd2d5635e3addb Mon Sep 17 00:00:00 2001 From: Stephanie Neubauer Date: Tue, 23 Mar 2021 16:06:15 +0100 Subject: [PATCH 6/6] Documentation: Add repository license choice to ort.yml documentation Resolves #3396. Signed-off-by: Stephanie Neubauer --- docs/config-file-ort-yml.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/config-file-ort-yml.md b/docs/config-file-ort-yml.md index bcd40666cbc90..0247c8d682bfb 100644 --- a/docs/config-file-ort-yml.md +++ b/docs/config-file-ort-yml.md @@ -266,7 +266,7 @@ resolutions: ### When to Use License Choices For multi-licensed dependencies a specific license can be selected. -The license choice can be applied to a package. +The license choice can be applied to a package or globally to an SPDX expression in the project. A choice is only valid for licenses combined with the SPDX operator `OR`. The choices are applied in the evaluator, and the reporter to the effective license of a package, which is calculated by the chosen [LicenseView](../model/src/main/kotlin/licenses/LicenseView.kt). @@ -276,7 +276,7 @@ by the chosen [LicenseView](../model/src/main/kotlin/licenses/LicenseView.kt). To select a license from a multi-licensed dependency, specified by its `packageId`, an SPDX expression for a `choice` must be provided. The `choice` is either applied to the whole effective SPDX expression of the package or to an optional `given` SPDX -expression that can represent only a sub-expression of the whole effective SPDX expression. +expression that can represent only a sub-expression of the whole effective SPDX expression. e.g. ```yaml @@ -300,6 +300,25 @@ license_choices: # The result would be: C AND E ``` +### License Choice for the Project + +To globally select a license from an SPDX expression, that offers a choice, an SPDX expression for a `given` and a +`choice` must be provided. +The `choice` is applied to the whole `given` SPDX expression. +With a repository license choice, the license choice is applied to each package that offers this license as a choice. +Not allowing `given` to be null helps only applying the choice to a wanted `given` as opposed to all licenses with that +choice, which could lead to unwanted choices. +The license choices for a project can be overwritten by applying a +[license choice to a package](#license-choice-by-package). + +e.g. +```yaml +license_choices: + repository_license_choice: + - given: "A OR B" + choice: "B" +``` + --- **NOTE**