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>