Skip to content

Commit d5f0272

Browse files
oheger-boschsschuberth
authored andcommittedFeb 25, 2025
feat(Maven): Add an initial Tycho package manager implementation
This is an MVP implementation that can already handle real projects. Upcoming commits will add more robustness and missing features. Since this implementation uses a different approach to collect dependency information (based on parsing the dependency tree generated by the Maven Dependency Plugin [1]), it is placed in a dedicated class. Shared logic used by both the Maven and the Tycho implementations is provided by the `MavenSupport` class. [1]: https://maven.apache.org/plugins/maven-dependency-plugin/ Signed-off-by: Oliver Heger <oliver.heger@bosch.io>
1 parent 944c377 commit d5f0272

File tree

14 files changed

+854
-2
lines changed

14 files changed

+854
-2
lines changed
 

‎gradle/libs.versions.toml

+1
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ log4j-api-slf4j = { module = "org.apache.logging.log4j:log4j-to-slf4j", version.
156156
logbackClassic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
157157
maven-compat = { module = "org.apache.maven:maven-compat", version.ref = "maven" }
158158
maven-core = { module = "org.apache.maven:maven-core", version.ref = "maven" }
159+
maven-embedder = { module = "org.apache.maven:maven-embedder", version.ref = "maven" }
159160
maven-model = { module = "org.apache.maven:maven-model", version.ref = "maven" }
160161
maven-model-builder = { module = "org.apache.maven:maven-model-builder", version.ref = "maven" }
161162
maven-resolver-api = { module = "org.apache.maven.resolver:maven-resolver-api", version.ref = "mavenResolver" }

‎plugins/package-managers/maven/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ dependencies {
3535
implementation(projects.downloader)
3636
implementation(projects.utils.commonUtils)
3737

38+
implementation(libs.maven.embedder)
3839
implementation(libs.kotlinx.serialization.core)
3940
implementation(libs.kotlinx.serialization.json)
4041

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
---
2+
projects:
3+
- id: "Tycho:org.ossreviewtoolkit:tycho-test-aggregator:1.0.0-SNAPSHOT"
4+
definition_file_path: "plugins/package-managers/maven/src/funTest/assets/projects/synthetic/tycho/pom.xml"
5+
authors:
6+
- "ACME"
7+
- "Sirius Cybernetics Corporation"
8+
declared_licenses:
9+
- "Apache License, Version 2.0"
10+
declared_licenses_processed:
11+
spdx_expression: "Apache-2.0"
12+
mapped:
13+
Apache License, Version 2.0: "Apache-2.0"
14+
vcs:
15+
type: "Git"
16+
url: "https://example.com/git/repository.git"
17+
revision: ""
18+
path: ""
19+
vcs_processed:
20+
type: "Git"
21+
url: "<REPLACE_URL_PROCESSED>"
22+
revision: "<REPLACE_REVISION>"
23+
path: "plugins/package-managers/maven/src/funTest/assets/projects/synthetic/tycho"
24+
homepage_url: "http://maven.apache.org"
25+
scopes: []
26+
- id: "Tycho:org.ossreviewtoolkit:tycho-test-bundle:1.0.0-SNAPSHOT"
27+
definition_file_path: "plugins/package-managers/maven/src/funTest/assets/projects/synthetic/tycho/org.ossreviewtoolkit.tycho.bundle/pom.xml"
28+
declared_licenses: []
29+
declared_licenses_processed: {}
30+
vcs:
31+
type: ""
32+
url: ""
33+
revision: ""
34+
path: ""
35+
vcs_processed:
36+
type: "Git"
37+
url: "<REPLACE_URL_PROCESSED>"
38+
revision: "<REPLACE_REVISION>"
39+
path: "plugins/package-managers/maven/src/funTest/assets/projects/synthetic/tycho/org.ossreviewtoolkit.tycho.bundle"
40+
homepage_url: ""
41+
scopes:
42+
- name: "compile"
43+
dependencies:
44+
- id: "Maven:org.apache.commons:commons-configuration2:2.11.0"
45+
dependencies:
46+
- id: "Maven:commons-logging:commons-logging:1.3.2"
47+
- id: "Maven:org.apache.commons:commons-lang3:3.14.0"
48+
- id: "Maven:org.apache.commons:commons-text:1.12.0"
49+
- name: "test"
50+
dependencies:
51+
- id: "Maven:junit:junit:4.13.2"
52+
dependencies:
53+
- id: "Maven:org.hamcrest:hamcrest-core:1.3"
54+
packages:
55+
- id: "Maven:commons-logging:commons-logging:1.3.2"
56+
purl: "pkg:maven/commons-logging/commons-logging@1.3.2"
57+
authors:
58+
- "Apache"
59+
- "Brian Stansberry"
60+
- "Juozas Baliuka"
61+
- "Peter Donald"
62+
- "The Apache Software Foundation"
63+
declared_licenses:
64+
- "Apache-2.0"
65+
declared_licenses_processed:
66+
spdx_expression: "Apache-2.0"
67+
description: "Apache Commons Logging is a thin adapter allowing configurable bridging\
68+
\ to other,\n well-known logging systems."
69+
homepage_url: "https://commons.apache.org/proper/commons-logging/"
70+
binary_artifact:
71+
url: "https://repo.maven.apache.org/maven2/commons-logging/commons-logging/1.3.2/commons-logging-1.3.2.jar"
72+
hash:
73+
value: "3dc966156ef19d23c839715165435e582fafa753"
74+
algorithm: "SHA-1"
75+
source_artifact:
76+
url: "https://repo.maven.apache.org/maven2/commons-logging/commons-logging/1.3.2/commons-logging-1.3.2-sources.jar"
77+
hash:
78+
value: "3bf2b5ce48a273d1d37bac9df0ab9f73b4ef92cb"
79+
algorithm: "SHA-1"
80+
vcs:
81+
type: "Git"
82+
url: "https://gitbox.apache.org/repos/asf/commons-logging"
83+
revision: ""
84+
path: ""
85+
vcs_processed:
86+
type: "Git"
87+
url: "https://gitbox.apache.org/repos/asf/commons-logging"
88+
revision: ""
89+
path: ""
90+
- id: "Maven:junit:junit:4.13.2"
91+
purl: "pkg:maven/junit/junit@4.13.2"
92+
authors:
93+
- "David Saff"
94+
- "JUnit"
95+
- "Kevin Cooney"
96+
- "Marc Philipp"
97+
- "Stefan Birkner"
98+
declared_licenses:
99+
- "Eclipse Public License 1.0"
100+
declared_licenses_processed:
101+
spdx_expression: "EPL-1.0"
102+
mapped:
103+
Eclipse Public License 1.0: "EPL-1.0"
104+
description: "JUnit is a unit testing framework for Java, created by Erich Gamma\
105+
\ and Kent Beck."
106+
homepage_url: "http://junit.org"
107+
binary_artifact:
108+
url: "https://repo.maven.apache.org/maven2/junit/junit/4.13.2/junit-4.13.2.jar"
109+
hash:
110+
value: "8ac9e16d933b6fb43bc7f576336b8f4d7eb5ba12"
111+
algorithm: "SHA-1"
112+
source_artifact:
113+
url: "https://repo.maven.apache.org/maven2/junit/junit/4.13.2/junit-4.13.2-sources.jar"
114+
hash:
115+
value: "33987872a811fe4d4001ed494b07854822257f42"
116+
algorithm: "SHA-1"
117+
vcs:
118+
type: "Git"
119+
url: "git://github.com/junit-team/junit4.git"
120+
revision: "r4.13.2"
121+
path: ""
122+
vcs_processed:
123+
type: "Git"
124+
url: "https://github.com/junit-team/junit4.git"
125+
revision: "r4.13.2"
126+
path: ""
127+
- id: "Maven:org.apache.commons:commons-configuration2:2.11.0"
128+
purl: "pkg:maven/org.apache.commons/commons-configuration2@2.11.0"
129+
authors:
130+
- "Ariane Software"
131+
- "Bosch Software Innovations"
132+
- "Claude Warren"
133+
- "CollabNet, Inc."
134+
- "INTERMETA - Gesellschaft fuer Mehrwertdienste mbH"
135+
- "Intuit"
136+
- "Jörg Schaible"
137+
- "Multitask Consulting"
138+
- "Rob Tompkins"
139+
- "The Apache Software Foundation"
140+
- "Zenplex"
141+
- "dunbarconsulting.org"
142+
- "tucana.at"
143+
- "upstate.com"
144+
declared_licenses:
145+
- "Apache-2.0"
146+
declared_licenses_processed:
147+
spdx_expression: "Apache-2.0"
148+
description: "Tools to assist in the reading of configuration/preferences files\
149+
\ in various formats; requires Java 8."
150+
homepage_url: "https://commons.apache.org/proper/commons-configuration/"
151+
binary_artifact:
152+
url: "https://repo.maven.apache.org/maven2/org/apache/commons/commons-configuration2/2.11.0/commons-configuration2-2.11.0.jar"
153+
hash:
154+
value: "af5a2c6abe587074c0be1107fcb27fa2fad91304"
155+
algorithm: "SHA-1"
156+
source_artifact:
157+
url: "https://repo.maven.apache.org/maven2/org/apache/commons/commons-configuration2/2.11.0/commons-configuration2-2.11.0-sources.jar"
158+
hash:
159+
value: "613216569295c3594984d97225ae255c6082ad4c"
160+
algorithm: "SHA-1"
161+
vcs:
162+
type: "Git"
163+
url: "https://gitbox.apache.org/repos/asf/commons-configuration.git"
164+
revision: ""
165+
path: ""
166+
vcs_processed:
167+
type: "Git"
168+
url: "https://gitbox.apache.org/repos/asf/commons-configuration.git"
169+
revision: ""
170+
path: ""
171+
- id: "Maven:org.apache.commons:commons-lang3:3.14.0"
172+
purl: "pkg:maven/org.apache.commons/commons-lang3@3.14.0"
173+
authors:
174+
- "Benedikt Ritter"
175+
- "Carman Consulting, Inc."
176+
- "CollabNet, Inc."
177+
- "Duncan Jones"
178+
- "Fredrik Westermarck"
179+
- "Henri Yandell"
180+
- "Joerg Schaible"
181+
- "Loic Guibert"
182+
- "Matt Benson"
183+
- "Niall Pemberton"
184+
- "Oliver Heger"
185+
- "Paul Benedict"
186+
- "Rob Tompkins"
187+
- "Robert Burrell Donkin"
188+
- "SITA ATS Ltd"
189+
- "Steven Caswell"
190+
- "The Apache Software Foundation"
191+
declared_licenses:
192+
- "Apache-2.0"
193+
declared_licenses_processed:
194+
spdx_expression: "Apache-2.0"
195+
description: "Apache Commons Lang, a package of Java utility classes for the\n \
196+
\ classes that are in java.lang's hierarchy, or are considered to be so\n standard\
197+
\ as to justify existence in java.lang."
198+
homepage_url: "https://commons.apache.org/proper/commons-lang/"
199+
binary_artifact:
200+
url: "https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/3.14.0/commons-lang3-3.14.0.jar"
201+
hash:
202+
value: "1ed471194b02f2c6cb734a0cd6f6f107c673afae"
203+
algorithm: "SHA-1"
204+
source_artifact:
205+
url: "https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/3.14.0/commons-lang3-3.14.0-sources.jar"
206+
hash:
207+
value: "9ef3e18356f4ac30b15bfa48c02a7f54b51af382"
208+
algorithm: "SHA-1"
209+
vcs:
210+
type: "Git"
211+
url: "http://gitbox.apache.org/repos/asf/commons-lang.git"
212+
revision: "rel/commons-lang-3.13.0"
213+
path: ""
214+
vcs_processed:
215+
type: "Git"
216+
url: "http://gitbox.apache.org/repos/asf/commons-lang.git"
217+
revision: "rel/commons-lang-3.13.0"
218+
path: ""
219+
- id: "Maven:org.apache.commons:commons-text:1.12.0"
220+
purl: "pkg:maven/org.apache.commons/commons-text@1.12.0"
221+
authors:
222+
- "Benedikt Ritter"
223+
- "Bruno P. Kinoshita"
224+
- "Duncan Jones"
225+
- "Rob Tompkins"
226+
- "The Apache Software Foundation"
227+
declared_licenses:
228+
- "Apache-2.0"
229+
declared_licenses_processed:
230+
spdx_expression: "Apache-2.0"
231+
description: "Apache Commons Text is a set of utility functions and reusable components\
232+
\ for the purpose of processing\n and manipulating text that should be of use\
233+
\ in a Java environment."
234+
homepage_url: "https://commons.apache.org/proper/commons-text"
235+
binary_artifact:
236+
url: "https://repo.maven.apache.org/maven2/org/apache/commons/commons-text/1.12.0/commons-text-1.12.0.jar"
237+
hash:
238+
value: "66aa90dc099701c4d3b14bd256c328f592ccf0d6"
239+
algorithm: "SHA-1"
240+
source_artifact:
241+
url: "https://repo.maven.apache.org/maven2/org/apache/commons/commons-text/1.12.0/commons-text-1.12.0-sources.jar"
242+
hash:
243+
value: "9ff4f979b84635ca1b57a3298dd905b61e7fd0ca"
244+
algorithm: "SHA-1"
245+
vcs:
246+
type: "Git"
247+
url: "https://gitbox.apache.org/repos/asf/commons-text"
248+
revision: ""
249+
path: ""
250+
vcs_processed:
251+
type: "Git"
252+
url: "https://gitbox.apache.org/repos/asf/commons-text"
253+
revision: ""
254+
path: ""
255+
- id: "Maven:org.hamcrest:hamcrest-core:1.3"
256+
purl: "pkg:maven/org.hamcrest/hamcrest-core@1.3"
257+
authors:
258+
- "Joe Walnes"
259+
- "Nat Pryce"
260+
- "Neil Dunn"
261+
- "Steve Freeman"
262+
- "Tom Denley"
263+
declared_licenses:
264+
- "New BSD License"
265+
declared_licenses_processed:
266+
spdx_expression: "BSD-3-Clause"
267+
mapped:
268+
New BSD License: "BSD-3-Clause"
269+
description: "This is the core API of hamcrest matcher framework to be used by third-party\
270+
\ framework providers. This includes the a foundation set of matcher implementations\
271+
\ for common operations."
272+
homepage_url: "https://github.com/hamcrest/JavaHamcrest/hamcrest-core"
273+
binary_artifact:
274+
url: "https://repo.maven.apache.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar"
275+
hash:
276+
value: "42a25dc3219429f0e5d060061f71acb49bf010a0"
277+
algorithm: "SHA-1"
278+
source_artifact:
279+
url: "https://repo.maven.apache.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-sources.jar"
280+
hash:
281+
value: "1dc37250fbc78e23a65a67fbbaf71d2e9cbc3c0b"
282+
algorithm: "SHA-1"
283+
vcs:
284+
type: "Git"
285+
url: "git@github.com:hamcrest/JavaHamcrest.git"
286+
revision: ""
287+
path: ""
288+
vcs_processed:
289+
type: "Git"
290+
url: "ssh://git@github.com/hamcrest/JavaHamcrest.git"
291+
revision: ""
292+
path: ""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<extensions>
2+
<extension>
3+
<groupId>org.eclipse.tycho</groupId>
4+
<artifactId>tycho-build</artifactId>
5+
<version>4.0.8</version>
6+
</extension>
7+
</extensions>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<artifactId>tycho-test-bundle</artifactId>
7+
<version>1.0.0-SNAPSHOT</version>
8+
9+
<name>[bundle] Bundle</name>
10+
<packaging>pom</packaging>
11+
12+
<parent>
13+
<groupId>org.ossreviewtoolkit</groupId>
14+
<artifactId>tycho-test-parent</artifactId>
15+
<version>1.0.0-SNAPSHOT</version>
16+
<relativePath>../org.ossreviewtoolkit.tycho.parent/pom.xml</relativePath>
17+
</parent>
18+
19+
<dependencies>
20+
<dependency>
21+
<groupId>org.apache.commons</groupId>
22+
<artifactId>commons-configuration2</artifactId>
23+
<version>2.11.0</version>
24+
</dependency>
25+
26+
<dependency>
27+
<groupId>junit</groupId>
28+
<artifactId>junit</artifactId>
29+
<version>4.13.2</version>
30+
<scope>test</scope>
31+
</dependency>
32+
</dependencies>
33+
34+
<build>
35+
<plugins>
36+
<plugin>
37+
<groupId>org.eclipse.tycho</groupId>
38+
<artifactId>tycho-compiler-plugin</artifactId>
39+
<configuration>
40+
</configuration>
41+
</plugin>
42+
</plugins>
43+
</build>
44+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<project>
2+
<modelVersion>4.0.0</modelVersion>
3+
<groupId>org.ossreviewtoolkit</groupId>
4+
<artifactId>tycho-test-parent</artifactId>
5+
<version>1.0.0-SNAPSHOT</version>
6+
<packaging>pom</packaging>
7+
8+
<properties>
9+
<tycho.version>4.0.8</tycho.version>
10+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
11+
</properties>
12+
13+
<build>
14+
<pluginManagement>
15+
<plugins>
16+
<plugin>
17+
<groupId>org.eclipse.tycho</groupId>
18+
<artifactId>tycho-p2-director-plugin</artifactId>
19+
<version>${tycho.version}</version>
20+
</plugin>
21+
<plugin>
22+
<groupId>org.eclipse.tycho</groupId>
23+
<artifactId>tycho-maven-plugin</artifactId>
24+
<version>${tycho.version}</version>
25+
<extensions>true</extensions>
26+
</plugin>
27+
<plugin>
28+
<groupId>org.eclipse.tycho</groupId>
29+
<artifactId>tycho-packaging-plugin</artifactId>
30+
<version>${tycho.version}</version>
31+
<executions>
32+
<execution>
33+
<phase>package</phase>
34+
<id>package-feature</id>
35+
<configuration>
36+
<finalName>${project.artifactId}_${unqualifiedVersion}.${buildQualifier}</finalName>
37+
</configuration>
38+
</execution>
39+
</executions>
40+
</plugin>
41+
<plugin>
42+
<groupId>org.eclipse.tycho</groupId>
43+
<artifactId>target-platform-configuration</artifactId>
44+
<version>${tycho.version}</version>
45+
<configuration>
46+
<environments>
47+
<environment>
48+
<os>linux</os>
49+
<ws>gtk</ws>
50+
<arch>x86_64</arch>
51+
</environment>
52+
<environment>
53+
<os>win32</os>
54+
<ws>win32</ws>
55+
<arch>x86_64</arch>
56+
</environment>
57+
<environment>
58+
<os>macosx</os>
59+
<ws>cocoa</ws>
60+
<arch>x86_64</arch>
61+
</environment>
62+
</environments>
63+
<target>
64+
<file>../org.ossreviewtoolkit.tycho.target/org.ossreviewtoolkit.tycho.target.target</file>
65+
</target>
66+
</configuration>
67+
</plugin>
68+
<plugin>
69+
<groupId>org.eclipse.tycho</groupId>
70+
<artifactId>tycho-compiler-plugin</artifactId>
71+
<version>${tycho.version}</version>
72+
<configuration>
73+
<compilerArgument>-enableJavadoc</compilerArgument>
74+
<compilerArgument>-warn:-warningToken</compilerArgument>
75+
<encoding>${project.build.sourceEncoding}</encoding>
76+
<optimize>true</optimize>
77+
<showDeprecation>true</showDeprecation>
78+
<showWarnings>true</showWarnings>
79+
<source>17.0</source>
80+
<target>17.0</target>
81+
<useProjectSettings>true</useProjectSettings>
82+
</configuration>
83+
</plugin>
84+
</plugins>
85+
</pluginManagement>
86+
<plugins>
87+
<plugin>
88+
<artifactId>target-platform-configuration</artifactId>
89+
<groupId>org.eclipse.tycho</groupId>
90+
</plugin>
91+
<plugin>
92+
<artifactId>tycho-compiler-plugin</artifactId>
93+
<groupId>org.eclipse.tycho</groupId>
94+
</plugin>
95+
<plugin>
96+
<artifactId>tycho-maven-plugin</artifactId>
97+
<extensions>true</extensions>
98+
<groupId>org.eclipse.tycho</groupId>
99+
</plugin>
100+
</plugins>
101+
</build>
102+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?pde version="3.8"?>
2+
<target name="MyTarget" sequenceNumber="1">
3+
<locations>
4+
<location includeAllPlatforms="false" includeMode="planner" includeSource="true" type="InstallableUnit">
5+
<unit id="org.apache.commons:commons-lang3" version="3.12.0"/>
6+
<repository location="https://repo1.maven.org/maven2/"/>
7+
</location>
8+
</locations>
9+
</target>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<groupId>org.ossreviewtoolkit</groupId>
7+
<artifactId>tycho-test-aggregator</artifactId>
8+
<version>1.0.0-SNAPSHOT</version>
9+
10+
<name>[pom] Aggregator</name>
11+
<packaging>pom</packaging>
12+
<description>
13+
This is a test project for the ORT Tycho package manager.
14+
</description>
15+
<url>http://maven.apache.org</url>
16+
<scm>
17+
<connection>scm:git:https://example.com/git/repository.git</connection>
18+
<developerConnection>scm:git:https://example.com/git/repository.git</developerConnection>
19+
<url>https://example.com/git/repository</url>
20+
</scm>
21+
<organization>
22+
<name>ACME</name>
23+
</organization>
24+
<developers>
25+
<developer>
26+
<organization>ACME</organization>
27+
<name>Dev A</name>
28+
</developer>
29+
<developer>
30+
<organization>Sirius Cybernetics Corporation</organization>
31+
<name>Dev B</name>
32+
</developer>
33+
</developers>
34+
<licenses>
35+
<license>
36+
<name>Apache License, Version 2.0</name>
37+
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
38+
<distribution>repo</distribution>
39+
</license>
40+
</licenses>
41+
42+
<parent>
43+
<groupId>org.ossreviewtoolkit</groupId>
44+
<artifactId>tycho-test-parent</artifactId>
45+
<version>1.0.0-SNAPSHOT</version>
46+
<relativePath>org.ossreviewtoolkit.tycho.parent/pom.xml</relativePath>
47+
</parent>
48+
49+
<modules>
50+
<module>org.ossreviewtoolkit.tycho.bundle</module>
51+
</modules>
52+
53+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (C) 2025 The ORT Project Authors (see <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
* License-Filename: LICENSE
18+
*/
19+
20+
package org.ossreviewtoolkit.plugins.packagemanagers.maven
21+
22+
import io.kotest.core.spec.style.StringSpec
23+
import io.kotest.matchers.should
24+
25+
import org.ossreviewtoolkit.analyzer.collateMultipleProjects
26+
import org.ossreviewtoolkit.analyzer.create
27+
import org.ossreviewtoolkit.model.toYaml
28+
import org.ossreviewtoolkit.utils.test.getAssetFile
29+
import org.ossreviewtoolkit.utils.test.matchExpectedResult
30+
31+
class TychoFunTest : StringSpec({
32+
"Project dependencies are detected correctly" {
33+
val definitionFile = getAssetFile("projects/synthetic/tycho/pom.xml")
34+
val expectedResultFile = getAssetFile("projects/synthetic/tycho-expected-output.yml")
35+
36+
val result = create("Tycho").collateMultipleProjects(definitionFile).withResolvedScopes()
37+
38+
result.toYaml() should matchExpectedResult(expectedResultFile, definitionFile)
39+
}
40+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
/*
2+
* Copyright (C) 2025 The ORT Project Authors (see <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
* License-Filename: LICENSE
18+
*/
19+
20+
package org.ossreviewtoolkit.plugins.packagemanagers.maven
21+
22+
import java.io.File
23+
import java.io.PrintStream
24+
import java.util.concurrent.atomic.AtomicReference
25+
26+
import org.apache.logging.log4j.kotlin.logger
27+
import org.apache.maven.AbstractMavenLifecycleParticipant
28+
import org.apache.maven.cli.MavenCli
29+
import org.apache.maven.execution.MavenSession
30+
import org.apache.maven.project.MavenProject
31+
32+
import org.codehaus.plexus.DefaultPlexusContainer
33+
import org.codehaus.plexus.PlexusContainer
34+
import org.codehaus.plexus.classworlds.ClassWorld
35+
import org.codehaus.plexus.logging.BaseLoggerManager
36+
37+
import org.eclipse.aether.graph.DependencyNode
38+
39+
import org.ossreviewtoolkit.analyzer.AbstractPackageManagerFactory
40+
import org.ossreviewtoolkit.analyzer.PackageManager
41+
import org.ossreviewtoolkit.analyzer.PackageManagerResult
42+
import org.ossreviewtoolkit.analyzer.ProjectResults
43+
import org.ossreviewtoolkit.model.DependencyGraph
44+
import org.ossreviewtoolkit.model.ProjectAnalyzerResult
45+
import org.ossreviewtoolkit.model.config.AnalyzerConfiguration
46+
import org.ossreviewtoolkit.model.config.RepositoryConfiguration
47+
import org.ossreviewtoolkit.model.utils.DependencyGraphBuilder
48+
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.DependencyTreeLogger
49+
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.LocalProjectWorkspaceReader
50+
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.MavenDependencyHandler
51+
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.MavenLogger
52+
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.MavenSupport
53+
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.identifier
54+
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.internalId
55+
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.isTychoProject
56+
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.parseDependencyTree
57+
import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.toOrtProject
58+
import org.ossreviewtoolkit.utils.ort.createOrtTempFile
59+
60+
/**
61+
* A package manager implementation supporting Maven projects using [Tycho](https://github.com/eclipse-tycho/tycho).
62+
*/
63+
class Tycho(
64+
name: String,
65+
analysisRoot: File,
66+
analyzerConfig: AnalyzerConfiguration,
67+
repoConfig: RepositoryConfiguration
68+
) : PackageManager(name, "Tycho", analysisRoot, analyzerConfig, repoConfig) {
69+
class Factory : AbstractPackageManagerFactory<Tycho>("Tycho") {
70+
override val globsForDefinitionFiles = listOf("pom.xml")
71+
72+
override fun create(
73+
analysisRoot: File,
74+
analyzerConfig: AnalyzerConfiguration,
75+
repoConfig: RepositoryConfiguration
76+
) = Tycho(type, analysisRoot, analyzerConfig, repoConfig)
77+
}
78+
79+
/**
80+
* The builder to generate the dependency graph. This could actually be a local variable, but it is also needed
81+
* to construct the final [PackageManagerResult].
82+
*/
83+
private lateinit var graphBuilder: DependencyGraphBuilder<DependencyNode>
84+
85+
override fun mapDefinitionFiles(definitionFiles: List<File>): List<File> = definitionFiles.filter(::isTychoProject)
86+
87+
override fun resolveDependencies(definitionFile: File, labels: Map<String, String>): List<ProjectAnalyzerResult> {
88+
logger.info { "Resolving Tycho dependencies for $definitionFile." }
89+
90+
val collector = TychoProjectsCollector()
91+
val (_, buildLog) = runBuild(collector, definitionFile.parentFile)
92+
// TODO: Create issues for a failed build and projects for which dependencies could not be resolved.
93+
94+
val resolvedProjects = createMavenSupport(collector).use { mavenSupport ->
95+
val dependencyHandler =
96+
MavenDependencyHandler(managerName, projectType, mavenSupport, collector.mavenProjects, false)
97+
98+
graphBuilder = DependencyGraphBuilder(dependencyHandler)
99+
100+
buildLog.inputStream().use { stream ->
101+
parseDependencyTree(stream, collector.mavenProjects.values).map { projectNode ->
102+
val project = collector.mavenProjects.getValue(projectNode.artifact.identifier())
103+
processProjectDependencies(graphBuilder, project, projectNode.children)
104+
project
105+
}.toList()
106+
}
107+
}
108+
109+
buildLog.delete()
110+
111+
return resolvedProjects.map { mavenProject ->
112+
val projectId = mavenProject.identifier(projectType)
113+
val project = mavenProject.toOrtProject(
114+
projectId,
115+
mavenProject.file,
116+
mavenProject.file.parentFile,
117+
graphBuilder.scopesFor(projectId)
118+
)
119+
ProjectAnalyzerResult(project, emptySet())
120+
}
121+
}
122+
123+
override fun createPackageManagerResult(projectResults: ProjectResults): PackageManagerResult =
124+
PackageManagerResult(projectResults, graphBuilder.build(), graphBuilder.packages())
125+
126+
/**
127+
* Create the [MavenCli] instance to trigger a build on the analyzed project. Register the given [collector], so
128+
* that it is invoked during the build. Also install a specific logger that forwards the output of the dependency
129+
* tree plugin to the given [outputStream].
130+
*/
131+
private fun createMavenCli(collector: TychoProjectsCollector, outputStream: PrintStream): MavenCli =
132+
object : MavenCli(ClassWorld("plexus.core", javaClass.classLoader)) {
133+
override fun customizeContainer(container: PlexusContainer) {
134+
container.addComponent(
135+
collector,
136+
AbstractMavenLifecycleParticipant::class.java,
137+
"TychoProjectsCollector"
138+
)
139+
140+
(container as? DefaultPlexusContainer)?.loggerManager = object : BaseLoggerManager() {
141+
override fun createLogger(name: String): org.codehaus.plexus.logging.Logger =
142+
if (DEPENDENCY_TREE_LOGGER == name) {
143+
DependencyTreeLogger(outputStream)
144+
} else {
145+
MavenLogger(logger.delegate.level)
146+
}
147+
}
148+
}
149+
}
150+
151+
/**
152+
* Run a Maven build on the Tycho project in [projectRoot] utilizing the given [collector]. Return a pair with
153+
* the exit code of the Maven project and a [File] that contains the output generated during the build.
154+
*/
155+
private fun runBuild(collector: TychoProjectsCollector, projectRoot: File): Pair<Int, File> {
156+
// The Maven CLI seems to change the context class loader. This has side effects on ORT's plugin mechanism.
157+
// To prevent this, store the class loader and restore it at the end of this function.
158+
val tccl = Thread.currentThread().contextClassLoader
159+
160+
try {
161+
val buildLog = createOrtTempFile()
162+
163+
val exitCode = PrintStream(buildLog.outputStream()).use { out ->
164+
val cli = createMavenCli(collector, out)
165+
166+
// With the current CLI API, there does not seem to be another way to set the build root folder than
167+
// using a system property.
168+
System.setProperty(MavenCli.MULTIMODULE_PROJECT_DIRECTORY, projectRoot.absolutePath)
169+
170+
cli.doMain(
171+
// The "package" goal is required; otherwise the Tycho extension is not activated.
172+
arrayOf("package", "dependency:tree", "-DoutputType=json"),
173+
projectRoot.path,
174+
null,
175+
null
176+
).also { logger.info { "Tycho analysis completed. Exit code: $it." } }
177+
}
178+
179+
return exitCode to buildLog
180+
} finally {
181+
Thread.currentThread().contextClassLoader = tccl
182+
}
183+
}
184+
185+
/**
186+
* Create a [MavenSupport] instance to be used for resolving the packages found during the Maven build. Obtain
187+
* the local projects from the given [collector]
188+
*/
189+
private fun createMavenSupport(collector: TychoProjectsCollector): MavenSupport {
190+
val localProjects = collector.mavenProjects
191+
val resolveFunc: (String) -> File? = { projectId -> localProjects[projectId]?.file }
192+
193+
return MavenSupport(LocalProjectWorkspaceReader(resolveFunc))
194+
}
195+
196+
/**
197+
* Process the [dependencies] of the given [project] by adding them to the [graphBuilder].
198+
*/
199+
private fun processProjectDependencies(
200+
graphBuilder: DependencyGraphBuilder<DependencyNode>,
201+
project: MavenProject,
202+
dependencies: Collection<DependencyNode>
203+
) {
204+
val projectId = project.identifier(projectType)
205+
206+
dependencies.forEach { node ->
207+
graphBuilder.addDependency(DependencyGraph.qualifyScope(projectId, node.dependency.scope), node)
208+
}
209+
}
210+
}
211+
212+
/** The name of the logger used by the Maven dependency tree plugin. */
213+
private const val DEPENDENCY_TREE_LOGGER = "org.apache.maven.plugins.dependency.tree.TreeMojo"
214+
215+
/**
216+
* An internal helper class that gets registered as a Maven lifecycle participant to obtain all [MavenProject]s
217+
* encountered during the build.
218+
*/
219+
internal class TychoProjectsCollector : AbstractMavenLifecycleParticipant() {
220+
/**
221+
* Stores the projects that have been found during the Maven build. To be on the safe side with regard to
222+
* possible threading issues, use an [AtomicReference] to ensure safe publication.
223+
*/
224+
private val projects = AtomicReference<Map<String, MavenProject>>(emptyMap())
225+
226+
/**
227+
* Return the projects that have been found during the Maven build.
228+
*/
229+
val mavenProjects: Map<String, MavenProject>
230+
get() = projects.get()
231+
232+
override fun afterSessionEnd(session: MavenSession) {
233+
val builtProjects = session.projects.associateBy(MavenProject::internalId)
234+
projects.set(builtProjects)
235+
236+
logger.info { "Found ${builtProjects.size} projects during build." }
237+
}
238+
}

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

+6
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ internal fun MavenProject.identifier(projectType: String): Identifier =
4949
version = version
5050
)
5151

52+
/**
53+
* Return an internal ID string to uniquely identify this [MavenProject] during the currently ongoing analysis.
54+
*/
55+
internal val MavenProject.internalId: String
56+
get() = "$groupId:$artifactId:$version"
57+
5258
/**
5359
* Convert this [MavenProject] to an ORT [Project] using the given [identifier], the path to the [definitionFile],
5460
* the [projectDir], and the set of [scopeNames].

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,8 @@ class MavenSupport(private val workspaceReader: WorkspaceReader) : Closeable {
219219
}
220220
} else {
221221
val project = projectBuildingResult.project
222-
val identifier = "${project.groupId}:${project.artifactId}:${project.version}"
223222

224-
result[identifier] = projectBuildingResult
223+
result[project.internalId] = projectBuildingResult
225224
}
226225
}
227226

Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
org.ossreviewtoolkit.plugins.packagemanagers.maven.Maven$Factory
2+
org.ossreviewtoolkit.plugins.packagemanagers.maven.Tycho$Factory
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright (C) 2025 The ORT Project Authors (see <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
* License-Filename: LICENSE
18+
*/
19+
20+
package org.ossreviewtoolkit.plugins.packagemanagers.maven
21+
22+
import io.kotest.core.spec.style.WordSpec
23+
import io.kotest.engine.spec.tempdir
24+
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
25+
26+
import io.mockk.mockk
27+
28+
class TychoTest : WordSpec({
29+
"mapDefinitionFiles()" should {
30+
"select only Tycho root projects" {
31+
val tychoProjectDir1 = tempdir()
32+
tychoProjectDir1.addTychoExtension()
33+
val tychoDefinitionFile1 = tychoProjectDir1.resolve("pom.xml").also { it.writeText("pom-tycho1") }
34+
val tychoSubProjectDir = tychoProjectDir1.resolve("subproject")
35+
val tychoSubModule = tychoSubProjectDir.resolve("pom.xml")
36+
val tychoProjectDir2 = tempdir()
37+
tychoProjectDir2.addTychoExtension()
38+
val tychoDefinitionFile2 = tychoProjectDir2.resolve("pom.xml").also { it.writeText("pom-tycho2") }
39+
40+
val mavenProjectDir = tempdir()
41+
val mavenDefinitionFile = mavenProjectDir.resolve("pom.xml").also { it.writeText("pom-maven") }
42+
val mavenSubProjectDir = mavenProjectDir.resolve("subproject")
43+
val mavenSubModule = mavenSubProjectDir.resolve("pom.xml")
44+
45+
val definitionFiles = listOf(
46+
tychoDefinitionFile1,
47+
mavenDefinitionFile,
48+
tychoDefinitionFile2,
49+
mavenSubModule,
50+
tychoSubModule
51+
)
52+
53+
val tycho = Tycho("Tycho", tempdir(), mockk(relaxed = true), mockk(relaxed = true))
54+
val mappedDefinitionFiles = tycho.mapDefinitionFiles(definitionFiles)
55+
56+
mappedDefinitionFiles shouldContainExactlyInAnyOrder listOf(tychoDefinitionFile1, tychoDefinitionFile2)
57+
}
58+
}
59+
})

0 commit comments

Comments
 (0)
Please sign in to comment.