Skip to content

Commit 985b09f

Browse files
committedFeb 27, 2025
feat(Maven): Support path excludes for Tycho
If the `skipExcluded` flag in the Analyzer configuration is set, exclude projects matched by path excludes from the Maven build. This should speed up the processing time and can prevent problematic modules to cause the build to fail. Signed-off-by: Oliver Heger <oliver.heger@bosch.io>
1 parent 5b69a8b commit 985b09f

File tree

2 files changed

+159
-2
lines changed

2 files changed

+159
-2
lines changed
 

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

+40-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ package org.ossreviewtoolkit.plugins.packagemanagers.maven
2121

2222
import java.io.File
2323
import java.io.PrintStream
24+
import java.nio.file.Path
2425
import java.util.concurrent.atomic.AtomicReference
2526

27+
import kotlin.io.path.invariantSeparatorsPathString
28+
2629
import org.apache.logging.log4j.kotlin.logger
2730
import org.apache.maven.AbstractMavenLifecycleParticipant
2831
import org.apache.maven.cli.MavenCli
@@ -187,8 +190,7 @@ class Tycho(
187190
System.setProperty(MavenCli.MULTIMODULE_PROJECT_DIRECTORY, projectRoot.absolutePath)
188191

189192
cli.doMain(
190-
// The "package" goal is required; otherwise the Tycho extension is not activated.
191-
arrayOf("package", "dependency:tree", "-DoutputType=json"),
193+
generateMavenOptions(projectRoot),
192194
projectRoot.path,
193195
null,
194196
null
@@ -260,11 +262,47 @@ class Tycho(
260262

261263
return rootIssues
262264
}
265+
266+
/**
267+
* Generate the command line options to be passed to the Maven CLI for the given [root] folder.
268+
*/
269+
private fun generateMavenOptions(root: File): Array<String> =
270+
buildList {
271+
// The "package" goal is required; otherwise the Tycho extension is not activated.
272+
add("package")
273+
add("dependency:tree")
274+
add("-DoutputType=json")
275+
276+
generateModuleExcludes(root)?.takeUnless { it.isEmpty() }?.let { excludedModules ->
277+
add("-pl")
278+
add(excludedModules)
279+
}
280+
}.toTypedArray()
281+
282+
/**
283+
* Generate a list of submodules to be excluded for the Maven build with the given [rootProject] folder based on
284+
* the configured exclusions. The resulting string (if any) is used as the value of Maven's `-pl` option.
285+
*/
286+
private fun generateModuleExcludes(rootProject: File): String? {
287+
if (!analyzerConfig.skipExcluded) return null
288+
289+
val analysisRootPath = analysisRoot.toPath()
290+
val rootProjectPath = rootProject.toPath()
291+
return rootProject.walk().filter { it.name == "pom.xml" }
292+
.map { it.toPath().parent }
293+
.filter { excludes.isPathExcluded(analysisRootPath.relativeSubPath(it)) }
294+
.joinToString(",") { "!${rootProjectPath.relativeSubPath(it)}" }
295+
}
263296
}
264297

265298
/** The name of the logger used by the Maven dependency tree plugin. */
266299
private const val DEPENDENCY_TREE_LOGGER = "org.apache.maven.plugins.dependency.tree.TreeMojo"
267300

301+
/**
302+
* Return the relative path of [other] to this [Path].
303+
*/
304+
private fun Path.relativeSubPath(other: Path): String = relativize(other).invariantSeparatorsPathString
305+
268306
/**
269307
* A special exception class to indicate that a Tycho build failed completely.
270308
*/

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

+119
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,19 @@ import io.kotest.engine.spec.tempdir
2626
import io.kotest.engine.spec.tempfile
2727
import io.kotest.inspectors.forAll
2828
import io.kotest.matchers.collections.beEmpty
29+
import io.kotest.matchers.collections.contain
2930
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
31+
import io.kotest.matchers.comparables.shouldBeGreaterThan
3032
import io.kotest.matchers.should
3133
import io.kotest.matchers.shouldBe
34+
import io.kotest.matchers.shouldNot
3235
import io.kotest.matchers.string.shouldContain
3336

3437
import io.mockk.every
3538
import io.mockk.mockk
39+
import io.mockk.slot
3640
import io.mockk.spyk
41+
import io.mockk.verify
3742

3843
import java.io.File
3944
import java.io.PrintStream
@@ -47,6 +52,11 @@ import org.eclipse.aether.graph.DependencyNode
4752
import org.ossreviewtoolkit.model.Identifier
4853
import org.ossreviewtoolkit.model.Package
4954
import org.ossreviewtoolkit.model.Severity
55+
import org.ossreviewtoolkit.model.config.AnalyzerConfiguration
56+
import org.ossreviewtoolkit.model.config.Excludes
57+
import org.ossreviewtoolkit.model.config.PathExclude
58+
import org.ossreviewtoolkit.model.config.PathExcludeReason
59+
import org.ossreviewtoolkit.model.config.RepositoryConfiguration
5060
import org.ossreviewtoolkit.model.utils.DependencyGraphBuilder
5161
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.DependencyTreeMojoNode
5262
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.JSON
@@ -186,6 +196,99 @@ class TychoTest : WordSpec({
186196
subProject3.identifier("Tycho").toCoordinates()
187197
)
188198
}
199+
200+
"exclude projects from the build according to path excludes" {
201+
val analysisRoot = tempdir()
202+
val tychoRoot = analysisRoot.createSubModule("tycho-root")
203+
tychoRoot.createSubModule("tycho-sub1")
204+
tychoRoot.createSubModule("tycho-sub2")
205+
tychoRoot.createSubModule("tycho-excluded-sub1")
206+
val module = tychoRoot.createSubModule("tycho-excluded-sub2")
207+
module.createSubModule("tycho-excluded-sub2-sub")
208+
val rootProject = createMavenProject("root", tychoRoot.pom)
209+
210+
val excludes = Excludes(
211+
paths = listOf(
212+
PathExclude("tycho-root/tycho-excluded-sub1", PathExcludeReason.EXAMPLE_OF),
213+
PathExclude("tycho-root/tycho-excluded-sub2/**", PathExcludeReason.TEST_OF),
214+
PathExclude("other-root/**", PathExcludeReason.EXAMPLE_OF)
215+
)
216+
)
217+
val analyzerConfig = AnalyzerConfiguration(skipExcluded = true)
218+
val repositoryConfig = RepositoryConfiguration(excludes = excludes)
219+
220+
val tycho = spyk(Tycho("Tycho", analysisRoot, analyzerConfig, repositoryConfig))
221+
val cli = injectCliMock(tycho, listOf(rootProject).toJson(), listOf(rootProject))
222+
223+
tycho.resolveDependencies(tychoRoot.pom, emptyMap())
224+
225+
val slotArgs = slot<Array<String>>()
226+
verify {
227+
cli.doMain(capture(slotArgs), any(), any(), any())
228+
}
229+
230+
with(slotArgs.captured) {
231+
val indexPl = indexOf("-pl")
232+
indexPl shouldBeGreaterThan -1
233+
val excludedProjects = get(indexPl + 1).split(",")
234+
excludedProjects shouldContainExactlyInAnyOrder listOf(
235+
"!tycho-excluded-sub1",
236+
"!tycho-excluded-sub2",
237+
"!tycho-excluded-sub2/tycho-excluded-sub2-sub"
238+
)
239+
}
240+
}
241+
242+
"not add a -pl option if no projects are excluded" {
243+
val analysisRoot = tempdir()
244+
val tychoRoot = analysisRoot.createSubModule("tycho-root")
245+
tychoRoot.createSubModule("tycho-sub1")
246+
tychoRoot.createSubModule("tycho-sub2")
247+
val rootProject = createMavenProject("root", tychoRoot.pom)
248+
249+
val excludes = Excludes(paths = listOf(PathExclude("other-root/**", PathExcludeReason.EXAMPLE_OF)))
250+
val analyzerConfig = AnalyzerConfiguration(skipExcluded = true)
251+
val repositoryConfig = RepositoryConfiguration(excludes = excludes)
252+
253+
val tycho = spyk(Tycho("Tycho", analysisRoot, analyzerConfig, repositoryConfig))
254+
val cli = injectCliMock(tycho, listOf(rootProject).toJson(), listOf(rootProject))
255+
256+
tycho.resolveDependencies(tychoRoot.pom, emptyMap())
257+
258+
val slotArgs = slot<Array<String>>()
259+
verify {
260+
cli.doMain(capture(slotArgs), any(), any(), any())
261+
}
262+
263+
slotArgs.captured.toList() shouldNot contain("-pl")
264+
}
265+
266+
"not exclude projects if skipExcluded is false" {
267+
val analysisRoot = tempdir()
268+
val tychoRoot = analysisRoot.createSubModule("tycho-root")
269+
tychoRoot.createSubModule("tycho-sub1")
270+
tychoRoot.createSubModule("tycho-sub2")
271+
tychoRoot.createSubModule("tycho-excluded-sub1")
272+
val rootProject = createMavenProject("root", tychoRoot.pom)
273+
274+
val excludes = Excludes(
275+
paths = listOf(PathExclude("tycho-root/tycho-excluded-sub1", PathExcludeReason.EXAMPLE_OF))
276+
)
277+
val analyzerConfig = AnalyzerConfiguration()
278+
val repositoryConfig = RepositoryConfiguration(excludes = excludes)
279+
280+
val tycho = spyk(Tycho("Tycho", analysisRoot, analyzerConfig, repositoryConfig))
281+
val cli = injectCliMock(tycho, listOf(rootProject).toJson(), listOf(rootProject))
282+
283+
tycho.resolveDependencies(tychoRoot.pom, emptyMap())
284+
285+
val slotArgs = slot<Array<String>>()
286+
verify {
287+
cli.doMain(capture(slotArgs), any(), any(), any())
288+
}
289+
290+
slotArgs.captured.toList() shouldNot contain("-pl")
291+
}
189292
}
190293
})
191294

@@ -245,3 +348,19 @@ private fun injectCliMock(
245348

246349
return cli
247350
}
351+
352+
/**
353+
* Return a reference to the Maven pom file in this folder.
354+
*/
355+
private val File.pom: File
356+
get() = resolve("pom.xml")
357+
358+
/**
359+
* Create a sub folder with the given [name] and a pom file to simulate a Maven module. Return the created folder
360+
* for the module.
361+
*/
362+
private fun File.createSubModule(name: String): File =
363+
resolve(name).apply {
364+
mkdirs()
365+
pom.also { it.writeText("pom-$name") }
366+
}

0 commit comments

Comments
 (0)
Please sign in to comment.