Skip to content

Commit 83a3863

Browse files
authored
Fix false parent cycle detection with flatten-maven-plugin (fixes #11399) (#11400) (#11403)
When using flatten-maven-plugin with updatePomFile=true and parent expansion, Maven incorrectly detected a parent cycle during the install phase. The error occurred because the consumer POM builder was using Path instead of ModelSource when reading the flattened POM. This change updates the PomArtifactTransformer API to use ModelSource instead of Path. ModelSource includes the necessary context (base directory, ModelLocator) to properly resolve parent POMs and avoid false cycle detection. Changes: - Updated PomArtifactTransformer.transform() to accept ModelSource instead of Path - Modified ConsumerPomArtifactTransformer to create ModelSource with proper resolution context - Updated DefaultConsumerPomBuilder and related classes to work with ModelSource - Added integration test to verify the fix Fixes #11399 (cherry picked from commit 5ec059c)
1 parent 3411fd2 commit 83a3863

File tree

10 files changed

+201
-27
lines changed

10 files changed

+201
-27
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.nio.file.Path;
2525

2626
import org.apache.maven.api.services.ModelBuilderException;
27+
import org.apache.maven.api.services.ModelSource;
2728
import org.apache.maven.project.MavenProject;
2829
import org.eclipse.aether.RepositorySystemSession;
2930
import org.eclipse.aether.deployment.DeployRequest;
@@ -41,6 +42,6 @@ public interface PomArtifactTransformer {
4142

4243
void injectTransformedArtifacts(RepositorySystemSession session, MavenProject currentProject) throws IOException;
4344

44-
void transform(MavenProject project, RepositorySystemSession session, Path src, Path tgt)
45+
void transform(MavenProject project, RepositorySystemSession session, ModelSource src, Path tgt)
4546
throws ModelBuilderException, XMLStreamException, IOException;
4647
}

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

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
import javax.inject.Singleton;
2424
import javax.xml.stream.XMLStreamException;
2525

26+
import java.io.File;
2627
import java.io.IOException;
28+
import java.io.InputStream;
2729
import java.nio.file.Files;
2830
import java.nio.file.Path;
2931
import java.nio.file.Paths;
@@ -36,6 +38,9 @@
3638
import org.apache.maven.api.feature.Features;
3739
import org.apache.maven.api.model.Model;
3840
import org.apache.maven.api.services.ModelBuilderException;
41+
import org.apache.maven.api.services.ModelSource;
42+
import org.apache.maven.api.services.Source;
43+
import org.apache.maven.api.services.Sources;
3944
import org.apache.maven.project.MavenProject;
4045
import org.apache.maven.project.artifact.ProjectArtifact;
4146
import org.eclipse.aether.RepositorySystemSession;
@@ -93,19 +98,53 @@ public void injectTransformedArtifacts(RepositorySystemSession session, MavenPro
9398

9499
TransformedArtifact createConsumerPomArtifact(
95100
MavenProject project, Path consumer, RepositorySystemSession session) {
101+
Path actual = project.getFile().toPath();
102+
Path parent = project.getBaseDirectory();
103+
ModelSource source = new ModelSource() {
104+
@Override
105+
public Path getPath() {
106+
return actual;
107+
}
108+
109+
@Override
110+
public InputStream openStream() throws IOException {
111+
return Files.newInputStream(actual);
112+
}
113+
114+
@Override
115+
public String getLocation() {
116+
return actual.toString();
117+
}
118+
119+
@Override
120+
public Source resolve(String relative) {
121+
return Sources.buildSource(actual.resolve(relative));
122+
}
123+
124+
@Override
125+
public ModelSource resolve(ModelLocator modelLocator, String relative) {
126+
String norm = relative.replace('\\', File.separatorChar).replace('/', File.separatorChar);
127+
Path path = parent.resolve(norm);
128+
Path relatedPom = modelLocator.locateExistingPom(path);
129+
if (relatedPom != null) {
130+
return Sources.buildSource(relatedPom);
131+
}
132+
return null;
133+
}
134+
};
96135
return new TransformedArtifact(
97136
this,
98137
project,
99138
consumer,
100139
session,
101140
new ProjectArtifact(project),
102-
() -> project.getFile().toPath(),
141+
() -> source,
103142
CONSUMER_POM_CLASSIFIER,
104143
"pom");
105144
}
106145

107146
@Override
108-
public void transform(MavenProject project, RepositorySystemSession session, Path src, Path tgt)
147+
public void transform(MavenProject project, RepositorySystemSession session, ModelSource src, Path tgt)
109148
throws ModelBuilderException, XMLStreamException, IOException {
110149
Model model = builder.build(session, project, src);
111150
write(model, tgt);

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

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import javax.inject.Inject;
2222
import javax.inject.Named;
2323

24-
import java.nio.file.Path;
2524
import java.util.LinkedHashMap;
2625
import java.util.List;
2726
import java.util.Map;
@@ -45,7 +44,7 @@
4544
import org.apache.maven.api.services.ModelBuilderException;
4645
import org.apache.maven.api.services.ModelBuilderRequest;
4746
import org.apache.maven.api.services.ModelBuilderResult;
48-
import org.apache.maven.api.services.Sources;
47+
import org.apache.maven.api.services.ModelSource;
4948
import org.apache.maven.api.services.model.LifecycleBindingsInjector;
5049
import org.apache.maven.impl.InternalSession;
5150
import org.apache.maven.model.v4.MavenModelVersion;
@@ -71,7 +70,8 @@ class DefaultConsumerPomBuilder implements PomBuilder {
7170
}
7271

7372
@Override
74-
public Model build(RepositorySystemSession session, MavenProject project, Path src) throws ModelBuilderException {
73+
public Model build(RepositorySystemSession session, MavenProject project, ModelSource src)
74+
throws ModelBuilderException {
7575
Model model = project.getModel().getDelegate();
7676
boolean flattenEnabled = Features.consumerPomFlatten(session.getConfigProperties());
7777

@@ -95,27 +95,27 @@ public Model build(RepositorySystemSession session, MavenProject project, Path s
9595
}
9696
}
9797

98-
protected Model buildPom(RepositorySystemSession session, MavenProject project, Path src)
98+
protected Model buildPom(RepositorySystemSession session, MavenProject project, ModelSource src)
9999
throws ModelBuilderException {
100100
ModelBuilderResult result = buildModel(session, src);
101101
Model model = result.getRawModel();
102102
return transformPom(model, project);
103103
}
104104

105-
protected Model buildBom(RepositorySystemSession session, MavenProject project, Path src)
105+
protected Model buildBom(RepositorySystemSession session, MavenProject project, ModelSource src)
106106
throws ModelBuilderException {
107107
ModelBuilderResult result = buildModel(session, src);
108108
Model model = result.getEffectiveModel();
109109
return transformBom(model, project);
110110
}
111111

112-
protected Model buildNonPom(RepositorySystemSession session, MavenProject project, Path src)
112+
protected Model buildNonPom(RepositorySystemSession session, MavenProject project, ModelSource src)
113113
throws ModelBuilderException {
114114
Model model = buildEffectiveModel(session, src);
115115
return transformNonPom(model, project);
116116
}
117117

118-
private Model buildEffectiveModel(RepositorySystemSession session, Path src) throws ModelBuilderException {
118+
private Model buildEffectiveModel(RepositorySystemSession session, ModelSource src) throws ModelBuilderException {
119119
InternalSession iSession = InternalSession.from(session);
120120
ModelBuilderResult result = buildModel(session, src);
121121
Model model = result.getEffectiveModel();
@@ -222,12 +222,13 @@ private static String getDependencyKey(Dependency dependency) {
222222
+ (dependency.getClassifier() != null ? dependency.getClassifier() : "");
223223
}
224224

225-
private ModelBuilderResult buildModel(RepositorySystemSession session, Path src) throws ModelBuilderException {
225+
private ModelBuilderResult buildModel(RepositorySystemSession session, ModelSource src)
226+
throws ModelBuilderException {
226227
InternalSession iSession = InternalSession.from(session);
227228
ModelBuilderRequest.ModelBuilderRequestBuilder request = ModelBuilderRequest.builder();
228229
request.requestType(ModelBuilderRequest.RequestType.BUILD_CONSUMER);
229230
request.session(iSession);
230-
request.source(Sources.buildSource(src));
231+
request.source(src);
231232
request.locationTracking(false);
232233
request.systemProperties(session.getSystemProperties());
233234
request.userProperties(session.getUserProperties());

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
import javax.xml.stream.XMLStreamException;
2222

2323
import java.io.IOException;
24-
import java.nio.file.Path;
2524

2625
import org.apache.maven.api.model.Model;
2726
import org.apache.maven.api.services.ModelBuilderException;
27+
import org.apache.maven.api.services.ModelSource;
2828
import org.apache.maven.project.MavenProject;
2929
import org.eclipse.aether.RepositorySystemSession;
3030

@@ -33,6 +33,6 @@
3333
* of {@link ConsumerPomArtifactTransformer}.
3434
*/
3535
interface PomBuilder {
36-
Model build(RepositorySystemSession session, MavenProject project, Path src)
36+
Model build(RepositorySystemSession session, MavenProject project, ModelSource src)
3737
throws ModelBuilderException, IOException, XMLStreamException;
3838
}

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.function.Supplier;
3131

3232
import org.apache.maven.api.services.ModelBuilderException;
33+
import org.apache.maven.api.services.ModelSource;
3334
import org.apache.maven.artifact.DefaultArtifact;
3435
import org.apache.maven.internal.transformation.PomArtifactTransformer;
3536
import org.apache.maven.internal.transformation.TransformationFailedException;
@@ -48,7 +49,7 @@ class TransformedArtifact extends DefaultArtifact {
4849
private static final int SHA1_BUFFER_SIZE = 8192;
4950
private final PomArtifactTransformer pomArtifactTransformer;
5051
private final MavenProject project;
51-
private final Supplier<Path> sourcePathProvider;
52+
private final Supplier<ModelSource> sourcePathProvider;
5253
private final Path target;
5354
private final RepositorySystemSession session;
5455
private final AtomicReference<String> sourceState;
@@ -60,7 +61,7 @@ class TransformedArtifact extends DefaultArtifact {
6061
Path target,
6162
RepositorySystemSession session,
6263
org.apache.maven.artifact.Artifact source,
63-
Supplier<Path> sourcePathProvider,
64+
Supplier<ModelSource> sourcePathProvider,
6465
String classifier,
6566
String extension) {
6667
super(
@@ -105,20 +106,21 @@ public synchronized File getFile() {
105106

106107
private String mayUpdate() throws IOException, XMLStreamException, ModelBuilderException {
107108
String result;
108-
Path src = sourcePathProvider.get();
109+
ModelSource src = sourcePathProvider.get();
109110
if (src == null) {
110111
Files.deleteIfExists(target);
111112
result = null;
112-
} else if (!Files.exists(src)) {
113+
} else if (!Files.exists(src.getPath())) {
113114
Files.deleteIfExists(target);
114115
result = "";
115116
} else {
116-
String current = ChecksumAlgorithmHelper.calculate(src, List.of(new Sha1ChecksumAlgorithmFactory()))
117+
String current = ChecksumAlgorithmHelper.calculate(
118+
src.getPath(), List.of(new Sha1ChecksumAlgorithmFactory()))
117119
.get(Sha1ChecksumAlgorithmFactory.NAME);
118120
String existing = sourceState.get();
119121
if (!Files.exists(target) || !Objects.equals(current, existing)) {
120122
pomArtifactTransformer.transform(project, session, src, target);
121-
Files.setLastModifiedTime(target, Files.getLastModifiedTime(src));
123+
Files.setLastModifiedTime(target, Files.getLastModifiedTime(src.getPath()));
122124
}
123125
result = current;
124126
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import org.apache.maven.api.model.Model;
3030
import org.apache.maven.api.services.ModelBuilderException;
31+
import org.apache.maven.api.services.ModelSource;
3132
import org.apache.maven.internal.transformation.PomArtifactTransformer;
3233
import org.apache.maven.model.v4.MavenStaxReader;
3334
import org.apache.maven.model.v4.MavenStaxWriter;
@@ -58,7 +59,7 @@ public DeployRequest remapDeployArtifacts(RepositorySystemSession session, Deplo
5859
public void injectTransformedArtifacts(RepositorySystemSession session, MavenProject project) throws IOException {}
5960

6061
@Override
61-
public void transform(MavenProject project, RepositorySystemSession session, Path src, Path tgt)
62+
public void transform(MavenProject project, RepositorySystemSession session, ModelSource src, Path tgt)
6263
throws ModelBuilderException, XMLStreamException, IOException {
6364
throw new IllegalStateException("This transformer does not use this call.");
6465
}

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.Map;
2929

3030
import org.apache.maven.api.Constants;
31+
import org.apache.maven.api.services.Sources;
3132
import org.apache.maven.model.Model;
3233
import org.apache.maven.model.v4.MavenStaxReader;
3334
import org.apache.maven.project.MavenProject;
@@ -66,12 +67,12 @@ void transform() throws Exception {
6667
MavenProject project = new MavenProject(model);
6768
project.setOriginalModel(model);
6869
ConsumerPomArtifactTransformer t = new ConsumerPomArtifactTransformer((s, p, f) -> {
69-
try (InputStream is = Files.newInputStream(f)) {
70+
try (InputStream is = f.openStream()) {
7071
return DefaultConsumerPomBuilder.transformPom(new MavenStaxReader().read(is), project);
7172
}
7273
});
7374

74-
t.transform(project, systemSessionMock, beforePomFile, tempFile);
75+
t.transform(project, systemSessionMock, Sources.buildSource(beforePomFile), tempFile);
7576
}
7677
Diff diff = DiffBuilder.compare(afterPomFile.toFile())
7778
.withTest(tempFile.toFile())
@@ -98,12 +99,12 @@ void transformJarConsumerPom() throws Exception {
9899
MavenProject project = new MavenProject(model);
99100
project.setOriginalModel(model);
100101
ConsumerPomArtifactTransformer t = new ConsumerPomArtifactTransformer((s, p, f) -> {
101-
try (InputStream is = Files.newInputStream(f)) {
102+
try (InputStream is = f.openStream()) {
102103
return DefaultConsumerPomBuilder.transformNonPom(new MavenStaxReader().read(is), project);
103104
}
104105
});
105106

106-
t.transform(project, systemSessionMock, beforePomFile, tempFile);
107+
t.transform(project, systemSessionMock, Sources.buildSource(beforePomFile), tempFile);
107108
}
108109
Diff diff = DiffBuilder.compare(afterPomFile.toFile())
109110
.withTest(tempFile.toFile())

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ void testTrivialConsumer() throws Exception {
108108

109109
MavenProject project = new MavenProject(orgModel);
110110
project.setOriginalModel(new org.apache.maven.model.Model(orgModel));
111-
Model model = builder.build(session, project, file);
111+
Model model = builder.build(session, project, Sources.buildSource(file));
112112

113113
assertNotNull(model);
114114
}
@@ -135,7 +135,7 @@ void testSimpleConsumer() throws Exception {
135135
MavenProject project = new MavenProject(orgModel);
136136
project.setOriginalModel(new org.apache.maven.model.Model(orgModel));
137137
request.setRootDirectory(Paths.get("src/test/resources/consumer/simple"));
138-
Model model = builder.build(session, project, file);
138+
Model model = builder.build(session, project, Sources.buildSource(file));
139139

140140
assertNotNull(model);
141141
assertTrue(model.getProfiles().isEmpty());
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.it;
20+
21+
import java.io.File;
22+
23+
import org.junit.jupiter.api.Test;
24+
25+
/**
26+
* This is a test set for <a href="https://github.com/apache/maven/issues/11399">GH-11399</a>.
27+
*
28+
* Verifies that using flatten-maven-plugin with updatePomFile=true does not cause a false
29+
* parent cycle detection error during install phase. The issue occurred when the plugin
30+
* updated the POM file reference, causing the consumer POM builder to incorrectly detect
31+
* a cycle between the project and its parent.
32+
*
33+
* @see <a href="https://github.com/mojohaus/flatten-maven-plugin">flatten-maven-plugin</a>
34+
*/
35+
class MavenITgh11399FlattenPluginParentCycleTest extends AbstractMavenIntegrationTestCase {
36+
37+
/**
38+
* Verify that flatten-maven-plugin with updatePomFile=true and parent expansion
39+
* does not cause a false parent cycle detection error during install.
40+
*
41+
* The error was:
42+
* "The parents form a cycle: org.apache:apache:35 -> /path/to/pom.xml -> org.apache:apache:35"
43+
*
44+
* @throws Exception in case of failure
45+
*/
46+
@Test
47+
void testFlattenPluginWithParentExpansionDoesNotCauseCycle() throws Exception {
48+
File testDir = extractResources("/gh-11399-flatten-plugin-parent-cycle");
49+
50+
Verifier verifier = newVerifier(testDir.getAbsolutePath());
51+
verifier.setAutoclean(false);
52+
verifier.deleteArtifacts("org.apache.maven.its.mng8750");
53+
verifier.addCliArgument("install");
54+
verifier.execute();
55+
verifier.verifyErrorFreeLog();
56+
57+
// Verify that the flattened POM was created
58+
verifier.verifyFilePresent("target/.flattened-pom.xml");
59+
}
60+
}
61+

0 commit comments

Comments
 (0)