1717 * License-Filename: LICENSE
1818 */
1919
20- @file:Suppress(" TooManyFunctions" )
21-
2220package org.ossreviewtoolkit.plugins.packagemanagers.spdx
2321
2422import java.io.File
@@ -32,44 +30,36 @@ import org.ossreviewtoolkit.analyzer.PackageManagerResult
3230import org.ossreviewtoolkit.analyzer.determineEnabledPackageManagers
3331import org.ossreviewtoolkit.analyzer.toPackageReference
3432import 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
3933import org.ossreviewtoolkit.model.Issue
4034import org.ossreviewtoolkit.model.Package
4135import org.ossreviewtoolkit.model.PackageLinkage
4236import org.ossreviewtoolkit.model.PackageReference
4337import org.ossreviewtoolkit.model.Project
4438import org.ossreviewtoolkit.model.ProjectAnalyzerResult
45- import org.ossreviewtoolkit.model.RemoteArtifact
4639import org.ossreviewtoolkit.model.Scope
4740import org.ossreviewtoolkit.model.VcsInfo
48- import org.ossreviewtoolkit.model.VcsType
4941import org.ossreviewtoolkit.model.config.AnalyzerConfiguration
5042import org.ossreviewtoolkit.model.config.Excludes
5143import org.ossreviewtoolkit.model.config.Includes
5244import org.ossreviewtoolkit.model.createAndLogIssue
53- import org.ossreviewtoolkit.model.orEmpty
54- import org.ossreviewtoolkit.model.utils.toPackageUrl
55- import org.ossreviewtoolkit.model.utils.toPurl
5645import org.ossreviewtoolkit.plugins.api.OrtPlugin
5746import org.ossreviewtoolkit.plugins.api.PluginConfig
5847import org.ossreviewtoolkit.plugins.api.PluginDescriptor
5948import org.ossreviewtoolkit.plugins.packagemanagers.spdx.utils.SpdxDocumentCache
6049import 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
6958import org.ossreviewtoolkit.utils.spdxdocument.model.SpdxPackage
7059import org.ossreviewtoolkit.utils.spdxdocument.model.SpdxRelationship
7160
7261private const val PROJECT_TYPE = " SpdxDocumentFile"
62+ internal const val PACKAGE_TYPE_SPDX = " SpdxDocumentFile"
7363
7464private const val DEFAULT_SCOPE_NAME = " default"
7565
@@ -80,182 +70,6 @@ private val SPDX_LINKAGE_RELATIONSHIPS = mapOf(
8070
8171private 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