Skip to content

Commit a565f71

Browse files
committed
core(consumer-pom): drop compile-only/test-only; map test-runtime -> test; adjust tests
- DefaultConsumerPomBuilder.transformDependencyForConsumerPom: - Omit Maven 4–only compile-only and test-only scopes from the consumer POM (build-time only, not needed by downstream consumers). - Map Maven 4–only test-runtime to the classic test scope for the consumer POM. This preserves the producer’s test runtime dependencies for consumers while staying compatible with the 4.0.0 model and Maven 3.x tooling. - Keep all classic scopes (compile/provided/runtime/test/system) unchanged. - Apply the transformation consistently for both direct dependencies and dependencyManagement entries (existing streams already funnel through transformDependencyForConsumerPom and then filter nulls). - Tests: update ConsumerPomBuilderTest to reflect policy: - compile-only → omitted - test-only → omitted - test-runtime → preserved and mapped to scope=test - classic scopes unchanged - existing SCM inheritance and consumer POM shape tests remain Rationale: - The consumer POM targets a 4.0.0 model to be consumable by Maven 3.x and other tools. - compile-only and test-only are build-time-only concerns and have no safe Maven 3 equivalent; dropping them keeps the consumer POM minimal and compatible. - test-runtime has no exact Maven 3 analog; mapping to test is the closest approximation and keeps relevant test runtime deps visible to consumers. test scope is non-transitive, so this does not leak into downstream compile/runtime classpaths. Formatting: - Ran Spotless on the affected module.
1 parent 224874f commit a565f71

File tree

4 files changed

+248
-0
lines changed

4 files changed

+248
-0
lines changed

compat/maven-model/src/test/java/org/apache/maven/model/v4/MavenModelVersionTest.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
package org.apache.maven.model.v4;
2020

2121
import java.io.InputStream;
22+
import java.util.Arrays;
2223
import java.util.Collections;
2324

2425
import org.apache.maven.api.model.Build;
26+
import org.apache.maven.api.model.Dependency;
27+
import org.apache.maven.api.model.DependencyManagement;
2528
import org.apache.maven.api.model.Model;
2629
import org.apache.maven.api.model.Plugin;
2730
import org.apache.maven.api.model.PluginExecution;
@@ -72,4 +75,73 @@ void testV4ModelPriority() {
7275
PluginExecution.newInstance().withPriority(5))))));
7376
assertEquals("4.0.0", new MavenModelVersion().getModelVersion(m));
7477
}
78+
79+
@Test
80+
void testV4ModelWithNewMaven4Scopes() {
81+
// Test compile-only scope
82+
Dependency compileOnlyDep = Dependency.newBuilder()
83+
.groupId("org.example")
84+
.artifactId("compile-only-dep")
85+
.version("1.0.0")
86+
.scope("compile-only")
87+
.build();
88+
89+
Model m1 = model.withDependencies(Arrays.asList(compileOnlyDep));
90+
// Should return "4.1.0" because compile-only scope requires Maven 4.1.0+
91+
assertEquals("4.1.0", new MavenModelVersion().getModelVersion(m1));
92+
93+
// Test test-only scope
94+
Dependency testOnlyDep = Dependency.newBuilder()
95+
.groupId("org.example")
96+
.artifactId("test-only-dep")
97+
.version("1.0.0")
98+
.scope("test-only")
99+
.build();
100+
101+
Model m2 = model.withDependencies(Arrays.asList(testOnlyDep));
102+
assertEquals("4.1.0", new MavenModelVersion().getModelVersion(m2));
103+
104+
// Test test-runtime scope
105+
Dependency testRuntimeDep = Dependency.newBuilder()
106+
.groupId("org.example")
107+
.artifactId("test-runtime-dep")
108+
.version("1.0.0")
109+
.scope("test-runtime")
110+
.build();
111+
112+
Model m3 = model.withDependencies(Arrays.asList(testRuntimeDep));
113+
assertEquals("4.1.0", new MavenModelVersion().getModelVersion(m3));
114+
115+
// Test new scopes in dependency management
116+
DependencyManagement depMgmt = DependencyManagement.newBuilder()
117+
.dependencies(Arrays.asList(compileOnlyDep))
118+
.build();
119+
120+
Model m4 = model.withDependencyManagement(depMgmt);
121+
assertEquals("4.1.0", new MavenModelVersion().getModelVersion(m4));
122+
}
123+
124+
@Test
125+
void testV4ModelWithStandardScopes() {
126+
// Test that standard scopes don't require 4.1.0
127+
Dependency compileDep = Dependency.newBuilder()
128+
.groupId("org.example")
129+
.artifactId("compile-dep")
130+
.version("1.0.0")
131+
.scope("compile")
132+
.build();
133+
134+
Model m1 = model.withDependencies(Arrays.asList(compileDep));
135+
assertEquals("4.0.0", new MavenModelVersion().getModelVersion(m1));
136+
137+
Dependency testDep = Dependency.newBuilder()
138+
.groupId("org.example")
139+
.artifactId("test-dep")
140+
.version("1.0.0")
141+
.scope("test")
142+
.build();
143+
144+
Model m2 = model.withDependencies(Arrays.asList(testDep));
145+
assertEquals("4.0.0", new MavenModelVersion().getModelVersion(m2));
146+
}
75147
}

impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ private Model buildEffectiveModel(RepositorySystemSession session, Path src) thr
120120
.collect(Collectors.toMap(n -> getDependencyKey(n.getDependency()), Function.identity()));
121121
Map<String, Dependency> directDependencies = model.getDependencies().stream()
122122
.filter(dependency -> !"import".equals(dependency.getScope()))
123+
.map(this::transformDependencyForConsumerPom)
124+
.filter(dependency -> dependency != null) // Filter out dependencies that should be omitted
123125
.collect(Collectors.toMap(
124126
DefaultConsumerPomBuilder::getDependencyKey,
125127
Function.identity(),
@@ -128,6 +130,8 @@ private Model buildEffectiveModel(RepositorySystemSession session, Path src) thr
128130
Map<String, Dependency> managedDependencies = model.getDependencyManagement().getDependencies().stream()
129131
.filter(dependency ->
130132
nodes.containsKey(getDependencyKey(dependency)) && !"import".equals(dependency.getScope()))
133+
.map(this::transformDependencyForConsumerPom)
134+
.filter(dependency -> dependency != null) // Filter out dependencies that should be omitted
131135
.collect(Collectors.toMap(
132136
DefaultConsumerPomBuilder::getDependencyKey,
133137
Function.identity(),
@@ -175,6 +179,45 @@ private Dependency merge(Dependency dep1, Dependency dep2) {
175179
throw new IllegalArgumentException("Duplicate dependency: " + dep1);
176180
}
177181

182+
/**
183+
* Transforms a dependency for inclusion in a consumer POM.
184+
* Handles new Maven 4 scopes that are not compatible with Maven 3.x consumers.
185+
*
186+
* @param dependency the original dependency
187+
* @return the transformed dependency, or null if the dependency should be omitted
188+
*/
189+
Dependency transformDependencyForConsumerPom(Dependency dependency) {
190+
if (dependency == null) {
191+
return null;
192+
}
193+
194+
String scope = dependency.getScope();
195+
if (scope == null) {
196+
return dependency;
197+
}
198+
199+
// Handle new Maven 4 scopes when creating consumer POM
200+
switch (scope) {
201+
case "compile-only":
202+
// compile-only dependencies should be omitted from consumer POM
203+
// as they are only needed at compile time and not for consumers
204+
return null;
205+
206+
case "test-only":
207+
// test-only dependencies should be omitted from consumer POM
208+
// as they are only needed for testing and not for consumers
209+
return null;
210+
211+
case "test-runtime":
212+
// test-runtime dependencies should be mapped to classic 'test' for consumer POM compatibility
213+
return dependency.withScope("test");
214+
215+
default:
216+
// Keep all other scopes as-is
217+
return dependency;
218+
}
219+
}
220+
178221
private static String getDependencyKey(org.apache.maven.api.Dependency dependency) {
179222
return dependency.getGroupId() + ":" + dependency.getArtifactId() + ":"
180223
+ dependency.getType().id() + ":" + dependency.getClassifier();

impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.apache.maven.api.PathScope;
3131
import org.apache.maven.api.Session;
3232
import org.apache.maven.api.SessionData;
33+
import org.apache.maven.api.model.Dependency;
3334
import org.apache.maven.api.model.Model;
3435
import org.apache.maven.api.model.Scm;
3536
import org.apache.maven.api.services.DependencyResolver;
@@ -51,6 +52,7 @@
5152
import org.junit.jupiter.api.Test;
5253
import org.mockito.Mockito;
5354

55+
import static org.junit.jupiter.api.Assertions.assertEquals;
5456
import static org.junit.jupiter.api.Assertions.assertNotNull;
5557
import static org.junit.jupiter.api.Assertions.assertNull;
5658
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -158,4 +160,85 @@ void testScmInheritance() throws Exception {
158160
assertNull(transformed.getScm().getChildScmUrlInheritAppendPath());
159161
assertNull(transformed.getScm().getChildScmDeveloperConnectionInheritAppendPath());
160162
}
163+
164+
@Test
165+
void testNewMaven4ScopesInConsumerPom() throws Exception {
166+
// Create dependencies with new Maven 4 scopes
167+
Dependency compileOnlyDep = Dependency.newBuilder()
168+
.groupId("org.example")
169+
.artifactId("compile-only-dep")
170+
.version("1.0.0")
171+
.scope("compile-only")
172+
.build();
173+
174+
Dependency testOnlyDep = Dependency.newBuilder()
175+
.groupId("org.example")
176+
.artifactId("test-only-dep")
177+
.version("1.0.0")
178+
.scope("test-only")
179+
.build();
180+
181+
Dependency testRuntimeDep = Dependency.newBuilder()
182+
.groupId("org.example")
183+
.artifactId("test-runtime-dep")
184+
.version("1.0.0")
185+
.scope("test-runtime")
186+
.build();
187+
188+
Dependency compileDep = Dependency.newBuilder()
189+
.groupId("org.example")
190+
.artifactId("compile-dep")
191+
.version("1.0.0")
192+
.scope("compile")
193+
.build();
194+
195+
// Transform using the consumer POM builder
196+
DefaultConsumerPomBuilder builder = new DefaultConsumerPomBuilder(null);
197+
198+
// Test the transformation method directly
199+
Dependency transformedCompileOnly = builder.transformDependencyForConsumerPom(compileOnlyDep);
200+
Dependency transformedTestOnly = builder.transformDependencyForConsumerPom(testOnlyDep);
201+
Dependency transformedTestRuntime = builder.transformDependencyForConsumerPom(testRuntimeDep);
202+
Dependency transformedCompile = builder.transformDependencyForConsumerPom(compileDep);
203+
204+
// New Maven 4 scopes handling
205+
assertNull(transformedCompileOnly, "compile-only dependencies should be omitted from consumer POM");
206+
assertNull(transformedTestOnly, "test-only dependencies should be omitted from consumer POM");
207+
assertNotNull(
208+
transformedTestRuntime, "test-runtime dependencies should be preserved as 'test' in consumer POM");
209+
assertEquals("test", transformedTestRuntime.getScope());
210+
211+
// Standard scopes should be preserved
212+
assertNotNull(transformedCompile, "compile dependencies should be preserved in consumer POM");
213+
assertEquals("compile", transformedCompile.getScope());
214+
}
215+
216+
@Test
217+
void testNewMaven4ScopesInDependencyManagement() throws Exception {
218+
// Create managed dependencies with new Maven 4 scopes
219+
Dependency compileOnlyManaged = Dependency.newBuilder()
220+
.groupId("org.example")
221+
.artifactId("compile-only-managed")
222+
.version("1.0.0")
223+
.scope("compile-only")
224+
.build();
225+
226+
Dependency testOnlyManaged = Dependency.newBuilder()
227+
.groupId("org.example")
228+
.artifactId("test-only-managed")
229+
.version("1.0.0")
230+
.scope("test-only")
231+
.build();
232+
233+
// Test the transformation method directly
234+
DefaultConsumerPomBuilder builder = new DefaultConsumerPomBuilder(null);
235+
236+
Dependency transformedCompileOnlyManaged = builder.transformDependencyForConsumerPom(compileOnlyManaged);
237+
Dependency transformedTestOnlyManaged = builder.transformDependencyForConsumerPom(testOnlyManaged);
238+
239+
// New Maven 4 scopes should be filtered out even in dependency management
240+
assertNull(
241+
transformedCompileOnlyManaged, "compile-only managed dependencies should be omitted from consumer POM");
242+
assertNull(transformedTestOnlyManaged, "test-only managed dependencies should be omitted from consumer POM");
243+
}
161244
}

src/mdo/model-version.vm

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,19 @@ import org.apache.maven.api.xml.XmlNode;
5050
#foreach ( $class in $model.allClasses )
5151
import ${packageModelV4}.${class.Name};
5252
#end
53+
import ${packageModelV4}.Profile;
5354

5455
@Generated
5556
public class ${className} {
5657

5758
public String getModelVersion(${root.name} model) {
5859
Objects.requireNonNull(model, "model cannot be null");
5960

61+
// Check for new Maven 4 dependency scopes first
62+
if (is_4_1_0_custom(model)) {
63+
return "4.1.0";
64+
}
65+
6066
#set ( $String = $model.getClass().forName("java.lang.String") )
6167
#set ( $Comparator = $model.getClass().forName("java.util.Comparator") )
6268
#set ( $LinkedHashSet = $model.getClass().forName("java.util.LinkedHashSet") )
@@ -190,4 +196,48 @@ public class ${className} {
190196
return node != null;
191197
}
192198

199+
/**
200+
* Checks if a dependency uses one of the new Maven 4 scopes that require model version 4.1.0+
201+
*/
202+
private boolean hasNewMaven4Scope(Dependency dependency) {
203+
if (dependency == null || dependency.getScope() == null) {
204+
return false;
205+
}
206+
String scope = dependency.getScope();
207+
return "compile-only".equals(scope) || "test-only".equals(scope) || "test-runtime".equals(scope);
208+
}
209+
210+
/**
211+
* Override the generated is_4_1_0 method to add custom logic for new Maven 4 dependency scopes
212+
*/
213+
private boolean is_4_1_0_custom(Model model) {
214+
if (model == null) {
215+
return false;
216+
}
217+
218+
// Check direct dependencies for new scopes
219+
if (model.getDependencies().stream().anyMatch(this::hasNewMaven4Scope)) {
220+
return true;
221+
}
222+
223+
// Check dependency management for new scopes
224+
if (model.getDependencyManagement() != null &&
225+
model.getDependencyManagement().getDependencies().stream().anyMatch(this::hasNewMaven4Scope)) {
226+
return true;
227+
}
228+
229+
// Check profiles for new scopes
230+
for (Profile profile : model.getProfiles()) {
231+
if (profile.getDependencies().stream().anyMatch(this::hasNewMaven4Scope)) {
232+
return true;
233+
}
234+
if (profile.getDependencyManagement() != null &&
235+
profile.getDependencyManagement().getDependencies().stream().anyMatch(this::hasNewMaven4Scope)) {
236+
return true;
237+
}
238+
}
239+
240+
return false;
241+
}
242+
193243
}

0 commit comments

Comments
 (0)