Skip to content

Commit e4bf624

Browse files
porsche-rbienieksschuberth
authored andcommitted
model: Introduce a new declared copyrights field to data model
In German law, the author and the copyright holder can be two separate legal entities and therefore also need to be tracked separately. Introduce a new field for declared copyrights that is now the primary source for copyright holder information. Authors are still only used as copyright holders if the `addAuthorsToCopyrights` option is enabled. For now, all package manager implementations set empty declared copyrights. Filling the declared copyrights is left as a future exercise (also see [1]). Currently, the only way to add declared copyrights is via curations. This change resolves #4519. [1]: #5504 (comment) Signed-off-by: Rainer Bieniek <extern.rainer.bieniek@porsche.de> Signed-off-by: Sebastian Schuberth <sschuberth@gmail.com>
1 parent e777807 commit e4bf624

File tree

11 files changed

+79
-5
lines changed

11 files changed

+79
-5
lines changed

model/src/main/kotlin/Package.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ data class Package(
6262
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
6363
val authors: SortedSet<String> = sortedSetOf(),
6464

65+
/**
66+
* The set of concluded copyright statements for this package. It can be used to override the [detected copyright
67+
* statements][CopyrightFinding.statement] (note that there is no such thing as a *declared* copyright statement as
68+
* package managers to not support declaring them explicitly).
69+
*
70+
* ORT itself does not set this field, it needs to be set by the user using a [PackageCuration].
71+
*/
72+
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
73+
val concludedCopyrights: SortedSet<String> = sortedSetOf(),
74+
6575
/**
6676
* The set of licenses declared for this package. This does not necessarily correspond to the licenses as detected
6777
* by a scanner. Both need to be taken into account for any conclusions.
@@ -138,6 +148,7 @@ data class Package(
138148
id = Identifier.EMPTY,
139149
purl = "",
140150
authors = sortedSetOf(),
151+
concludedCopyrights = sortedSetOf(),
141152
declaredLicenses = sortedSetOf(),
142153
declaredLicensesProcessed = ProcessedDeclaredLicense.EMPTY,
143154
concludedLicense = null,

model/src/main/kotlin/PackageCurationData.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ data class PackageCurationData(
5555
*/
5656
val authors: SortedSet<String>? = null,
5757

58+
/**
59+
* The set of concluded copyright statements for the package. It can be used to override the [detected copyright
60+
* statements][CopyrightFinding.statement].
61+
*/
62+
val concludedCopyrights: SortedSet<String>? = null,
63+
5864
/**
5965
* The concluded license as an [SpdxExpression]. It can be used to override the [declared][Package.declaredLicenses]
6066
* / [detected][LicenseFinding.license] licenses of a package.
@@ -131,6 +137,7 @@ data class PackageCurationData(
131137
purl = purl ?: original.purl,
132138
cpe = cpe ?: original.cpe,
133139
authors = authors ?: original.authors,
140+
concludedCopyrights = concludedCopyrights ?: original.concludedCopyrights,
134141
declaredLicenses = original.declaredLicenses,
135142
declaredLicensesProcessed = declaredLicensesProcessed,
136143
concludedLicense = concludedLicense ?: original.concludedLicense,
@@ -170,6 +177,7 @@ data class PackageCurationData(
170177
purl = purl ?: other.purl,
171178
cpe = cpe ?: other.cpe,
172179
authors = (authors.orEmpty() + other.authors.orEmpty()).toSortedSet(),
180+
concludedCopyrights = (concludedCopyrights.orEmpty() + other.concludedCopyrights.orEmpty()).toSortedSet(),
173181
concludedLicense = setOfNotNull(concludedLicense, other.concludedLicense).reduce(SpdxExpression::and),
174182
description = description ?: other.description,
175183
homepageUrl = homepageUrl ?: other.homepageUrl,

model/src/main/kotlin/licenses/DefaultLicenseInfoProvider.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,11 @@ class DefaultLicenseInfoProvider(
5353
private fun createConcludedLicenseInfo(id: Identifier): ConcludedLicenseInfo =
5454
ortResult.getPackage(id)?.let { (pkg, curations) ->
5555
ConcludedLicenseInfo(
56+
concludedCopyrights = pkg.concludedCopyrights,
5657
concludedLicense = pkg.concludedLicense,
5758
appliedCurations = curations.filter { it.curation.concludedLicense != null }
5859
)
59-
} ?: ConcludedLicenseInfo(concludedLicense = null, appliedCurations = emptyList())
60+
} ?: ConcludedLicenseInfo(concludedCopyrights = null, concludedLicense = null, appliedCurations = emptyList())
6061

6162
private fun createDeclaredLicenseInfo(id: Identifier): DeclaredLicenseInfo =
6263
ortResult.getProject(id)?.let { project ->

model/src/main/kotlin/licenses/LicenseInfo.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ data class LicenseInfo(
6262
* Information about the concluded license of a package or project.
6363
*/
6464
data class ConcludedLicenseInfo(
65+
/**
66+
* The concluded copyright statements, or null if no copyrights were concluded.
67+
*/
68+
val concludedCopyrights: SortedSet<String>?,
69+
6570
/**
6671
* The concluded license, or null if no license was concluded.
6772
*/

model/src/main/kotlin/licenses/LicenseInfoResolver.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,22 @@ class LicenseInfoResolver(
9696
originalDeclaredLicenses += licenseInfo.declaredLicenseInfo.processed.mapped.filterValues {
9797
it == license
9898
}.keys
99+
100+
licenseInfo.concludedLicenseInfo.concludedCopyrights?.takeIf { it.isNotEmpty() }?.also {
101+
locations += ResolvedLicenseLocation(
102+
provenance = UnknownProvenance,
103+
location = UNDEFINED_TEXT_LOCATION,
104+
appliedCuration = null,
105+
matchingPathExcludes = emptyList(),
106+
copyrights = it.mapTo(mutableSetOf()) { statement ->
107+
ResolvedCopyrightFinding(
108+
statement = statement,
109+
location = UNDEFINED_TEXT_LOCATION,
110+
matchingPathExcludes = emptyList()
111+
)
112+
}
113+
)
114+
}
99115
}
100116
}
101117

model/src/test/kotlin/PackageCurationDataTest.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class PackageCurationDataTest : WordSpec({
3030
purl = "original",
3131
cpe = "original",
3232
authors = sortedSetOf("original"),
33+
concludedCopyrights = sortedSetOf("original"),
3334
concludedLicense = "original".toSpdx(),
3435
description = "original",
3536
homepageUrl = "original",
@@ -57,6 +58,7 @@ class PackageCurationDataTest : WordSpec({
5758
purl = "other",
5859
cpe = "other",
5960
authors = sortedSetOf("other"),
61+
concludedCopyrights = sortedSetOf("other"),
6062
concludedLicense = "other".toSpdx(),
6163
description = "other",
6264
homepageUrl = "other",
@@ -88,6 +90,7 @@ class PackageCurationDataTest : WordSpec({
8890
val originalWithSomeUnsetData = original.copy(
8991
comment = null,
9092
authors = null,
93+
concludedCopyrights = null,
9194
concludedLicense = null,
9295
binaryArtifact = null,
9396
vcs = null,
@@ -98,6 +101,7 @@ class PackageCurationDataTest : WordSpec({
98101
originalWithSomeUnsetData.merge(other) shouldBe originalWithSomeUnsetData.copy(
99102
comment = other.comment,
100103
authors = other.authors,
104+
concludedCopyrights = other.concludedCopyrights,
101105
concludedLicense = other.concludedLicense,
102106
binaryArtifact = other.binaryArtifact,
103107
vcs = other.vcs,
@@ -110,6 +114,7 @@ class PackageCurationDataTest : WordSpec({
110114
original.merge(other) shouldBe original.copy(
111115
comment = "original\nother",
112116
authors = sortedSetOf("original", "other"),
117+
concludedCopyrights = sortedSetOf("original", "other"),
113118
concludedLicense = "original AND other".toSpdx(),
114119
declaredLicenseMapping = mapOf(
115120
"original" to "original".toSpdx(),
@@ -122,6 +127,7 @@ class PackageCurationDataTest : WordSpec({
122127
val otherWithSomeOriginalData = other.copy(
123128
comment = original.comment,
124129
authors = original.authors,
130+
concludedCopyrights = original.concludedCopyrights,
125131
concludedLicense = original.concludedLicense,
126132
declaredLicenseMapping = original.declaredLicenseMapping
127133
)

model/src/test/kotlin/PackageCurationTest.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class PackageCurationTest : WordSpec({
5656
purl = "pkg:maven/org.hamcrest/hamcrest-core@1.3#subpath=src/main/java/org/hamcrest/core",
5757
cpe = "cpe:2.3:a:apache:commons_io:2.8.0:rc2:*:*:*:*:*:*",
5858
authors = sortedSetOf("author 1", "author 2"),
59-
declaredLicenseMapping = mapOf("license a" to "Apache-2.0".toSpdx()),
59+
concludedCopyrights = sortedSetOf("copyright 1", "copyright 2"),
6060
concludedLicense = "license1 OR license2".toSpdx(),
6161
description = "description",
6262
homepageUrl = "http://home.page",
@@ -75,7 +75,8 @@ class PackageCurationTest : WordSpec({
7575
path = "path"
7676
),
7777
isMetaDataOnly = true,
78-
isModified = true
78+
isModified = true,
79+
declaredLicenseMapping = mapOf("license a" to "Apache-2.0".toSpdx())
7980
)
8081
)
8182

@@ -86,6 +87,7 @@ class PackageCurationTest : WordSpec({
8687
purl shouldBe curation.data.purl
8788
cpe shouldBe curation.data.cpe
8889
authors shouldBe curation.data.authors
90+
concludedCopyrights shouldBe curation.data.concludedCopyrights
8991
declaredLicenses shouldBe pkg.declaredLicenses
9092
declaredLicensesProcessed.spdxExpression shouldBe "Apache-2.0".toSpdx()
9193
declaredLicensesProcessed.unmapped should containExactlyInAnyOrder("license b")
@@ -147,6 +149,7 @@ class PackageCurationTest : WordSpec({
147149
purl shouldBe pkg.purl
148150
cpe shouldBe pkg.cpe
149151
authors shouldBe pkg.authors
152+
concludedCopyrights shouldBe pkg.concludedCopyrights
150153
declaredLicenses shouldBe pkg.declaredLicenses
151154
concludedLicense shouldBe pkg.concludedLicense
152155
description shouldBe pkg.description

model/src/test/kotlin/licenses/LicenseInfoResolverTest.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,27 @@ class LicenseInfoResolverTest : WordSpec({
238238
result should containFindingsForCopyrightExactly("(c) 2010 Holder 2", TextLocation("LICENSE", 3))
239239
}
240240

241+
"resolve concluded copyright statements" {
242+
val licenseInfos = listOf(
243+
createLicenseInfo(
244+
id = pkgId,
245+
concludedCopyrights = concludedCopyrights,
246+
declaredLicenses = declaredLicenses
247+
)
248+
)
249+
val resolver = createResolver(licenseInfos)
250+
251+
val result = resolver.resolveLicenseInfo(pkgId)
252+
result should containCopyrightStatementsForLicenseExactly(
253+
"LicenseRef-a",
254+
"Copyright (C) 2017 foo", "Copyright (C) 2022 bar"
255+
)
256+
result should containCopyrightStatementsForLicenseExactly(
257+
"LicenseRef-b",
258+
"Copyright (C) 2017 foo", "Copyright (C) 2022 bar"
259+
)
260+
}
261+
241262
"process copyrights by license" {
242263
val licenseInfos = listOf(
243264
createLicenseInfo(
@@ -625,6 +646,7 @@ private fun createResolver(
625646

626647
private fun createLicenseInfo(
627648
id: Identifier,
649+
concludedCopyrights: SortedSet<String>? = null,
628650
declaredLicenses: Set<String> = emptySet(),
629651
detectedLicenses: List<Findings> = emptyList(),
630652
concludedLicense: SpdxExpression? = null
@@ -640,6 +662,7 @@ private fun createLicenseInfo(
640662
findings = detectedLicenses
641663
),
642664
concludedLicenseInfo = ConcludedLicenseInfo(
665+
concludedCopyrights = concludedCopyrights,
643666
concludedLicense = concludedLicense,
644667
appliedCurations = emptyList()
645668
)

model/src/test/kotlin/licenses/TestData.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import org.ossreviewtoolkit.utils.spdx.toSpdx
5353
val authors = sortedSetOf("The Author", "The Other Author")
5454
val projectAuthors = sortedSetOf("The Project Author")
5555

56+
val concludedCopyrights = sortedSetOf("Copyright (C) 2017 foo", "Copyright (C) 2022 bar")
5657
val concludedLicense = "LicenseRef-a AND LicenseRef-b".toSpdx()
5758
val declaredLicenses = sortedSetOf("LicenseRef-a", "LicenseRef-b")
5859
val declaredLicensesProcessed = DeclaredLicenseProcessor.process(declaredLicenses)

scanner/src/main/kotlin/Scanner.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const val TOOL_NAME = "scanner"
4848
private fun removeConcludedPackages(packages: Set<Package>, scanner: Scanner): Set<Package> =
4949
packages.takeUnless { scanner.scannerConfig.skipConcluded }
5050
// Remove all packages that have a concluded license and authors set.
51-
?: packages.partition { it.concludedLicense != null && it.authors.isNotEmpty() }.let { (skip, keep) ->
51+
?: packages.partition { it.concludedLicense != null && it.concludedCopyrights.isNotEmpty() }.let { (skip, keep) ->
5252
if (skip.isNotEmpty()) {
5353
Scanner.logger.debug { "Not scanning the following packages with concluded licenses: $skip" }
5454
}

0 commit comments

Comments
 (0)