This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch maven-4.0.x
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/maven-4.0.x by this push:
new 1b53468bfe Fix #12074: prevent false parent cycle with shade plugin's
dependency-reduced-pom.xml (#12079)
1b53468bfe is described below
commit 1b53468bfe32771351d4c28d4f677c68240cb32f
Author: Guillaume Nodet <[email protected]>
AuthorDate: Tue May 19 15:33:49 2026 +0200
Fix #12074: prevent false parent cycle with shade plugin's
dependency-reduced-pom.xml (#12079)
In readParentLocally(), the GA mismatch check ran after readAsParentModel()
which recursively resolves the candidate's parent chain. When a generated
POM
(e.g., dependency-reduced-pom.xml) sits alongside the project POM and the
default relative path resolves to the wrong POM, the recursive resolution
adds the shared parent's model ID to the chain — triggering a false cycle.
Move the GA check before the recursive call by reading only the file model
first. This matches the behavior of the Maven 3 model builder which checked
GA before recursing.
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
---
.../maven/impl/model/DefaultModelBuilder.java | 26 +++++----
.../maven/impl/model/ParentCycleDetectionTest.java | 63 ++++++++++++++++++++++
2 files changed, 78 insertions(+), 11 deletions(-)
diff --git
a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
index 7e155fa4dd..3c4cd3c4b0 100644
---
a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
+++
b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
@@ -1116,6 +1116,21 @@ private Model readParentLocally(
try {
ModelBuilderSessionState derived = derive(candidateSource);
+
+ // Check GA match BEFORE readAsParentModel() which recursively
resolves
+ // the candidate's parent chain and can trigger false cycle
detection (GH-12074).
+ Model fileModel = derived.readFileModel();
+ String fileGroupId = getGroupId(fileModel);
+ String fileArtifactId = fileModel.getArtifactId();
+
+ if (fileGroupId == null
+ || !fileGroupId.equals(parent.getGroupId())
+ || fileArtifactId == null
+ || !fileArtifactId.equals(parent.getArtifactId())) {
+ mismatchRelativePathAndGA(childModel, fileGroupId,
fileArtifactId);
+ return null;
+ }
+
Model candidateModel =
derived.readAsParentModel(profileActivationContext, parentChain);
// Add profiles from parent, preserving model ID tracking
for (Map.Entry<String, List<Profile>> entry :
@@ -1123,19 +1138,8 @@ private Model readParentLocally(
addActivePomProfiles(entry.getKey(), entry.getValue());
}
- String groupId = getGroupId(candidateModel);
- String artifactId = candidateModel.getArtifactId();
String version = getVersion(candidateModel);
- // Ensure that relative path and GA match, if both are provided
- if (groupId == null
- || !groupId.equals(parent.getGroupId())
- || artifactId == null
- || !artifactId.equals(parent.getArtifactId())) {
- mismatchRelativePathAndGA(childModel, groupId, artifactId);
- return null;
- }
-
if (version != null && parent.getVersion() != null &&
!version.equals(parent.getVersion())) {
try {
VersionRange parentRange =
versionParser.parseVersionRange(parent.getVersion());
diff --git
a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/ParentCycleDetectionTest.java
b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/ParentCycleDetectionTest.java
index f0db48c998..c133ffb1ab 100644
---
a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/ParentCycleDetectionTest.java
+++
b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/ParentCycleDetectionTest.java
@@ -187,6 +187,69 @@ void testDirectCycleDetection(@TempDir Path tempDir)
throws IOException {
}
}
+ /**
+ * Reproduces GH-12074: dependency-reduced-pom.xml from shade plugin
causes false parent cycle.
+ *
+ * When a POM's parent relative path resolves to a POM with a different GA
(e.g., the
+ * default ".." from dependency-reduced-pom.xml resolves to a sibling
project POM), the model
+ * builder should detect the GA mismatch early and skip local resolution —
not recursively
+ * read the candidate's parents and trigger a false cycle.
+ */
+ @Test
+ void testNoFalseCycleWhenRelativePathResolvesToWrongPom(@TempDir Path
tempDir) throws IOException {
+ Files.createDirectories(tempDir.resolve(".mvn"));
+
+ // Root POM found by resolving ".." from the project directory.
+ // It has a different GA than the expected parent but shares the same
parent reference.
+ Path rootPom = tempDir.resolve("pom.xml");
+ Files.writeString(rootPom, """
+ <project xmlns="http://maven.apache.org/POM/4.0.0">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>test</groupId>
+ <artifactId>parent</artifactId>
+ <version>1.0</version>
+ <relativePath/>
+ </parent>
+ <artifactId>root</artifactId>
+ </project>
+ """);
+
+ // Project POM in a subdirectory, referencing the same parent.
+ // Default relativePath ".." resolves to rootPom above, which has GA
test:root (not test:parent).
+ Path projectPom = tempDir.resolve("project").resolve("pom.xml");
+ Files.createDirectories(projectPom.getParent());
+ Files.writeString(projectPom, """
+ <project xmlns="http://maven.apache.org/POM/4.0.0">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>test</groupId>
+ <artifactId>parent</artifactId>
+ <version>1.0</version>
+ </parent>
+ <artifactId>project</artifactId>
+ </project>
+ """);
+
+ // Use BUILD_EFFECTIVE to match what the shade plugin triggers via
compat ProjectBuilder
+ ModelBuilderRequest request = ModelBuilderRequest.builder()
+ .session(session)
+ .source(Sources.buildSource(projectPom))
+ .requestType(ModelBuilderRequest.RequestType.BUILD_EFFECTIVE)
+ .build();
+
+ try {
+ modelBuilder.newSession().build(request);
+ } catch (StackOverflowError error) {
+ fail("StackOverflowError — cycle detection not working");
+ } catch (ModelBuilderException exception) {
+ // Parent not found externally is expected; a cycle error is not
+ if (exception.getMessage().contains("cycle")) {
+ fail("False parent cycle detected: " + exception.getMessage());
+ }
+ }
+ }
+
@Test
void testMultipleModulesWithSameParentDoNotCauseCycle(@TempDir Path
tempDir) throws IOException {
// Create .mvn directory to mark root