Skip to content

Commit d929ebc

Browse files
committed
refactor(spdx): Move various functions for a better overview
Split extension functions to separate files and move top-level function to below the class. This way "TooManyFunctions" is not exceeded anymore. This requires to parameterize `toIdentifier()` as it does not have access to `projectType` anymore, but that change is anyway required by follow-up changes. Signed-off-by: Sebastian Schuberth <sebastian@doubleopen.org>
1 parent 1ae68cf commit d929ebc

File tree

4 files changed

+315
-250
lines changed

4 files changed

+315
-250
lines changed

plugins/package-managers/spdx/src/main/kotlin/SpdxDocumentFile.kt

Lines changed: 52 additions & 250 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
* License-Filename: LICENSE
1818
*/
1919

20-
@file:Suppress("TooManyFunctions")
21-
2220
package org.ossreviewtoolkit.plugins.packagemanagers.spdx
2321

2422
import java.io.File
@@ -32,44 +30,36 @@ import org.ossreviewtoolkit.analyzer.PackageManagerResult
3230
import org.ossreviewtoolkit.analyzer.determineEnabledPackageManagers
3331
import org.ossreviewtoolkit.analyzer.toPackageReference
3432
import org.ossreviewtoolkit.downloader.VersionControlSystem
35-
import org.ossreviewtoolkit.model.ArtifactProvenance
36-
import org.ossreviewtoolkit.model.Hash
37-
import org.ossreviewtoolkit.model.HashAlgorithm
38-
import org.ossreviewtoolkit.model.Identifier
3933
import org.ossreviewtoolkit.model.Issue
4034
import org.ossreviewtoolkit.model.Package
4135
import org.ossreviewtoolkit.model.PackageLinkage
4236
import org.ossreviewtoolkit.model.PackageReference
4337
import org.ossreviewtoolkit.model.Project
4438
import org.ossreviewtoolkit.model.ProjectAnalyzerResult
45-
import org.ossreviewtoolkit.model.RemoteArtifact
4639
import org.ossreviewtoolkit.model.Scope
4740
import org.ossreviewtoolkit.model.VcsInfo
48-
import org.ossreviewtoolkit.model.VcsType
4941
import org.ossreviewtoolkit.model.config.AnalyzerConfiguration
5042
import org.ossreviewtoolkit.model.config.Excludes
5143
import org.ossreviewtoolkit.model.config.Includes
5244
import org.ossreviewtoolkit.model.createAndLogIssue
53-
import org.ossreviewtoolkit.model.orEmpty
54-
import org.ossreviewtoolkit.model.utils.toPackageUrl
55-
import org.ossreviewtoolkit.model.utils.toPurl
5645
import org.ossreviewtoolkit.plugins.api.OrtPlugin
5746
import org.ossreviewtoolkit.plugins.api.PluginConfig
5847
import org.ossreviewtoolkit.plugins.api.PluginDescriptor
5948
import org.ossreviewtoolkit.plugins.packagemanagers.spdx.utils.SpdxDocumentCache
6049
import org.ossreviewtoolkit.plugins.packagemanagers.spdx.utils.SpdxResolvedDocument
61-
import org.ossreviewtoolkit.utils.common.collapseWhitespace
62-
import org.ossreviewtoolkit.utils.common.withoutPrefix
63-
import org.ossreviewtoolkit.utils.spdx.SpdxConstants
64-
import org.ossreviewtoolkit.utils.spdx.SpdxExpression
65-
import org.ossreviewtoolkit.utils.spdx.toSpdx
66-
import org.ossreviewtoolkit.utils.spdxdocument.model.SpdxDocument
67-
import org.ossreviewtoolkit.utils.spdxdocument.model.SpdxExternalDocumentReference
68-
import org.ossreviewtoolkit.utils.spdxdocument.model.SpdxExternalReference
50+
import org.ossreviewtoolkit.plugins.packagemanagers.spdx.utils.extractScopeFromExternalReferences
51+
import org.ossreviewtoolkit.plugins.packagemanagers.spdx.utils.isExternalDocumentReferenceId
52+
import org.ossreviewtoolkit.plugins.packagemanagers.spdx.utils.locateCpe
53+
import org.ossreviewtoolkit.plugins.packagemanagers.spdx.utils.mapNotPresentToEmpty
54+
import org.ossreviewtoolkit.plugins.packagemanagers.spdx.utils.projectPackage
55+
import org.ossreviewtoolkit.plugins.packagemanagers.spdx.utils.toIdentifier
56+
import org.ossreviewtoolkit.plugins.packagemanagers.spdx.utils.toPackage
57+
import org.ossreviewtoolkit.plugins.packagemanagers.spdx.utils.wrapPresentInSet
6958
import org.ossreviewtoolkit.utils.spdxdocument.model.SpdxPackage
7059
import org.ossreviewtoolkit.utils.spdxdocument.model.SpdxRelationship
7160

7261
private const val PROJECT_TYPE = "SpdxDocumentFile"
62+
internal const val PACKAGE_TYPE_SPDX = "SpdxDocumentFile"
7363

7464
private const val DEFAULT_SCOPE_NAME = "default"
7565

@@ -80,182 +70,6 @@ private val SPDX_LINKAGE_RELATIONSHIPS = mapOf(
8070

8171
private val SPDX_SCOPE_RELATIONSHIPS = SpdxRelationship.Type.entries.filter { it.name.endsWith("_DEPENDENCY_OF") }
8272

83-
private val SPDX_VCS_PREFIXES = mapOf(
84-
"git+" to VcsType.GIT,
85-
"hg+" to VcsType.MERCURIAL,
86-
"bzr+" to VcsType.UNKNOWN,
87-
"svn+" to VcsType.SUBVERSION
88-
)
89-
90-
/**
91-
* Return the [SpdxPackage] in the [SpdxDocument] that denotes a project, or null if no project but only packages are
92-
* defined.
93-
*/
94-
internal fun SpdxDocument.projectPackage(): SpdxPackage? =
95-
// An SpdxDocument that describes a project must have at least 2 packages, one for the project itself, and another
96-
// one for at least one dependency package.
97-
packages.takeIf { it.size > 1 || (it.size == 1 && externalDocumentRefs.isNotEmpty()) }
98-
// The package that describes a project must have an "empty" package filename (as the "filename" is the project
99-
// directory itself).
100-
?.singleOrNull { it.packageFilename.isEmpty() || it.packageFilename == "." }
101-
102-
/**
103-
* Try to find an [SpdxExternalReference] in this [SpdxPackage] of type purl from which the scope of a
104-
* package manager dependency can be extracted. Return this scope or *null* if cannot be determined.
105-
*/
106-
internal fun SpdxPackage.extractScopeFromExternalReferences(): String? =
107-
externalRefs.filter { it.referenceType == SpdxExternalReference.Type.Purl }
108-
.firstNotNullOfOrNull { it.referenceLocator.toPackageUrl()?.qualifiers?.get("scope") }
109-
110-
/**
111-
* Return the declared license to be used in ORT's data model, which expects a not present value to be an empty set
112-
* instead of NONE or NOASSERTION.
113-
*/
114-
private fun SpdxPackage.getDeclaredLicense(): Set<String> =
115-
setOfNotNull(licenseDeclared.takeIf { SpdxConstants.isPresent(it) })
116-
117-
/**
118-
* Return the concluded license to be used in ORT's data model, which uses null instead of NOASSERTION.
119-
*/
120-
private fun SpdxPackage.getConcludedLicense(): SpdxExpression? =
121-
licenseConcluded.takeUnless { it == SpdxConstants.NOASSERTION }?.toSpdx()
122-
123-
/**
124-
* Return a [RemoteArtifact] for the artifact that the [downloadLocation][SpdxPackage.downloadLocation] points to. If
125-
* the download location is a "not present" value, or if it points to a VCS location instead, return null.
126-
*/
127-
private fun SpdxPackage.getRemoteArtifact(): RemoteArtifact? =
128-
when {
129-
SpdxConstants.isNotPresent(downloadLocation) -> null
130-
131-
SPDX_VCS_PREFIXES.any { (prefix, _) -> downloadLocation.startsWith(prefix) } -> null
132-
133-
else -> {
134-
if (downloadLocation.endsWith(".git")) {
135-
logger.warn {
136-
"The download location $downloadLocation of SPDX package '$spdxId' looks like a Git repository " +
137-
"URL but it lacks the 'git+' prefix and thus will be treated as an artifact URL."
138-
}
139-
}
140-
141-
RemoteArtifact(downloadLocation, Hash.NONE)
142-
}
143-
}
144-
145-
/**
146-
* Return the [VcsInfo] contained in the [downloadLocation][SpdxPackage.downloadLocation], or null if the download
147-
* location is a "not present" value / does not point to a VCS location.
148-
*/
149-
internal fun SpdxPackage.getVcsInfo(): VcsInfo? {
150-
if (SpdxConstants.isNotPresent(downloadLocation)) return null
151-
152-
return SPDX_VCS_PREFIXES.mapNotNull { (prefix, vcsType) ->
153-
downloadLocation.withoutPrefix(prefix)?.let { url ->
154-
var vcsUrl = url
155-
156-
val vcsPath = vcsUrl.substringAfterLast('#', "")
157-
vcsUrl = vcsUrl.removeSuffix("#$vcsPath")
158-
159-
val vcsRevision = vcsUrl.substringAfterLast('@', "")
160-
vcsUrl = vcsUrl.removeSuffix("@$vcsRevision")
161-
162-
VcsInfo(vcsType, vcsUrl, vcsRevision, path = vcsPath)
163-
}
164-
}.firstOrNull()
165-
}
166-
167-
/**
168-
* Return the location of the first [external reference][SpdxExternalReference] of the given [type] in this
169-
* [SpdxPackage], or null if there is no such reference.
170-
*/
171-
private fun SpdxPackage.locateExternalReference(type: SpdxExternalReference.Type): String? =
172-
externalRefs.find { it.referenceType == type }?.referenceLocator
173-
174-
/**
175-
* Return a CPE identifier for this package if present. Search for all CPE versions.
176-
*/
177-
private fun SpdxPackage.locateCpe(): String? =
178-
locateExternalReference(SpdxExternalReference.Type.Cpe23Type)
179-
?: locateExternalReference(SpdxExternalReference.Type.Cpe22Type)
180-
181-
/**
182-
* Return whether the string has the format of an [SpdxExternalDocumentReference], with or without an additional
183-
* package id.
184-
*/
185-
private fun String.isExternalDocumentReferenceId(): Boolean = startsWith(SpdxConstants.DOCUMENT_REF_PREFIX)
186-
187-
/**
188-
* Map a "not preset" SPDX value, i.e. NONE or NOASSERTION, to an empty string.
189-
*/
190-
private fun String.mapNotPresentToEmpty(): String = takeUnless { SpdxConstants.isNotPresent(it) }.orEmpty()
191-
192-
/**
193-
* Sanitize a string for use as an [Identifier] property where colons are not supported by replacing them with spaces,
194-
* trimming, and finally collapsing multiple consecutive spaces.
195-
*/
196-
private fun String.sanitize(): String = replace(':', ' ').collapseWhitespace()
197-
198-
/**
199-
* Wrap any "present" SPDX value in a sorted set, or return an empty sorted set otherwise.
200-
*/
201-
private fun String?.wrapPresentInSet(): Set<String> {
202-
if (SpdxConstants.isPresent(this)) {
203-
withoutPrefix(SpdxConstants.PERSON)?.let { persons ->
204-
// In case of a person, allow a comma-separated list of persons.
205-
return persons.split(',').mapTo(mutableSetOf()) { it.trim() }
206-
}
207-
208-
// Do not split an organization like "Acme, Inc." by comma.
209-
withoutPrefix(SpdxConstants.ORGANIZATION)?.let {
210-
return setOf(it.trim())
211-
}
212-
}
213-
214-
return emptySet()
215-
}
216-
217-
/**
218-
* Return the [PackageLinkage] between [dependency] and [dependant] as specified in [relationships]. If no
219-
* relationship is found, return [PackageLinkage.DYNAMIC].
220-
*/
221-
private fun getLinkageForDependency(
222-
dependency: SpdxPackage,
223-
dependant: String,
224-
relationships: List<SpdxRelationship>
225-
): PackageLinkage =
226-
relationships.mapNotNull { relation ->
227-
SPDX_LINKAGE_RELATIONSHIPS[relation.relationshipType]?.takeIf {
228-
val relationId = if (relation.relatedSpdxElement.isExternalDocumentReferenceId()) {
229-
relation.relatedSpdxElement.substringAfter(":")
230-
} else {
231-
relation.relatedSpdxElement
232-
}
233-
234-
relationId == dependency.spdxId && relation.spdxElementId == dependant
235-
}
236-
}.singleOrNull() ?: PackageLinkage.DYNAMIC
237-
238-
/**
239-
* Return true if the [relation] as defined in [relationships] describes an [SPDX_LINKAGE_RELATIONSHIPS] in the
240-
* [DEFAULT_SCOPE_NAME] so that the [source] depends on the [target].
241-
*/
242-
private fun hasDefaultScopeLinkage(
243-
source: String,
244-
target: String,
245-
relation: SpdxRelationship.Type,
246-
relationships: List<SpdxRelationship>
247-
): Boolean {
248-
if (relation !in SPDX_LINKAGE_RELATIONSHIPS) return false
249-
250-
val hasScopeRelationship = relationships.any {
251-
it.relationshipType in SPDX_SCOPE_RELATIONSHIPS
252-
// Scope relationships are defined in "reverse" as a "dependency of".
253-
&& it.relatedSpdxElement == source && it.spdxElementId == target
254-
}
255-
256-
return !hasScopeRelationship
257-
}
258-
25973
/**
26074
* A "fake" package manager implementation that uses SPDX documents as definition files to declare projects and describe
26175
* packages. See https://github.com/spdx/spdx-spec/issues/439 for details.
@@ -271,60 +85,6 @@ class SpdxDocumentFile(override val descriptor: PluginDescriptor = SpdxDocumentF
27185

27286
private val spdxDocumentCache = SpdxDocumentCache()
27387

274-
/**
275-
* Create an [Identifier] out of this [SpdxPackage].
276-
*/
277-
private fun SpdxPackage.toIdentifier() =
278-
Identifier(
279-
type = projectType,
280-
namespace = listOfNotNull(supplier, originator).firstOrNull()
281-
?.withoutPrefix(SpdxConstants.ORGANIZATION).orEmpty().sanitize(),
282-
name = name.sanitize(),
283-
version = versionInfo.sanitize()
284-
)
285-
286-
/**
287-
* Create a [Package] out of this [SpdxPackage].
288-
*/
289-
private fun SpdxPackage.toPackage(definitionFile: File?, doc: SpdxResolvedDocument): Package {
290-
val packageDescription = description.ifEmpty { summary }
291-
292-
// If the VCS information cannot be determined from the VCS working tree itself, fall back to try getting it
293-
// from the download location.
294-
val packageDir = definitionFile?.resolveSibling(packageFilename)
295-
val vcs = packageDir?.let { VersionControlSystem.forDirectory(it)?.getInfo() } ?: getVcsInfo().orEmpty()
296-
297-
val generatedFromRelations = doc.relationships.filter {
298-
it.relationshipType == SpdxRelationship.Type.GENERATED_FROM
299-
}
300-
301-
val isBinaryArtifact = generatedFromRelations.any { it.spdxElementId == spdxId }
302-
&& generatedFromRelations.none { it.relatedSpdxElement == spdxId }
303-
304-
val id = toIdentifier()
305-
val artifact = getRemoteArtifact()
306-
307-
val purl = locateExternalReference(SpdxExternalReference.Type.Purl)
308-
?: artifact
309-
?.let { if (it.hash.algorithm in HashAlgorithm.VERIFIABLE) it else it.copy(hash = Hash.NONE) }
310-
?.let { id.toPurl(ArtifactProvenance(it)) }
311-
?: id.toPurl()
312-
313-
return Package(
314-
id = id,
315-
purl = purl,
316-
cpe = locateCpe(),
317-
authors = originator.wrapPresentInSet(),
318-
declaredLicenses = getDeclaredLicense(),
319-
concludedLicense = getConcludedLicense(),
320-
description = packageDescription,
321-
homepageUrl = homepage.mapNotPresentToEmpty(),
322-
binaryArtifact = artifact.takeIf { isBinaryArtifact }.orEmpty(),
323-
sourceArtifact = artifact.takeUnless { isBinaryArtifact }.orEmpty(),
324-
vcs = vcs
325-
)
326-
}
327-
32888
/**
32989
* Return the dependencies of the package with the given [pkgId] defined in [doc] of the
33090
* [SpdxRelationship.Type.DEPENDENCY_OF] type. Identified dependencies are mapped to ORT [Package]s and then
@@ -510,7 +270,7 @@ class SpdxDocumentFile(override val descriptor: PluginDescriptor = SpdxDocumentF
510270
)
511271

512272
val project = Project(
513-
id = projectPackage.toIdentifier(),
273+
id = projectPackage.toIdentifier(projectType),
514274
cpe = projectPackage.locateCpe(),
515275
definitionFilePath = VersionControlSystem.getPathInfo(definitionFile).path,
516276
authors = projectPackage.originator.wrapPresentInSet(),
@@ -572,3 +332,45 @@ internal fun getPackageManagerDependency(
572332

573333
return null
574334
}
335+
336+
/**
337+
* Return the [PackageLinkage] between [dependency] and [dependant] as specified in [relationships]. If no
338+
* relationship is found, return [PackageLinkage.DYNAMIC].
339+
*/
340+
private fun getLinkageForDependency(
341+
dependency: SpdxPackage,
342+
dependant: String,
343+
relationships: List<SpdxRelationship>
344+
): PackageLinkage =
345+
relationships.mapNotNull { relation ->
346+
SPDX_LINKAGE_RELATIONSHIPS[relation.relationshipType]?.takeIf {
347+
val relationId = if (relation.relatedSpdxElement.isExternalDocumentReferenceId()) {
348+
relation.relatedSpdxElement.substringAfter(":")
349+
} else {
350+
relation.relatedSpdxElement
351+
}
352+
353+
relationId == dependency.spdxId && relation.spdxElementId == dependant
354+
}
355+
}.singleOrNull() ?: PackageLinkage.DYNAMIC
356+
357+
/**
358+
* Return true if the [relation] as defined in [relationships] describes an [SPDX_LINKAGE_RELATIONSHIPS] in the
359+
* [DEFAULT_SCOPE_NAME] so that the [source] depends on the [target].
360+
*/
361+
private fun hasDefaultScopeLinkage(
362+
source: String,
363+
target: String,
364+
relation: SpdxRelationship.Type,
365+
relationships: List<SpdxRelationship>
366+
): Boolean {
367+
if (relation !in SPDX_LINKAGE_RELATIONSHIPS) return false
368+
369+
val hasScopeRelationship = relationships.any {
370+
it.relationshipType in SPDX_SCOPE_RELATIONSHIPS
371+
// Scope relationships are defined in "reverse" as a "dependency of".
372+
&& it.relatedSpdxElement == source && it.spdxElementId == target
373+
}
374+
375+
return !hasScopeRelationship
376+
}

0 commit comments

Comments
 (0)