You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by sl...@apache.org on 2026/10/05 20:04:06 UTC

(maven) 01/01: Fix #13294: build a parent outside the reactor once per POM

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

slachiewicz pushed a commit to branch agent/model-reread-13294-4.0.x
in repository https://gitbox.apache.org/repos/asf/maven.git

commit 982d09eefefa20e4890650c8f14152a7c14e5425
Author: Sylwester Lachiewicz <sl...@apache.org>
AuthorDate: Mon Oct 5 21:53:28 2026 +0200

    Fix #13294: build a parent outside the reactor once per POM
    
    Reactor POM sources are request-scoped in the model cache, so building
    the parent project again for each child re-read its whole parent chain
    from disk. Children of the same parent POM now share one parent project.
---
 .../maven/project/DefaultProjectBuilder.java       | 17 +++++-
 .../apache/maven/project/ProjectBuilderTest.java   | 66 ++++++++++++++++++++++
 2 files changed, 81 insertions(+), 2 deletions(-)

diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
index 51ee3e0d01..1ec66c6a27 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
@@ -316,6 +316,11 @@ class BuildSession implements AutoCloseable {
         private final ModelBuilder.ModelBuilderSession modelBuilderSession;
         private final Map<String, MavenProject> projectIndex = new ConcurrentHashMap<>(256);
 
+        // Parents outside the reactor, built once per POM file and shared by all their children. Reactor POM
+        // sources are request-scoped in the model cache, so rebuilding a parent per child re-reads its whole
+        // parent chain from disk each time
+        private final Map<Path, MavenProject> parentProjects = new ConcurrentHashMap<>();
+
         // Store computed repositories per project to avoid leakage between projects
         private final Map<String, List<ArtifactRepository>> projectRepositories = new ConcurrentHashMap<>();
 
@@ -1079,9 +1084,17 @@ private void initParent(MavenProject project, ModelBuilderResult result) {
                     Path parentPomFile = parentModel.getPomFile();
                     if (parentPomFile != null) {
                         project.setParentFile(parentPomFile.toFile());
+                        Path parentKey = parentPomFile.toAbsolutePath().normalize();
                         try {
-                            parent = build(true, parentPomFile, Sources.buildSource(parentPomFile))
-                                    .getProject();
+                            parent = parentProjects.get(parentKey);
+                            if (parent == null) {
+                                parent = build(true, parentPomFile, Sources.buildSource(parentPomFile))
+                                        .getProject();
+                                MavenProject existing = parentProjects.putIfAbsent(parentKey, parent);
+                                if (existing != null) {
+                                    parent = existing;
+                                }
+                            }
                         } catch (ProjectBuildingException e) {
                             // MNG-4488 where let invalid parents slide on by
                             if (logger.isDebugEnabled()) {
diff --git a/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java b/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java
index 0d55cbed90..8565287c55 100644
--- a/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java
+++ b/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java
@@ -57,6 +57,7 @@
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -197,6 +198,71 @@ void testReadModifiedPoms(@TempDir Path tempDir) throws Exception {
         assertThat(result.getProject().getProperties(), hasKey((Object) "addedProperty"));
     }
 
+    @Test
+    void testParentOutsideReactorIsBuiltOnce(@TempDir Path tempDir) throws Exception {
+        Files.writeString(tempDir.resolve("pom.xml"), """
+                <project>
+                  <modelVersion>4.0.0</modelVersion>
+                  <groupId>test</groupId>
+                  <artifactId>root</artifactId>
+                  <version>1</version>
+                  <packaging>pom</packaging>
+                  <properties>
+                    <inherited>root</inherited>
+                  </properties>
+                </project>
+                """);
+        Path parentPom = Files.createDirectory(tempDir.resolve("parent")).resolve("pom.xml");
+        Files.writeString(parentPom, """
+                <project>
+                  <modelVersion>4.0.0</modelVersion>
+                  <parent>
+                    <groupId>test</groupId>
+                    <artifactId>root</artifactId>
+                    <version>1</version>
+                  </parent>
+                  <artifactId>parent</artifactId>
+                  <packaging>pom</packaging>
+                </project>
+                """);
+        List<File> children = new ArrayList<>();
+        for (String name : List.of("a", "b")) {
+            Path childPom = Files.createDirectories(tempDir.resolve("parent").resolve(name))
+                    .resolve("pom.xml");
+            Files.writeString(childPom, """
+                    <project>
+                      <modelVersion>4.0.0</modelVersion>
+                      <parent>
+                        <groupId>test</groupId>
+                        <artifactId>parent</artifactId>
+                        <version>1</version>
+                      </parent>
+                      <artifactId>%s</artifactId>
+                    </project>
+                    """.formatted(name));
+            children.add(childPom.toFile());
+        }
+
+        MavenSession session = createMavenSession(null);
+        session.getRequest().setRootDirectory(tempDir);
+        ProjectBuildingRequest request = new DefaultProjectBuildingRequest();
+        request.setRepositorySession(session.getRepositorySession());
+        List<ProjectBuildingResult> results = getContainer()
+                .lookup(org.apache.maven.project.ProjectBuilder.class)
+                .build(children, false, request);
+
+        assertEquals(2, results.size());
+        MavenProject a = results.get(0).getProject();
+        MavenProject b = results.get(1).getProject();
+        assertEquals("root", a.getProperties().getProperty("inherited"));
+        assertNotNull(a.getParent());
+        assertEquals("parent", a.getParent().getArtifactId());
+        assertEquals("root", a.getParent().getParent().getArtifactId());
+        // both children share one parent chain instead of rebuilding it from disk (GH-13294)
+        assertSame(a.getParent(), b.getParent());
+        assertSame(a.getParent().getParent(), b.getParent().getParent());
+    }
+
     @Test
     void testReadErroneousMavenProjectContainsReference() throws Exception {
         File pomFile = new File("src/test/resources/projects/artifactMissingVersion/pom.xml").getAbsoluteFile();