Skip to content

Commit 17f979e

Browse files
committedFeb 27, 2025
feat(Maven): Generate hints about auto-generated POMs for Tycho
To make the required functionality available to the Tycho implementation, extract it as an extension function. Signed-off-by: Oliver Heger <oliver.heger@bosch.io>
1 parent c3f75e3 commit 17f979e

File tree

4 files changed

+167
-20
lines changed

4 files changed

+167
-20
lines changed
 

‎plugins/package-managers/maven/src/main/kotlin/Maven.kt

+2-14
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,13 @@ import org.ossreviewtoolkit.analyzer.PackageManager
3030
import org.ossreviewtoolkit.analyzer.PackageManagerResult
3131
import org.ossreviewtoolkit.model.DependencyGraph
3232
import org.ossreviewtoolkit.model.ProjectAnalyzerResult
33-
import org.ossreviewtoolkit.model.Severity
3433
import org.ossreviewtoolkit.model.config.AnalyzerConfiguration
3534
import org.ossreviewtoolkit.model.config.RepositoryConfiguration
36-
import org.ossreviewtoolkit.model.createAndLogIssue
3735
import org.ossreviewtoolkit.model.utils.DependencyGraphBuilder
3836
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.LocalProjectWorkspaceReader
3937
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.MavenDependencyHandler
4038
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.MavenSupport
39+
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.createIssuesForAutoGeneratedPoms
4140
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.identifier
4241
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.isTychoProject
4342
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.toOrtProject
@@ -130,18 +129,7 @@ class Maven(
130129
graphBuilder.scopesFor(projectId)
131130
)
132131

133-
val issues = (graphBuilder.packages() - knownPackages).mapNotNull { pkg ->
134-
if (pkg.description == "POM was created by Sonatype Nexus") {
135-
createAndLogIssue(
136-
managerName,
137-
"Package '${pkg.id.toCoordinates()}' seems to use an auto-generated POM which might lack metadata.",
138-
Severity.HINT
139-
)
140-
} else {
141-
null
142-
}
143-
}
144-
132+
val issues = (graphBuilder.packages() - knownPackages).createIssuesForAutoGeneratedPoms(managerName)
145133
return listOf(ProjectAnalyzerResult(project, emptySet(), issues))
146134
}
147135

‎plugins/package-managers/maven/src/main/kotlin/Tycho.kt

+22-6
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import org.ossreviewtoolkit.analyzer.PackageManager
4141
import org.ossreviewtoolkit.analyzer.PackageManagerResult
4242
import org.ossreviewtoolkit.analyzer.ProjectResults
4343
import org.ossreviewtoolkit.model.DependencyGraph
44+
import org.ossreviewtoolkit.model.Issue
4445
import org.ossreviewtoolkit.model.ProjectAnalyzerResult
4546
import org.ossreviewtoolkit.model.config.AnalyzerConfiguration
4647
import org.ossreviewtoolkit.model.config.RepositoryConfiguration
@@ -50,6 +51,7 @@ import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.LocalProjectWork
5051
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.MavenDependencyHandler
5152
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.MavenLogger
5253
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.MavenSupport
54+
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.createIssuesForAutoGeneratedPoms
5355
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.identifier
5456
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.internalId
5557
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.isTychoProject
@@ -92,10 +94,7 @@ class Tycho(
9294
// TODO: Create issues for a failed build and projects for which dependencies could not be resolved.
9395

9496
val resolvedProjects = createMavenSupport(collector).use { mavenSupport ->
95-
val dependencyHandler =
96-
MavenDependencyHandler(managerName, projectType, mavenSupport, collector.mavenProjects, false)
97-
98-
graphBuilder = DependencyGraphBuilder(dependencyHandler)
97+
graphBuilder = createGraphBuilder(mavenSupport, collector.mavenProjects)
9998

10099
buildLog.inputStream().use { stream ->
101100
parseDependencyTree(stream, collector.mavenProjects.values).map { projectNode ->
@@ -108,6 +107,10 @@ class Tycho(
108107

109108
buildLog.delete()
110109

110+
// Assign issues that are global to the build to the root project.
111+
val rootIssues = mutableListOf<Issue>()
112+
graphBuilder.packages().createIssuesForAutoGeneratedPoms(managerName, rootIssues)
113+
111114
return resolvedProjects.map { mavenProject ->
112115
val projectId = mavenProject.identifier(projectType)
113116
val project = mavenProject.toOrtProject(
@@ -116,7 +119,8 @@ class Tycho(
116119
mavenProject.file.parentFile,
117120
graphBuilder.scopesFor(projectId)
118121
)
119-
ProjectAnalyzerResult(project, emptySet())
122+
val issues = rootIssues.takeIf { mavenProject.file == definitionFile } ?: emptyList()
123+
ProjectAnalyzerResult(project, emptySet(), issues)
120124
}
121125
}
122126

@@ -128,7 +132,7 @@ class Tycho(
128132
* that it is invoked during the build. Also install a specific logger that forwards the output of the dependency
129133
* tree plugin to the given [outputStream].
130134
*/
131-
private fun createMavenCli(collector: TychoProjectsCollector, outputStream: PrintStream): MavenCli =
135+
internal fun createMavenCli(collector: TychoProjectsCollector, outputStream: PrintStream): MavenCli =
132136
object : MavenCli(ClassWorld("plexus.core", javaClass.classLoader)) {
133137
override fun customizeContainer(container: PlexusContainer) {
134138
container.addComponent(
@@ -148,6 +152,18 @@ class Tycho(
148152
}
149153
}
150154

155+
/**
156+
* Create the [DependencyGraphBuilder] for constructing the dependency graph of the analyzed Tycho project using
157+
* the given [mavenSupport] and the encountered [mavenProjects].
158+
*/
159+
internal fun createGraphBuilder(
160+
mavenSupport: MavenSupport,
161+
mavenProjects: Map<String, MavenProject>
162+
): DependencyGraphBuilder<DependencyNode> {
163+
val dependencyHandler = MavenDependencyHandler(managerName, projectType, mavenSupport, mavenProjects, false)
164+
return DependencyGraphBuilder(dependencyHandler)
165+
}
166+
151167
/**
152168
* Run a Maven build on the Tycho project in [projectRoot] utilizing the given [collector]. Return a pair with
153169
* the exit code of the Maven project and a [File] that contains the output generated during the build.

‎plugins/package-managers/maven/src/main/kotlin/utils/Extensions.kt

+25
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ import org.eclipse.aether.repository.RemoteRepository
3434
import org.ossreviewtoolkit.analyzer.PackageManager.Companion.processProjectVcs
3535
import org.ossreviewtoolkit.downloader.VersionControlSystem
3636
import org.ossreviewtoolkit.model.Identifier
37+
import org.ossreviewtoolkit.model.Issue
38+
import org.ossreviewtoolkit.model.Package
3739
import org.ossreviewtoolkit.model.Project
40+
import org.ossreviewtoolkit.model.Severity
41+
import org.ossreviewtoolkit.model.createAndLogIssue
3842

3943
fun Artifact.identifier() = "$groupId:$artifactId:$version"
4044

@@ -55,6 +59,27 @@ internal fun MavenProject.identifier(projectType: String): Identifier =
5559
internal val MavenProject.internalId: String
5660
get() = "$groupId:$artifactId:$version"
5761

62+
/**
63+
* Check whether this collection of [Package]s contains elements that stem from auto-generated POM files. If so,
64+
* create an [Issue] for each such [Package] using the given [managerName] as source. Optionally, the [targetIssues]
65+
* list can be provided.
66+
*/
67+
internal fun Collection<Package>.createIssuesForAutoGeneratedPoms(
68+
managerName: String,
69+
targetIssues: MutableList<Issue> = mutableListOf()
70+
): List<Issue> =
71+
mapNotNullTo(targetIssues) { pkg ->
72+
if (pkg.description == "POM was created by Sonatype Nexus") {
73+
createAndLogIssue(
74+
managerName,
75+
"Package '${pkg.id.toCoordinates()}' seems to use an auto-generated POM which might lack metadata.",
76+
Severity.HINT
77+
)
78+
} else {
79+
null
80+
}
81+
}
82+
5883
/**
5984
* Convert this [MavenProject] to an ORT [Project] using the given [identifier], the path to the [definitionFile],
6085
* the [projectDir], and the set of [scopeNames].

‎plugins/package-managers/maven/src/test/kotlin/TychoTest.kt

+118
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,35 @@
1919

2020
package org.ossreviewtoolkit.plugins.packagemanagers.maven
2121

22+
import io.kotest.core.TestConfiguration
2223
import io.kotest.core.spec.style.WordSpec
2324
import io.kotest.engine.spec.tempdir
25+
import io.kotest.engine.spec.tempfile
26+
import io.kotest.matchers.collections.beEmpty
2427
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
28+
import io.kotest.matchers.should
29+
import io.kotest.matchers.shouldBe
30+
import io.kotest.matchers.string.shouldContain
2531

32+
import io.mockk.every
2633
import io.mockk.mockk
34+
import io.mockk.spyk
35+
36+
import java.io.File
37+
import java.io.PrintStream
38+
39+
import org.apache.maven.cli.MavenCli
40+
import org.apache.maven.execution.MavenSession
41+
import org.apache.maven.project.MavenProject
42+
43+
import org.eclipse.aether.graph.DependencyNode
44+
45+
import org.ossreviewtoolkit.model.Identifier
46+
import org.ossreviewtoolkit.model.Package
47+
import org.ossreviewtoolkit.model.Severity
48+
import org.ossreviewtoolkit.model.utils.DependencyGraphBuilder
49+
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.DependencyTreeMojoNode
50+
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.JSON
2751

2852
class TychoTest : WordSpec({
2953
"mapDefinitionFiles()" should {
@@ -56,4 +80,98 @@ class TychoTest : WordSpec({
5680
mappedDefinitionFiles shouldContainExactlyInAnyOrder listOf(tychoDefinitionFile1, tychoDefinitionFile2)
5781
}
5882
}
83+
84+
"resolveDependencies()" should {
85+
"generate warnings about auto-generated pom files" {
86+
val definitionFile = tempfile()
87+
val rootProject = createMavenProject("root", definitionFile)
88+
val subProject = createMavenProject("sub")
89+
val projectsList = listOf(rootProject, subProject)
90+
91+
val tycho = spyk(Tycho("Tycho", tempdir(), mockk(relaxed = true), mockk(relaxed = true)))
92+
injectCliMock(tycho, projectsList.toJson(), projectsList)
93+
94+
val pkg1 = mockk<Package>(relaxed = true)
95+
val pkg2 = mockk<Package> {
96+
every { id } returns Identifier("Tycho:org.ossreviewtoolkit:sub-dependency:1.0.0")
97+
every { description } returns "POM was created by Sonatype Nexus"
98+
}
99+
100+
val graphBuilder = mockk<DependencyGraphBuilder<DependencyNode>>(relaxed = true) {
101+
every { packages() } returns setOf(pkg1, pkg2)
102+
}
103+
104+
every { tycho.createGraphBuilder(any(), any()) } returns graphBuilder
105+
106+
val results = tycho.resolveDependencies(definitionFile, emptyMap())
107+
108+
val (rootResults, subResults) = results.partition { it.project.id.name == "root" }
109+
110+
with(rootResults.single().issues.single()) {
111+
severity shouldBe Severity.HINT
112+
message shouldContain pkg2.id.toCoordinates()
113+
message shouldContain "auto-generated POM"
114+
source shouldBe "Tycho"
115+
}
116+
117+
subResults.single().issues should beEmpty()
118+
}
119+
}
59120
})
121+
122+
/**
123+
* Create a mock [MavenProject] with default coordinates and the given [name] and optional [definitionFile].
124+
*/
125+
private fun TestConfiguration.createMavenProject(name: String, definitionFile: File = tempfile()): MavenProject =
126+
mockk(relaxed = true) {
127+
every { groupId } returns "org.ossreviewtoolkit"
128+
every { artifactId } returns name
129+
every { version } returns "1.0.0"
130+
every { file } returns definitionFile
131+
every { id } returns "org.ossreviewtoolkit:$name:jar:1.0.0"
132+
every { parent } returns null
133+
}
134+
135+
/**
136+
* Return a [DependencyTreeMojoNode] with the properties of this [MavenProject].
137+
*/
138+
private fun MavenProject.toDependencyTreeMojoNode(): DependencyTreeMojoNode =
139+
DependencyTreeMojoNode(groupId, artifactId, version, "jar", "compile", "")
140+
141+
/**
142+
* Generate JSON output analogously to the Maven Dependency Plugin for this list of [MavenProject]s. Here only the
143+
* root project nodes are relevant; dependencies are not included.
144+
*/
145+
private fun Collection<MavenProject>.toJson(): String =
146+
joinToString("\n") { JSON.encodeToString(it.toDependencyTreeMojoNode()) }
147+
148+
/**
149+
* Create a mock for a [MavenCli] object and configure the given [tycho] spi to use it. The mock CLI simulates a
150+
* Maven build that produces the given [buildOutput] and detects the given [projectsList]. It returns the given
151+
* [exitCode].
152+
*/
153+
private fun injectCliMock(
154+
tycho: Tycho,
155+
buildOutput: String,
156+
projectsList: List<MavenProject>,
157+
exitCode: Int = 0
158+
): MavenCli {
159+
val cli = mockk<MavenCli> {
160+
every { doMain(any(), any(), any(), any()) } returns exitCode
161+
}
162+
163+
val session = mockk<MavenSession> {
164+
every { projects } returns projectsList
165+
}
166+
167+
every { tycho.createMavenCli(any(), any()) } answers {
168+
val collector = firstArg<TychoProjectsCollector>()
169+
collector.afterSessionEnd(session)
170+
171+
val out = secondArg<PrintStream>()
172+
out.println(buildOutput)
173+
cli
174+
}
175+
176+
return cli
177+
}

0 commit comments

Comments
 (0)
Please sign in to comment.