@@ -22,39 +22,51 @@ package org.ossreviewtoolkit.plugins.packagemanagers.maven
2222import java.io.File
2323import java.nio.file.Path
2424import java.util.concurrent.atomic.AtomicReference
25+ import java.util.jar.JarInputStream
26+ import java.util.jar.Manifest
2527
2628import kotlin.io.path.invariantSeparatorsPathString
2729
2830import org.apache.logging.log4j.kotlin.logger
31+ import org.apache.logging.log4j.kotlin.loggerOf
2932import org.apache.maven.AbstractMavenLifecycleParticipant
3033import org.apache.maven.cli.MavenCli
3134import org.apache.maven.execution.MavenSession
35+ import org.apache.maven.model.Scm
3236import org.apache.maven.project.MavenProject
37+ import org.apache.maven.repository.RepositorySystem
3338
3439import org.codehaus.plexus.PlexusContainer
3540import org.codehaus.plexus.classworlds.ClassWorld
3641
42+ import org.eclipse.aether.artifact.Artifact
3743import org.eclipse.aether.graph.DependencyNode
3844
3945import org.ossreviewtoolkit.analyzer.AbstractPackageManagerFactory
4046import org.ossreviewtoolkit.analyzer.PackageManager
4147import org.ossreviewtoolkit.analyzer.PackageManagerResult
4248import org.ossreviewtoolkit.analyzer.ProjectResults
4349import org.ossreviewtoolkit.model.DependencyGraph
50+ import org.ossreviewtoolkit.model.Identifier
4451import org.ossreviewtoolkit.model.Issue
52+ import org.ossreviewtoolkit.model.Package
4553import org.ossreviewtoolkit.model.ProjectAnalyzerResult
54+ import org.ossreviewtoolkit.model.RemoteArtifact
4655import org.ossreviewtoolkit.model.config.AnalyzerConfiguration
4756import org.ossreviewtoolkit.model.config.RepositoryConfiguration
4857import org.ossreviewtoolkit.model.createAndLogIssue
4958import org.ossreviewtoolkit.model.utils.DependencyGraphBuilder
5059import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.LocalProjectWorkspaceReader
5160import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.MavenDependencyHandler
5261import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.MavenSupport
62+ import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.PackageResolverFun
5363import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.createIssuesForAutoGeneratedPoms
5464import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.identifier
5565import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.internalId
5666import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.isTychoProject
5767import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.parseDependencyTree
68+ import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.parseScm
69+ import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.processDeclaredLicenses
5870import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.toOrtProject
5971import org.ossreviewtoolkit.utils.ort.createOrtTempFile
6072
@@ -151,8 +163,8 @@ class Tycho(
151163 mavenSupport : MavenSupport ,
152164 mavenProjects : Map <String , MavenProject >
153165 ): DependencyGraphBuilder <DependencyNode > {
154- val dependencyHandler =
155- MavenDependencyHandler (managerName, projectType, mavenProjects, mavenSupport.defaultPackageResolverFun() )
166+ val resolverFun = tychoPackageResolverFun(mavenSupport.defaultPackageResolverFun())
167+ val dependencyHandler = MavenDependencyHandler (managerName, projectType, mavenProjects, resolverFun )
156168 return DependencyGraphBuilder (dependencyHandler)
157169 }
158170
@@ -329,3 +341,104 @@ internal class TychoProjectsCollector : AbstractMavenLifecycleParticipant() {
329341 logger.info { " Found ${builtProjects.size} projects during build." }
330342 }
331343}
344+
345+ private val tychoLogger = loggerOf(Tycho ::class .java)
346+
347+ /* *
348+ * Return a [PackageResolverFun] that can handle special OSGi artifacts detected during a Tycho analysis.
349+ * OSGi bundles retrieved from a P2 repository cannot be handled by the default resolver function, which is not aware
350+ * of the special repository layout. Also, they do not have a POM with metadata, but may contain some metadata in
351+ * their OSGi manifest.
352+ * This function first delegates to the given [delegate] function to handle normal packages in the usual way. If this
353+ * fails, it checks whether the dependency can be found in the p2 cache of the local Maven repository pointed to by
354+ * [localRepositoryRoot]. (Since the Tycho project has been built, the cache should be populated.) If this is the case,
355+ * it tries to obtain metadata from the OSGi manifest of the bundle. This approach is partly inspired by
356+ * https://github.com/OpenNTF/p2-layout-provider.
357+ */
358+ internal fun tychoPackageResolverFun (
359+ delegate : PackageResolverFun ,
360+ localRepositoryRoot : File = RepositorySystem .defaultUserLocalRepository
361+ ): PackageResolverFun =
362+ { dependency ->
363+ runCatching {
364+ delegate(dependency)
365+ }.recoverCatching { exception ->
366+ createPackageFromLocalArtifact(dependency.artifact, localRepositoryRoot)
367+ ? : throw exception
368+ }.getOrThrow()
369+ }
370+
371+ /* *
372+ * Create a [Package] for the given [artifact] obtaining metadata from the given [manifest]. This function is used for
373+ * OSGi artifacts for which no POM is available.
374+ */
375+ internal fun createPackageFromManifest (artifact : Artifact , manifest : Manifest ): Package =
376+ with (manifest.mainAttributes) {
377+ val declaredLicenses = setOfNotNull(getValue(" Bundle-License" ))
378+ val declaredLicensesProcessed = processDeclaredLicenses(declaredLicenses)
379+ val authors = setOfNotNull(getValue(" Bundle-Vendor" ))
380+
381+ val vcs = parseScm(getValue(" Eclipse-SourceReferences" ).toScm(), artifact.identifier())
382+
383+ Package (
384+ id = Identifier (
385+ type = " Maven" ,
386+ namespace = artifact.groupId,
387+ name = artifact.artifactId,
388+ version = artifact.version
389+ ),
390+ authors = authors,
391+ declaredLicenses = declaredLicenses,
392+ declaredLicensesProcessed = declaredLicensesProcessed,
393+ description = getValue(" Bundle-Description" ).orEmpty(),
394+ homepageUrl = " " ,
395+ binaryArtifact = RemoteArtifact .EMPTY ,
396+ sourceArtifact = RemoteArtifact .EMPTY ,
397+ vcs = vcs,
398+ vcsProcessed = vcs
399+ )
400+ }
401+
402+ /* *
403+ * Try to find an OSGi artifact in the local Maven repository at [localRepositoryRoot] that matches the given
404+ * [artifact]. If found, create a [Package] from the artifact's OSGi manifest.
405+ */
406+ private fun createPackageFromLocalArtifact (artifact : Artifact , localRepositoryRoot : File ): Package ? {
407+ val artifactFile = localRepositoryRoot.resolve(" p2/osgi/bundle" )
408+ .resolve(artifact.artifactId)
409+ .resolve(artifact.version)
410+ .resolve(" ${artifact.artifactId} -${artifact.version} .jar" )
411+
412+ tychoLogger.debug {
413+ " Looking up package metadata for '${artifact.identifier()} ' in local repository '$artifactFile '."
414+ }
415+
416+ if (! artifactFile.isFile) return null
417+
418+ tychoLogger.info {
419+ " Reading metadata for '${artifact.identifier()} ' from local repository '$artifactFile '."
420+ }
421+
422+ return JarInputStream (artifactFile.inputStream()).use { jar ->
423+ createPackageFromManifest(artifact, jar.manifest ? : Manifest ())
424+ }
425+ }
426+
427+ /* *
428+ * Parse this string with a source references manifest header to a [Scm] structure.
429+ * See https://wiki.eclipse.org/PDE/UI/SourceReferences.
430+ */
431+ private fun String?.toScm (): Scm ? =
432+ this ?.split(' ,' )?.firstOrNull()
433+ ?.let { scmInfo ->
434+ val fields = scmInfo.split(' ;' )
435+ val properties = fields.drop(1 ).filter { " =" in it }.associate { field ->
436+ val (key, value) = field.split(' =' )
437+ key to value.removeSurrounding(" \" " )
438+ }
439+
440+ Scm ().apply {
441+ connection = fields[0 ]
442+ tag = properties[" tag" ]
443+ }
444+ }
0 commit comments