Skip to content

Commit 8577405

Browse files
committed
fix(spdx): Check for the presence of files
The SPDX specification allows certain property values for packages only if actually files are present for this package, e.g. [1, 2]. The SPDX reporter used to derive such properties from the presence of a package verification code. However, there could be cases in which such a verification code exists, but there are no files with relevant findings for the package. In such cases, validation of the generated SPDX documents with the SPDX validator tool [3] failed with a message like "Relationship error: Missing required package files for XXX". To prevent this, pass the list of files to the `toSpdxPackage()` function and evaluate it when computing the value for related properties. The new parameter also slightly simplifies the code on caller side. [1]: https://spdx.github.io/spdx-spec/v2.3/package-information/#78-files-analyzed-field [2]: https://spdx.github.io/spdx-spec/v2.3/package-information/#79-package-verification-code-field [3] https://github.com/spdx/tools-java Signed-off-by: Oliver Heger <oliver.heger@bosch.com>
1 parent f1a5a62 commit 8577405

4 files changed

Lines changed: 99 additions & 10 deletions

File tree

plugins/reporters/spdx/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ dependencies {
3838

3939
implementation(jacksonLibs.jacksonDatabind)
4040

41+
testImplementation(libs.mockk)
42+
4143
funTestImplementation(projects.utils.testUtils)
4244
funTestImplementation(projects.plugins.licenseFactProviders.scancodeLicenseFactProvider)
4345
funTestImplementation(testFixtures(projects.plugins.reporters.spdxReporter))

plugins/reporters/spdx/src/main/kotlin/Extensions.kt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,15 +143,23 @@ internal enum class SpdxPackageType(val infix: String, val suffix: String = "")
143143
/**
144144
* Convert this ORT package to an SPDX package. As an ORT package can hold more metadata about its associated artifacts
145145
* and origin than an SPDX package, the [type] is used to specify which kind of SPDX package should be created from the
146-
* respective ORT package metadata.
146+
* respective ORT package metadata. Also, the list of [files] contained in the package can be provided which has an
147+
* impact on some properties.
147148
*/
148149
internal fun Package.toSpdxPackage(
149150
type: SpdxPackageType,
150151
licenseInfoResolver: LicenseInfoResolver,
151-
ortResult: OrtResult
152+
ortResult: OrtResult,
153+
files: List<SpdxFile> = emptyList()
152154
): SpdxPackage {
153-
val packageVerificationCode = ortResult.getPackageVerificationCode(id, type)?.let {
154-
SpdxPackageVerificationCode(packageVerificationCodeValue = it)
155+
val packageVerificationCode = if (files.isNotEmpty()) {
156+
ortResult.getPackageVerificationCode(id, type)?.let {
157+
SpdxPackageVerificationCode(packageVerificationCodeValue = it)
158+
}
159+
} else {
160+
// A package verification code is only allowed if files are present. Some other properties are derived from
161+
// this as well.
162+
null
155163
}
156164

157165
val resolvedLicenseInfo = licenseInfoResolver.resolveLicenseInfo(id).filterExcluded()
@@ -181,6 +189,7 @@ internal fun Package.toSpdxPackage(
181189
},
182190
externalRefs = if (type == SpdxPackageType.PROJECT) emptyList() else toSpdxExternalReferences(),
183191
filesAnalyzed = packageVerificationCode != null,
192+
hasFiles = files.map { it.spdxId },
184193
homepage = homepageUrl.nullOrBlankToSpdxNone(),
185194
licenseComments = "effectiveLicense: ${effectiveLicense?.toString().nullOrBlankToSpdxNone()}",
186195
licenseConcluded = when (type) {

plugins/reporters/spdx/src/main/kotlin/SpdxDocumentModelMapper.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,9 @@ internal object SpdxDocumentModelMapper {
9494
val spdxProjectPackage = project.toPackage().toSpdxPackage(
9595
SpdxPackageType.PROJECT,
9696
licenseInfoResolver,
97-
ortResult
98-
).copy(hasFiles = filesForProject.map { it.spdxId })
97+
ortResult,
98+
filesForProject
99+
)
99100

100101
ortResult.getDependencies(
101102
id = project.id,
@@ -138,8 +139,9 @@ internal object SpdxDocumentModelMapper {
138139
val vcsPackage = pkg.toSpdxPackage(
139140
SpdxPackageType.VCS_PACKAGE,
140141
licenseInfoResolver,
141-
ortResult
142-
).copy(hasFiles = filesForPackage.map { it.spdxId })
142+
ortResult,
143+
filesForPackage
144+
)
143145

144146
val vcsPackageRelationShip = SpdxRelationship(
145147
spdxElementId = binaryPackage.spdxId,
@@ -163,8 +165,9 @@ internal object SpdxDocumentModelMapper {
163165
val sourceArtifactPackage = pkg.toSpdxPackage(
164166
SpdxPackageType.SOURCE_PACKAGE,
165167
licenseInfoResolver,
166-
ortResult
167-
).copy(hasFiles = filesForPackage.map { it.spdxId })
168+
ortResult,
169+
filesForPackage
170+
)
168171

169172
val sourceArtifactPackageRelationship = SpdxRelationship(
170173
spdxElementId = binaryPackage.spdxId,
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright (C) 2026 The ORT Project Copyright Holders <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
* License-Filename: LICENSE
18+
*/
19+
20+
package org.ossreviewtoolkit.plugins.reporters.spdx
21+
22+
import io.kotest.core.spec.style.WordSpec
23+
import io.kotest.matchers.collections.beEmpty
24+
import io.kotest.matchers.collections.shouldContainExactly
25+
import io.kotest.matchers.nulls.beNull
26+
import io.kotest.matchers.should
27+
import io.kotest.matchers.shouldBe
28+
import io.kotest.matchers.shouldNot
29+
30+
import io.mockk.mockk
31+
32+
import org.ossreviewtoolkit.model.Identifier
33+
import org.ossreviewtoolkit.utils.spdxdocument.model.SpdxFile
34+
35+
class ExtensionsTest : WordSpec({
36+
"toSpdxPackage" should {
37+
"set filesAnalyzed to true if there is a package verification code and files are available" {
38+
val id = Identifier("Maven:pkg1-grp:pkg1:0.0.1")
39+
val pkg = requireNotNull(ORT_RESULT.getPackage(id)?.metadata)
40+
val spdxFile = SpdxFile(
41+
spdxId = "SPDXRef-Pkg1-testFile",
42+
filename = "testFile",
43+
checksums = emptyList(),
44+
licenseConcluded = "MIT"
45+
)
46+
val files = listOf(spdxFile)
47+
48+
val spdxPkg = pkg.toSpdxPackage(
49+
type = SpdxPackageType.VCS_PACKAGE,
50+
licenseInfoResolver = mockk(relaxed = true),
51+
ortResult = ORT_RESULT,
52+
files = files
53+
)
54+
55+
spdxPkg.filesAnalyzed shouldBe true
56+
spdxPkg.hasFiles shouldContainExactly listOf(spdxFile.spdxId)
57+
spdxPkg.packageVerificationCode shouldNot beNull()
58+
}
59+
60+
"set filesAnalyzed to false and no verification code if there are no files available" {
61+
val id = Identifier("Maven:pkg1-grp:pkg1:0.0.1")
62+
val pkg = requireNotNull(ORT_RESULT.getPackage(id)?.metadata)
63+
64+
val spdxPkg = pkg.toSpdxPackage(
65+
type = SpdxPackageType.VCS_PACKAGE,
66+
licenseInfoResolver = mockk(relaxed = true),
67+
ortResult = ORT_RESULT
68+
)
69+
70+
spdxPkg.filesAnalyzed shouldBe false
71+
spdxPkg.hasFiles should beEmpty()
72+
spdxPkg.packageVerificationCode should beNull()
73+
}
74+
}
75+
})

0 commit comments

Comments
 (0)