This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/master by this push:
     new cfcd638823 Fix #12074: prevent false parent cycle with shade plugin's 
dependency-reduced-pom.xml (#12078)
cfcd638823 is described below

commit cfcd638823f528cc0d3a1202a7491fc9f607e858
Author: Guillaume Nodet <[email protected]>
AuthorDate: Tue May 19 15:32:47 2026 +0200

    Fix #12074: prevent false parent cycle with shade plugin's 
dependency-reduced-pom.xml (#12078)
    
    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      | 24 +++++----
 .../maven/impl/model/ParentCycleDetectionTest.java | 63 ++++++++++++++++++++++
 2 files changed, 77 insertions(+), 10 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 cd38dc88b9..aa282d2723 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
@@ -1134,6 +1134,20 @@ 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 (parent.getGroupId() != null && (fileGroupId == null || 
!fileGroupId.equals(parent.getGroupId()))
+                        || parent.getArtifactId() != null
+                                && (fileArtifactId == null || 
!fileArtifactId.equals(parent.getArtifactId()))) {
+                    mismatchRelativePathAndGA(childModel, parent, 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 :
@@ -1141,18 +1155,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 (parent.getGroupId() != null && (groupId == null || 
!groupId.equals(parent.getGroupId()))
-                        || parent.getArtifactId() != null
-                                && (artifactId == null || 
!artifactId.equals(parent.getArtifactId()))) {
-                    mismatchRelativePathAndGA(childModel, parent, 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 7fb2710205..b31d680048 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

Reply via email to