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: + *
    + *
  1. super-pom builds first (no dependencies)
  2. + *
  3. root pom and module-a..d start in parallel (all depend on super-pom)
  4. + *
  5. root pom's clean phase runs maven-clean-plugin which deletes target/
  6. + *
  7. modules complete and install artifacts into target/project-local-repo
  8. + *
+ * 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 +