diff --git a/impl/maven-core/src/main/java/org/apache/maven/ReactorReader.java b/impl/maven-core/src/main/java/org/apache/maven/ReactorReader.java
index db4882e38696..8217c743e48a 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/ReactorReader.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/ReactorReader.java
@@ -352,6 +352,12 @@ private File findInProjectLocalRepository(Artifact artifact) {
* The mojo started event is also captured to determine the lifecycle
* phases the project has been through.
*
+ *
When a project enters its clean phase, its artifacts are cleaned from the
+ * project-local-repo (per-GAV scope). Since the project-local-repo is located
+ * under {@code .mvn/} (not under {@code target/}), it is not affected by
+ * maven-clean-plugin's deletion of {@code target/}, eliminating the race
+ * condition between clean and install operations in parallel builds.
+ *
* @param event the execution event
*/
private void processEvent(ExecutionEvent event) {
@@ -364,9 +370,7 @@ private void processEvent(ExecutionEvent event) {
if (!Objects.equals(phase, phases.peekLast())) {
phases.addLast(phase);
if ("clean".equals(phase)) {
- synchronized (project) {
- cleanProjectLocalRepository(project);
- }
+ cleanProjectLocalRepository(project);
}
}
}
@@ -402,6 +406,12 @@ private void installIntoProjectLocalRepository(MavenProject project) {
}
}
+ /**
+ * Cleans the project-local-repo artifacts for the given project's GAV coordinates.
+ * Since the project-local-repo is under {@code .mvn/} and not {@code target/},
+ * it is not affected by maven-clean-plugin's deletion of {@code target/},
+ * so there is no race between clean and install operations in parallel builds.
+ */
private void cleanProjectLocalRepository(MavenProject project) {
try {
Path artifactPath = getProjectLocalRepo()
@@ -505,18 +515,7 @@ private Path getArtifactPath(
private Path getProjectLocalRepo() {
if (projectLocalRepository == null) {
Path root = session.getRequest().getRootDirectory();
- List projects = session.getProjects();
- if (projects != null) {
- projectLocalRepository = projects.stream()
- .filter(project -> Objects.equals(root.toFile(), project.getBasedir()))
- .findFirst()
- .map(project -> project.getBuild().getDirectory())
- .map(Paths::get)
- .orElseGet(() -> root.resolve("target"))
- .resolve(PROJECT_LOCAL_REPO);
- } else {
- return root.resolve("target").resolve(PROJECT_LOCAL_REPO);
- }
+ projectLocalRepository = root.resolve(".mvn").resolve(PROJECT_LOCAL_REPO);
}
return projectLocalRepository;
}
diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12646ProjectLocalRepoCleanRaceTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12646ProjectLocalRepoCleanRaceTest.java
new file mode 100644
index 000000000000..bc06787f36e0
--- /dev/null
+++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12646ProjectLocalRepoCleanRaceTest.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.it;
+
+import java.nio.file.Path;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * This is a test set for GH-12646.
+ *
+ * Verifies that parallel builds with {@code clean install} do not fail with a race condition
+ * when the root pom has a parent (super-pom) and other modules write to
+ * {@code target/project-local-repo} while the root project's clean phase is running.
+ *
+ * @since 4.0.0-rc-6
+ */
+class MavenITgh12646ProjectLocalRepoCleanRaceTest extends AbstractMavenIntegrationTestCase {
+
+ /**
+ * Verify that a parallel {@code clean install} succeeds when the root pom has a parent
+ * that is also part of the reactor. In this scenario:
+ *
+ * - super-pom builds first (no dependencies)
+ * - root pom and module-a..d start in parallel (all depend on super-pom)
+ * - root pom's clean phase runs maven-clean-plugin which deletes target/
+ * - modules complete and install artifacts into target/project-local-repo
+ *
+ * Without the fix, step 3 and 4 race, causing maven-clean-plugin to fail because
+ * target/project-local-repo is being written to while it tries to delete target/.
+ */
+ @Test
+ void testParallelCleanInstallWithParentPom() throws Exception {
+ Path testDir = extractResources("gh-12646-project-local-repo-clean-race");
+
+ Verifier verifier = newVerifier(testDir);
+ verifier.addCliArgument("-T");
+ verifier.addCliArgument("4");
+ verifier.addCliArguments("clean", "install");
+ verifier.execute();
+ verifier.verifyErrorFreeLog();
+ }
+}
diff --git a/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/.mvn/.gitkeep b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/.mvn/.gitkeep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-a/pom.xml b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-a/pom.xml
new file mode 100644
index 000000000000..0bd336766545
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-a/pom.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ org.apache.maven.its.gh12646
+ super-pom
+ 1.0-SNAPSHOT
+ ../super-pom
+
+
+ module-a
+
diff --git a/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-a/src/main/java/a/A.java b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-a/src/main/java/a/A.java
new file mode 100644
index 000000000000..4f3732128374
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-a/src/main/java/a/A.java
@@ -0,0 +1,7 @@
+package a;
+
+public class A {
+ public String greet() {
+ return "Hello from A";
+ }
+}
diff --git a/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-b/pom.xml b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-b/pom.xml
new file mode 100644
index 000000000000..cc2481ca8048
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-b/pom.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ org.apache.maven.its.gh12646
+ super-pom
+ 1.0-SNAPSHOT
+ ../super-pom
+
+
+ module-b
+
diff --git a/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-b/src/main/java/b/B.java b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-b/src/main/java/b/B.java
new file mode 100644
index 000000000000..0dda156c7e65
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-b/src/main/java/b/B.java
@@ -0,0 +1,7 @@
+package b;
+
+public class B {
+ public String greet() {
+ return "Hello from B";
+ }
+}
diff --git a/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-c/pom.xml b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-c/pom.xml
new file mode 100644
index 000000000000..118156c2f73a
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-c/pom.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ org.apache.maven.its.gh12646
+ super-pom
+ 1.0-SNAPSHOT
+ ../super-pom
+
+
+ module-c
+
diff --git a/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-c/src/main/java/c/C.java b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-c/src/main/java/c/C.java
new file mode 100644
index 000000000000..55e1d95b650c
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-c/src/main/java/c/C.java
@@ -0,0 +1,7 @@
+package c;
+
+public class C {
+ public String greet() {
+ return "Hello from C";
+ }
+}
diff --git a/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-d/pom.xml b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-d/pom.xml
new file mode 100644
index 000000000000..88e8aa163899
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-d/pom.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ org.apache.maven.its.gh12646
+ super-pom
+ 1.0-SNAPSHOT
+ ../super-pom
+
+
+ module-d
+
diff --git a/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-d/src/main/java/d/D.java b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-d/src/main/java/d/D.java
new file mode 100644
index 000000000000..fba6e63f6878
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/module-d/src/main/java/d/D.java
@@ -0,0 +1,7 @@
+package d;
+
+public class D {
+ public String greet() {
+ return "Hello from D";
+ }
+}
diff --git a/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/pom.xml b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/pom.xml
new file mode 100644
index 000000000000..9c80f90a35f3
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/pom.xml
@@ -0,0 +1,38 @@
+
+
+
+
+ org.apache.maven.its.gh12646
+ super-pom
+ 1.0-SNAPSHOT
+ super-pom
+
+
+ root
+ pom
+
+
+ super-pom
+ module-a
+ module-b
+ module-c
+ module-d
+
+
diff --git a/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/super-pom/pom.xml b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/super-pom/pom.xml
new file mode 100644
index 000000000000..9df54d716ac8
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-12646-project-local-repo-clean-race/super-pom/pom.xml
@@ -0,0 +1,25 @@
+
+
+
+ org.apache.maven.its.gh12646
+ super-pom
+ 1.0-SNAPSHOT
+ pom
+