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 35e10b8a1f [MNG-8653] Fix 'all' phase and  add 'each' phase (#2191)
35e10b8a1f is described below

commit 35e10b8a1fd3e03e26f6e13e939586fcff820f5a
Author: Guillaume Nodet <gno...@gmail.com>
AuthorDate: Fri Mar 28 16:21:19 2025 +0100

    [MNG-8653] Fix 'all' phase and  add 'each' phase (#2191)
    
    Fixes the existing 'all' phase and introduces a new 'each' phase to better 
support
    hierarchical builds and concurrent execution:
    
    - 'all': Properly coordinates execution across parent-child project 
hierarchies
    - 'each': A new phase that executes for each individual project in isolation
    
    Key changes:
    - Enhanced BuildPlanExecutor to properly handle parent-child relationships 
in phases
    - Added new children() pointer type for explicit phase dependencies
    - Improved phase dependency management to ensure:
      - Parent's before:all executes before children's phases
      - Children's after:all executes before parent's after:all
      - Each project's phases execute in isolation within the 'each' phase
    - Added comprehensive documentation to BuildPlanExecutor
    - Added integration test to verify hierarchical phase execution
    
    This commit also fixes thread-safety issues in concurrent builds:
    - Added synchronization to ReactorReader for project operations
    - Improved thread-safety for project-level clean and install operations
    - Enhanced build step state management in concurrent scenarios
    
    Fixes: MNG-8653
---
 .../main/java/org/apache/maven/api/Lifecycle.java  |   1 +
 .../main/java/org/apache/maven/ReactorReader.java  |   8 +-
 .../internal/impl/DefaultLifecycleRegistry.java    |  63 ++++++-----
 .../org/apache/maven/internal/impl/Lifecycles.java |  53 +++++++++-
 .../internal/concurrent/BuildPlanExecutor.java     | 116 +++++++++++++++------
 .../lifecycle/internal/concurrent/BuildStep.java   |   4 +
 .../internal/concurrent/BuildPlanCreatorTest.java  |  32 ++++++
 ...fterAndEachPhasesWithConcurrentBuilderTest.java |  62 +++++++++++
 .../org/apache/maven/it/TestSuiteOrdering.java     |   1 +
 .../src/test/resources/mng-8653/child-1/pom.xml    |   6 ++
 .../src/test/resources/mng-8653/child-2/pom.xml    |   6 ++
 .../src/test/resources/mng-8653/pom.xml            |  92 ++++++++++++++++
 12 files changed, 383 insertions(+), 61 deletions(-)

diff --git 
a/api/maven-api-core/src/main/java/org/apache/maven/api/Lifecycle.java 
b/api/maven-api-core/src/main/java/org/apache/maven/api/Lifecycle.java
index de7fe1ff90..b1d3a4db32 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/Lifecycle.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Lifecycle.java
@@ -102,6 +102,7 @@ interface Phase {
         // Maven defined phases
         // ======================
         String ALL = "all";
+        String EACH = "each";
         String BUILD = "build";
         String INITIALIZE = "initialize";
         String VALIDATE = "validate";
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 debd7dbfc8..1c242712ba 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
@@ -338,14 +338,18 @@ private void processEvent(ExecutionEvent event) {
                     if (!Objects.equals(phase, phases.peekLast())) {
                         phases.addLast(phase);
                         if ("clean".equals(phase)) {
-                            cleanProjectLocalRepository(project);
+                            synchronized (project) {
+                                cleanProjectLocalRepository(project);
+                            }
                         }
                     }
                 }
                 break;
             case ProjectSucceeded:
             case ForkedProjectSucceeded:
-                installIntoProjectLocalRepository(project);
+                synchronized (project) {
+                    installIntoProjectLocalRepository(project);
+                }
                 break;
             default:
                 break;
diff --git 
a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultLifecycleRegistry.java
 
b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultLifecycleRegistry.java
index 859821ceac..60a6c0b32a 100644
--- 
a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultLifecycleRegistry.java
+++ 
b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultLifecycleRegistry.java
@@ -37,6 +37,7 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import org.apache.maven.api.DependencyScope;
 import org.apache.maven.api.Lifecycle;
 import org.apache.maven.api.model.InputLocation;
 import org.apache.maven.api.model.InputSource;
@@ -55,6 +56,7 @@
 import static org.apache.maven.api.Lifecycle.Phase.BUILD;
 import static org.apache.maven.api.Lifecycle.Phase.COMPILE;
 import static org.apache.maven.api.Lifecycle.Phase.DEPLOY;
+import static org.apache.maven.api.Lifecycle.Phase.EACH;
 import static org.apache.maven.api.Lifecycle.Phase.INITIALIZE;
 import static org.apache.maven.api.Lifecycle.Phase.INSTALL;
 import static org.apache.maven.api.Lifecycle.Phase.INTEGRATION_TEST;
@@ -71,6 +73,7 @@
 import static org.apache.maven.api.Lifecycle.Phase.VERIFY;
 import static org.apache.maven.internal.impl.Lifecycles.after;
 import static org.apache.maven.internal.impl.Lifecycles.alias;
+import static org.apache.maven.internal.impl.Lifecycles.children;
 import static org.apache.maven.internal.impl.Lifecycles.dependencies;
 import static org.apache.maven.internal.impl.Lifecycles.phase;
 import static org.apache.maven.internal.impl.Lifecycles.plugin;
@@ -91,6 +94,11 @@ public class DefaultLifecycleRegistry implements 
LifecycleRegistry {
     public static final InputLocation DEFAULT_LIFECYCLE_INPUT_LOCATION =
             new InputLocation(new InputSource(DEFAULT_LIFECYCLE_MODELID, 
null));
 
+    public static final String SCOPE_COMPILE = DependencyScope.COMPILE.id();
+    public static final String SCOPE_RUNTIME = DependencyScope.RUNTIME.id();
+    public static final String SCOPE_TEST_ONLY = 
DependencyScope.TEST_ONLY.id();
+    public static final String SCOPE_TEST = DependencyScope.TEST.id();
+
     private final List<LifecycleProvider> providers;
 
     public DefaultLifecycleRegistry() {
@@ -384,35 +392,38 @@ public Collection<Phase> phases() {
             // START SNIPPET: default
             return List.of(phase(
                     ALL,
-                    phase(VALIDATE, phase(INITIALIZE)),
+                    children(ALL),
                     phase(
-                            BUILD,
-                            after(VALIDATE),
-                            phase(SOURCES),
-                            phase(RESOURCES),
-                            phase(COMPILE, after(SOURCES), 
dependencies(COMPILE, READY)),
-                            phase(READY, after(COMPILE), after(RESOURCES)),
-                            phase(PACKAGE, after(READY), 
dependencies("runtime", PACKAGE))),
-                    phase(
-                            VERIFY,
-                            after(VALIDATE),
+                            EACH,
+                            phase(VALIDATE, phase(INITIALIZE)),
                             phase(
-                                    UNIT_TEST,
-                                    phase(TEST_SOURCES),
-                                    phase(TEST_RESOURCES),
-                                    phase(
-                                            TEST_COMPILE,
-                                            after(TEST_SOURCES),
-                                            after(READY),
-                                            dependencies("test-only", READY)),
+                                    BUILD,
+                                    after(VALIDATE),
+                                    phase(SOURCES),
+                                    phase(RESOURCES),
+                                    phase(COMPILE, after(SOURCES), 
dependencies(SCOPE_COMPILE, READY)),
+                                    phase(READY, after(COMPILE), 
after(RESOURCES)),
+                                    phase(PACKAGE, after(READY), 
dependencies(SCOPE_RUNTIME, PACKAGE))),
+                            phase(
+                                    VERIFY,
+                                    after(VALIDATE),
                                     phase(
-                                            TEST,
-                                            after(TEST_COMPILE),
-                                            after(TEST_RESOURCES),
-                                            dependencies("test", READY))),
-                            phase(INTEGRATION_TEST)),
-                    phase(INSTALL, after(PACKAGE)),
-                    phase(DEPLOY, after(PACKAGE))));
+                                            UNIT_TEST,
+                                            phase(TEST_SOURCES),
+                                            phase(TEST_RESOURCES),
+                                            phase(
+                                                    TEST_COMPILE,
+                                                    after(TEST_SOURCES),
+                                                    after(READY),
+                                                    
dependencies(SCOPE_TEST_ONLY, READY)),
+                                            phase(
+                                                    TEST,
+                                                    after(TEST_COMPILE),
+                                                    after(TEST_RESOURCES),
+                                                    dependencies(SCOPE_TEST, 
READY))),
+                                    phase(INTEGRATION_TEST)),
+                            phase(INSTALL, after(PACKAGE)),
+                            phase(DEPLOY, after(PACKAGE)))));
             // END SNIPPET: default
         }
 
diff --git 
a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/Lifecycles.java 
b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/Lifecycles.java
index 86e4daefc6..d7ddeeba3d 100644
--- 
a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/Lifecycles.java
+++ 
b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/Lifecycles.java
@@ -89,7 +89,7 @@ static Plugin plugin(String coords, String phase) {
     }
 
     /** Indicates the phase is after the phases given in arguments */
-    static Lifecycle.Link after(String b) {
+    static Lifecycle.Link after(String phase) {
         return new Lifecycle.Link() {
             @Override
             public Kind kind() {
@@ -101,10 +101,20 @@ public Lifecycle.Pointer pointer() {
                 return new Lifecycle.PhasePointer() {
                     @Override
                     public String phase() {
-                        return b;
+                        return phase;
+                    }
+
+                    @Override
+                    public String toString() {
+                        return "phase(" + phase + ")";
                     }
                 };
             }
+
+            @Override
+            public String toString() {
+                return "after(" + pointer() + ")";
+            }
         };
     }
 
@@ -128,8 +138,47 @@ public String phase() {
                     public String scope() {
                         return scope;
                     }
+
+                    @Override
+                    public String toString() {
+                        return "dependencies(" + scope + ", " + phase + ")";
+                    }
+                };
+            }
+
+            @Override
+            public String toString() {
+                return "after(" + pointer() + ")";
+            }
+        };
+    }
+
+    static Lifecycle.Link children(String phase) {
+        return new Lifecycle.Link() {
+            @Override
+            public Kind kind() {
+                return Kind.AFTER;
+            }
+
+            @Override
+            public Lifecycle.Pointer pointer() {
+                return new Lifecycle.ChildrenPointer() {
+                    @Override
+                    public String phase() {
+                        return phase;
+                    }
+
+                    @Override
+                    public String toString() {
+                        return "children(" + phase + ")";
+                    }
                 };
             }
+
+            @Override
+            public String toString() {
+                return "after(" + pointer() + ")";
+            }
         };
     }
 
diff --git 
a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java
 
b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java
index b3cfaee9f2..8b330cc048 100644
--- 
a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java
+++ 
b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java
@@ -103,17 +103,46 @@
 import static 
org.apache.maven.lifecycle.internal.concurrent.BuildStep.TEARDOWN;
 
 /**
- * Builds the full lifecycle in weave-mode (phase by phase as opposed to 
project-by-project).
- * <p>
- * This builder uses a number of threads equal to the minimum of the degree of 
concurrency (which is the thread count
- * set with <code>-T</code> on the command-line) and the number of projects to 
build. As such, building a single project
- * will always result in a sequential build, regardless of the thread count.
- * </p>
- * <strong>NOTE:</strong> This class is not part of any public api and can be 
changed or deleted without prior notice.
+ * Executes the Maven build plan in a concurrent manner, handling the 
lifecycle phases and plugin executions.
+ * This executor implements a weave-mode build strategy, where builds are 
executed phase-by-phase rather than
+ * project-by-project.
+ *
+ * <h2>Key Features:</h2>
+ * <ul>
+ *   <li>Concurrent execution of compatible build steps across projects</li>
+ *   <li>Thread-safety validation for plugins</li>
+ *   <li>Support for forked executions and lifecycle phases</li>
+ *   <li>Dynamic build plan adjustment during execution</li>
+ * </ul>
+ *
+ * <h2>Execution Strategy:</h2>
+ * <p>The executor follows these main steps:</p>
+ * <ol>
+ *   <li>Initial plan creation based on project dependencies and task 
segments</li>
+ *   <li>Concurrent execution of build steps while maintaining dependency 
order</li>
+ *   <li>Dynamic replanning when necessary (e.g., for forked executions)</li>
+ *   <li>Project setup, execution, and teardown phases management</li>
+ * </ol>
+ *
+ * <h2>Thread Management:</h2>
+ * <p>The number of threads used is determined by:</p>
+ * <pre>
+ * min(degreeOfConcurrency, numberOfProjects)
+ * </pre>
+ * where degreeOfConcurrency is set via the -T command-line option.
+ *
+ * <h2>Build Step States:</h2>
+ * <ul>
+ *   <li>CREATED: Initial state of a build step</li>
+ *   <li>PLANNING: Step is being planned</li>
+ *   <li>SCHEDULED: Step is queued for execution</li>
+ *   <li>EXECUTED: Step has completed successfully</li>
+ *   <li>FAILED: Step execution failed</li>
+ * </ul>
+ *
+ * <p><strong>NOTE:</strong> This class is not part of any public API and can 
be changed or deleted without prior notice.</p>
  *
  * @since 3.0
- *         Builds one or more lifecycles for a full module
- *         NOTE: This class is not part of any public api and can be changed 
or deleted without prior notice.
  */
 @Named
 public class BuildPlanExecutor {
@@ -225,6 +254,7 @@ public BuildPlan buildInitialPlan(List<TaskSegment> 
taskSegments) {
                 pplan.status.set(PLANNING); // the plan step always need 
planning
                 BuildStep setup = new BuildStep(SETUP, project, null);
                 BuildStep teardown = new BuildStep(TEARDOWN, project, null);
+                teardown.executeAfter(setup);
                 setup.executeAfter(pplan);
                 plan.steps(project).forEach(step -> {
                     if (step.predecessors.isEmpty()) {
@@ -322,6 +352,10 @@ private void executePlan() {
             global.start();
             lock.readLock().lock();
             try {
+                // Get all build steps that are:
+                // 1. Not yet started (CREATED status)
+                // 2. Have all their prerequisites completed (predecessors 
EXECUTED)
+                // 3. Successfully transition from CREATED to SCHEDULED state
                 plan.sortedNodes().stream()
                         .filter(step -> step.status.get() == CREATED)
                         .filter(step -> step.predecessors.stream().allMatch(s 
-> s.status.get() == EXECUTED))
@@ -356,6 +390,17 @@ private void executePlan() {
             }
         }
 
+        /**
+         * Executes all pending after:* phases for a failed project.
+         * This ensures proper cleanup is performed even when a build fails.
+         * Only executes after:xxx phases if their corresponding before:xxx 
phase
+         * has been either executed or failed.
+         *
+         * For example, if a project fails during 'compile', this will execute
+         * any configured 'after:compile' phases to ensure proper cleanup.
+         *
+         * @param failedStep The build step that failed, containing the 
project that needs cleanup
+         */
         private void executeAfterPhases(BuildStep failedStep) {
             if (failedStep == null || failedStep.project == null) {
                 return;
@@ -393,6 +438,17 @@ private void executeAfterPhases(BuildStep failedStep) {
             }
         }
 
+        /**
+         * Executes a single build step, which can be one of:
+         * - PLAN: Project build planning
+         * - SETUP: Project initialization
+         * - TEARDOWN: Project cleanup
+         * - Default: Actual mojo/plugin executions
+         *
+         * @param step The build step to execute
+         * @throws IOException If there's an IO error during execution
+         * @throws LifecycleExecutionException If there's a lifecycle 
execution error
+         */
         private void executeStep(BuildStep step) throws IOException, 
LifecycleExecutionException {
             Clock clock = getClock(step.project);
             switch (step.name) {
@@ -796,16 +852,27 @@ public BuildPlan calculateLifecycleMappings(
             plan.allSteps().filter(step -> step.phase != null).forEach(step -> 
{
                 Lifecycle.Phase phase = step.phase;
                 MavenProject project = step.project;
-                phase.links().stream()
-                        .filter(l -> l.pointer().type() != 
Lifecycle.Pointer.Type.PROJECT)
-                        .forEach(link -> {
-                            String n1 = phase.name();
-                            String n2 = link.pointer().phase();
-                            // for each project, if the phase in the build, 
link after it
-                            getLinkedProjects(projects, project, 
link).forEach(p -> plan.step(p, AFTER + n2)
-                                    .ifPresent(a -> plan.requiredStep(project, 
BEFORE + n1)
-                                            .executeAfter(a)));
+                phase.links().stream().forEach(link -> {
+                    BuildStep before = plan.requiredStep(project, BEFORE + 
phase.name());
+                    BuildStep after = plan.requiredStep(project, AFTER + 
phase.name());
+                    Lifecycle.Pointer pointer = link.pointer();
+                    String n2 = pointer.phase();
+                    if (pointer instanceof Lifecycle.DependenciesPointer) {
+                        // For dependencies: ensure current project's phase 
starts after dependency's phase completes
+                        // Example: project's compile starts after 
dependency's package completes
+                        // TODO: String scope = 
((Lifecycle.DependenciesPointer) pointer).scope();
+                        projects.get(project)
+                                .forEach(p -> plan.step(p, AFTER + 
n2).ifPresent(before::executeAfter));
+                    } else if (pointer instanceof Lifecycle.ChildrenPointer) {
+                        // For children: ensure bidirectional phase 
coordination
+                        project.getCollectedProjects().forEach(p -> {
+                            // 1. Child's phase start waits for parent's phase 
start
+                            plan.step(p, BEFORE + 
n2).ifPresent(before::executeBefore);
+                            // 2. Parent's phase completion waits for child's 
phase completion
+                            plan.step(p, AFTER + 
n2).ifPresent(after::executeAfter);
                         });
+                    }
+                });
             });
 
             // Keep projects in reactors by GAV
@@ -832,19 +899,6 @@ public BuildPlan calculateLifecycleMappings(
 
             return plan;
         }
-
-        private List<MavenProject> getLinkedProjects(
-                Map<MavenProject, List<MavenProject>> projects, MavenProject 
project, Lifecycle.Link link) {
-            if (link.pointer().type() == Lifecycle.Pointer.Type.DEPENDENCIES) {
-                // TODO: String scope = ((Lifecycle.DependenciesPointer) 
link.pointer()).scope();
-                return projects.get(project);
-            } else if (link.pointer().type() == 
Lifecycle.Pointer.Type.CHILDREN) {
-                return project.getCollectedProjects();
-            } else {
-                throw new IllegalArgumentException(
-                        "Unsupported pointer type: " + link.pointer().type());
-            }
-        }
     }
 
     private void resolvePlugin(MavenSession session, List<RemoteRepository> 
repositories, Plugin plugin) {
diff --git 
a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildStep.java
 
b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildStep.java
index e0b7c9598a..a97b641ca7 100644
--- 
a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildStep.java
+++ 
b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildStep.java
@@ -103,6 +103,10 @@ public void executeAfter(BuildStep stepToExecuteBefore) {
         }
     }
 
+    public void executeBefore(BuildStep stepToExecuteAfter) {
+        stepToExecuteAfter.executeAfter(this);
+    }
+
     public Stream<MojoExecution> executions() {
         return mojos.values().stream().flatMap(m -> m.values().stream());
     }
diff --git 
a/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanCreatorTest.java
 
b/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanCreatorTest.java
index dbe1a04b6d..1881f2dcbd 100644
--- 
a/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanCreatorTest.java
+++ 
b/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanCreatorTest.java
@@ -38,6 +38,7 @@ class BuildPlanCreatorTest {
     @Test
     void testMulti() {
         MavenProject project = new MavenProject();
+        project.setCollectedProjects(List.of());
         Map<MavenProject, List<MavenProject>> projects = 
Collections.singletonMap(project, Collections.emptyList());
 
         BuildPlan plan = calculateLifecycleMappings(projects, "package");
@@ -48,8 +49,10 @@ void testMulti() {
     @Test
     void testCondense() {
         MavenProject p1 = new MavenProject();
+        p1.setCollectedProjects(List.of());
         p1.setArtifactId("p1");
         MavenProject p2 = new MavenProject();
+        p2.setCollectedProjects(List.of());
         p2.setArtifactId("p2");
         Map<MavenProject, List<MavenProject>> projects = new HashMap<>();
         projects.put(p1, Collections.emptyList());
@@ -83,12 +86,41 @@ protected void mojo(Consumer<String> writer, MojoExecution 
mojoExecution) {}
     void testAlias() {
         MavenProject p1 = new MavenProject();
         p1.setArtifactId("p1");
+        p1.setCollectedProjects(List.of());
         Map<MavenProject, List<MavenProject>> projects = 
Collections.singletonMap(p1, Collections.emptyList());
 
         BuildPlan plan = calculateLifecycleMappings(projects, 
"generate-resources");
         assertNotNull(plan);
     }
 
+    @Test
+    void testAllPhase() {
+        MavenProject c1 = new MavenProject();
+        c1.setArtifactId("c1");
+        c1.setCollectedProjects(List.of());
+        MavenProject c2 = new MavenProject();
+        c2.setArtifactId("c2");
+        c2.setCollectedProjects(List.of());
+        MavenProject p = new MavenProject();
+        p.setArtifactId("p");
+        p.setCollectedProjects(List.of(c1, c2));
+        Map<MavenProject, List<MavenProject>> projects = Map.of(p, List.of(), 
c1, List.of(), c2, List.of());
+
+        BuildPlan plan = calculateLifecycleMappings(projects, "all");
+        assertNotNull(plan);
+        assertIsSuccessor(plan.requiredStep(p, "before:all"), 
plan.requiredStep(p, "before:each"));
+        assertIsSuccessor(plan.requiredStep(p, "before:all"), 
plan.requiredStep(c1, "before:all"));
+        assertIsSuccessor(plan.requiredStep(p, "before:all"), 
plan.requiredStep(c2, "before:all"));
+        assertIsSuccessor(plan.requiredStep(c1, "after:all"), 
plan.requiredStep(p, "after:all"));
+        assertIsSuccessor(plan.requiredStep(c2, "after:all"), 
plan.requiredStep(p, "after:all"));
+    }
+
+    private void assertIsSuccessor(BuildStep predecessor, BuildStep successor) 
{
+        assertTrue(
+                successor.isSuccessorOf(predecessor),
+                String.format("Expected '%s' to be a successor of '%s'", 
successor.toString(), predecessor.toString()));
+    }
+
     @SuppressWarnings("checkstyle:UnusedLocalVariable")
     private BuildPlan calculateLifecycleMappings(Map<MavenProject, 
List<MavenProject>> projects, String phase) {
         DefaultLifecycleRegistry lifecycles = new 
DefaultLifecycleRegistry(Collections.emptyList());
diff --git 
a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8653AfterAndEachPhasesWithConcurrentBuilderTest.java
 
b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8653AfterAndEachPhasesWithConcurrentBuilderTest.java
new file mode 100644
index 0000000000..d5194a539b
--- /dev/null
+++ 
b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8653AfterAndEachPhasesWithConcurrentBuilderTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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 java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * This is a test set for <a 
href="https://issues.apache.org/jira/browse/MNG-8653";>MNG-8653</a>.
+ */
+class MavenITmng8653AfterAndEachPhasesWithConcurrentBuilderTest extends 
AbstractMavenIntegrationTestCase {
+
+    MavenITmng8653AfterAndEachPhasesWithConcurrentBuilderTest() {
+        super("(4.0.0-rc-3,)");
+    }
+
+    /**
+     *  Verify the dependency management of the consumer POM is computed 
correctly
+     */
+    @Test
+    void testIt() throws Exception {
+        Path basedir = 
extractResources("/mng-8653").getAbsoluteFile().toPath();
+
+        Verifier verifier = newVerifier(basedir.toString());
+        verifier.addCliArguments("compile", "-b", "concurrent", "-T8");
+        verifier.execute();
+        verifier.verifyErrorFreeLog();
+
+        List<String> lines = verifier.loadLogLines();
+        List<String> hallo = lines.stream().filter(l -> 
l.contains("Hallo")).toList();
+
+        // Verify parent's before:all is first
+        assertTrue(
+                hallo.get(0).contains("'before:all' phase from 'parent'"),
+                "First line should be parent's before:all but was: " + 
hallo.get(0));
+
+        // Verify parent's after:all is last
+        assertTrue(
+                hallo.get(hallo.size() - 1).contains("'after:all' phase from 
'parent'"),
+                "Last line should be parent's after:all but was: " + 
hallo.get(hallo.size() - 1));
+    }
+}
diff --git 
a/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java 
b/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java
index 364f4cd86d..8404b80183 100644
--- a/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java
+++ b/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java
@@ -101,6 +101,7 @@ public TestSuiteOrdering() {
          * the tests are to finishing. Newer tests are also more likely to 
fail, so this is
          * a fail fast technique as well.
          */
+        
suite.addTestSuite(MavenITmng8653AfterAndEachPhasesWithConcurrentBuilderTest.class);
         suite.addTestSuite(MavenITmng5668AfterPhaseExecutionTest.class);
         suite.addTestSuite(MavenITmng8648ProjectStartedEventsTest.class);
         
suite.addTestSuite(MavenITmng8645ConsumerPomDependencyManagementTest.class);
diff --git a/its/core-it-suite/src/test/resources/mng-8653/child-1/pom.xml 
b/its/core-it-suite/src/test/resources/mng-8653/child-1/pom.xml
new file mode 100644
index 0000000000..95b14b9006
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/mng-8653/child-1/pom.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.1.0"; root="true">
+  <parent />
+  <artifactId>child-1</artifactId>
+  <packaging>pom</packaging>
+</project>
diff --git a/its/core-it-suite/src/test/resources/mng-8653/child-2/pom.xml 
b/its/core-it-suite/src/test/resources/mng-8653/child-2/pom.xml
new file mode 100644
index 0000000000..a37c0aab56
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/mng-8653/child-2/pom.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.1.0"; root="true">
+  <parent />
+  <artifactId>child-2</artifactId>
+  <packaging>pom</packaging>
+</project>
diff --git a/its/core-it-suite/src/test/resources/mng-8653/pom.xml 
b/its/core-it-suite/src/test/resources/mng-8653/pom.xml
new file mode 100644
index 0000000000..26d0cd54a2
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/mng-8653/pom.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.1.0"; root="true">
+
+  <groupId>org.apache.maven.it.mng8653</groupId>
+  <artifactId>parent</artifactId>
+  <version>1.0.0</version>
+  <packaging>pom</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>com.soebes.maven.plugins</groupId>
+        <artifactId>echo-maven-plugin</artifactId>
+        <version>0.5.0</version>
+        <executions>
+          <execution>
+            <id>before-ready</id>
+            <goals>
+              <goal>echo</goal>
+            </goals>
+            <phase>before:ready</phase>
+            <configuration>
+              <echos>
+                <echo>Hallo 'before:ready' phase from 
'${project.artifactId}'.</echo>
+              </echos>
+            </configuration>
+          </execution>
+          <execution>
+            <id>before-each</id>
+            <goals>
+              <goal>echo</goal>
+            </goals>
+            <phase>before:each</phase>
+            <configuration>
+              <echos>
+                <echo>Hallo 'before:each' phase from 
'${project.artifactId}'.</echo>
+              </echos>
+            </configuration>
+          </execution>
+          <execution>
+            <id>after-each</id>
+            <goals>
+              <goal>echo</goal>
+            </goals>
+            <phase>after:each</phase>
+            <configuration>
+              <echos>
+                <echo>Hallo 'after:each' phase from 
'${project.artifactId}'.</echo>
+              </echos>
+            </configuration>
+          </execution>
+          <execution>
+            <id>before-all</id>
+            <goals>
+              <goal>echo</goal>
+            </goals>
+            <phase>before:all</phase>
+            <configuration>
+              <echos>
+                <echo>Hallo 'before:all' phase from 
'${project.artifactId}'.</echo>
+              </echos>
+            </configuration>
+          </execution>
+          <execution>
+            <id>all</id>
+            <goals>
+              <goal>echo</goal>
+            </goals>
+            <phase>all</phase>
+            <configuration>
+              <echos>
+                <echo>Hallo 'all' phase from '${project.artifactId}'.</echo>
+              </echos>
+            </configuration>
+          </execution>
+          <execution>
+            <id>after-all</id>
+            <goals>
+              <goal>echo</goal>
+            </goals>
+            <phase>after:all</phase>
+            <configuration>
+              <echos>
+                <echo>Hallo 'after:all' phase from 
'${project.artifactId}'.</echo>
+              </echos>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>

Reply via email to