[MNG-8244] Using before:all / all / after:all is not triggered (#1973)

This commit is contained in:
Guillaume Nodet 2024-12-14 00:04:37 +01:00 committed by GitHub
parent 1fd7f879a4
commit 5d449f9a24
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 325 additions and 169 deletions

View File

@ -20,7 +20,6 @@ package org.apache.maven.api;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import org.apache.maven.api.annotations.Experimental;
@ -66,10 +65,19 @@ public interface Lifecycle extends ExtensibleEnum {
String id();
/**
* Collection of phases for this lifecycle
* Collection of main phases for this lifecycle
*/
Collection<Phase> phases();
/**
* Collection of main phases for this lifecycle used with the Maven 3 builders.
* Those builders does not operate on a graph, but on the list and expect a slightly
* different ordering (mainly unit test being executed before packaging).
*/
default Collection<Phase> v3phases() {
return phases();
}
/**
* Stream of phases containing all child phases recursively.
*/
@ -82,14 +90,6 @@ public interface Lifecycle extends ExtensibleEnum {
*/
Collection<Alias> aliases();
/**
* Pre-ordered list of phases.
* If not provided, a default order will be computed.
*/
default Optional<List<String>> orderedPhases() {
return Optional.empty();
}
/**
* A phase in the lifecycle.
*
@ -101,6 +101,7 @@ public interface Lifecycle extends ExtensibleEnum {
// ======================
// Maven defined phases
// ======================
String ALL = "all";
String BUILD = "build";
String INITIALIZE = "initialize";
String VALIDATE = "validate";

View File

@ -24,7 +24,6 @@ import javax.inject.Provider;
import javax.inject.Singleton;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@ -50,6 +49,9 @@ import org.apache.maven.lifecycle.mapping.LifecyclePhase;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import static org.apache.maven.api.Lifecycle.AFTER;
import static org.apache.maven.api.Lifecycle.BEFORE;
import static org.apache.maven.api.Lifecycle.Phase.ALL;
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;
@ -129,34 +131,20 @@ public class DefaultLifecycleRegistry implements LifecycleRegistry {
public List<String> computePhases(Lifecycle lifecycle) {
Graph graph = new Graph();
lifecycle.phases().forEach(phase -> addPhase(graph, null, null, phase));
addPhases(graph, null, null, lifecycle.v3phases());
List<String> allPhases = graph.visitAll();
Collections.reverse(allPhases);
List<String> computed =
allPhases.stream().filter(s -> !s.startsWith("$")).collect(Collectors.toList());
List<String> given = lifecycle.orderedPhases().orElse(null);
if (given != null) {
if (given.size() != computed.size()) {
Set<String> s1 =
given.stream().filter(s -> !computed.contains(s)).collect(Collectors.toSet());
Set<String> s2 =
computed.stream().filter(s -> !given.contains(s)).collect(Collectors.toSet());
throw new IllegalStateException(
"List of phases differ in size: expected " + computed.size() + ", but received " + given.size()
+ (s1.isEmpty() ? "" : ", missing " + s1)
+ (s2.isEmpty() ? "" : ", unexpected " + s2));
}
return given;
}
return computed;
}
private static void addPhase(
Graph graph, Graph.Vertex before, Graph.Vertex after, org.apache.maven.api.Lifecycle.Phase phase) {
Graph.Vertex ep0 = graph.addVertex("$" + phase.name());
Graph.Vertex ep0 = graph.addVertex(BEFORE + phase.name());
Graph.Vertex ep1 = graph.addVertex("$$" + phase.name());
Graph.Vertex ep2 = graph.addVertex(phase.name());
Graph.Vertex ep3 = graph.addVertex("$$$" + phase.name());
Graph.Vertex ep3 = graph.addVertex(AFTER + phase.name());
graph.addEdge(ep0, ep1);
graph.addEdge(ep1, ep2);
graph.addEdge(ep2, ep3);
@ -171,11 +159,28 @@ public class DefaultLifecycleRegistry implements LifecycleRegistry {
if (link.kind() == Lifecycle.Link.Kind.AFTER) {
graph.addEdge(graph.addVertex(link.pointer().phase()), ep0);
} else {
graph.addEdge(ep3, graph.addVertex("$" + link.pointer().phase()));
graph.addEdge(ep3, graph.addVertex(BEFORE + link.pointer().phase()));
}
}
});
phase.phases().forEach(child -> addPhase(graph, ep1, ep2, child));
addPhases(graph, ep1, ep2, phase.phases());
}
private static void addPhases(
Graph graph, Graph.Vertex before, Graph.Vertex after, Collection<Lifecycle.Phase> phases) {
// We add ordering between internal phases.
// This would be wrong at execution time, but we are here computing a list and not a graph,
// so in order to obtain the expected order, we add these links between phases.
Lifecycle.Phase prev = null;
for (Lifecycle.Phase child : phases) {
// add phase
addPhase(graph, before, after, child);
if (prev != null) {
// add link between end of previous phase and beginning of this one
graph.addEdge(graph.addVertex(AFTER + prev.name()), graph.addVertex(BEFORE + child.name()));
}
prev = child;
}
}
@Named
@ -375,8 +380,8 @@ public class DefaultLifecycleRegistry implements LifecycleRegistry {
@Override
public Collection<Phase> phases() {
return List.of(phase(
"all",
phase(INITIALIZE, phase(VALIDATE)),
ALL,
phase(VALIDATE, phase(INITIALIZE)),
phase(
BUILD,
after(VALIDATE),
@ -407,6 +412,28 @@ public class DefaultLifecycleRegistry implements LifecycleRegistry {
phase(DEPLOY, after(PACKAGE))));
}
@Override
public Collection<Phase> v3phases() {
return List.of(phase(
ALL,
phase(INITIALIZE, phase(VALIDATE)),
phase(
BUILD,
phase(SOURCES),
phase(RESOURCES),
phase(COMPILE),
phase(READY),
phase(TEST_SOURCES),
phase(TEST_RESOURCES),
phase(TEST_COMPILE),
phase(TEST),
phase(UNIT_TEST),
phase(PACKAGE)),
phase(VERIFY, phase(INTEGRATION_TEST)),
phase(INSTALL),
phase(DEPLOY)));
}
@Override
public Collection<Alias> aliases() {
return List.of(
@ -424,42 +451,6 @@ public class DefaultLifecycleRegistry implements LifecycleRegistry {
alias("pre-integration-test", BEFORE + INTEGRATION_TEST),
alias("post-integration-test", AFTER + INTEGRATION_TEST));
}
@Override
public Optional<List<String>> orderedPhases() {
return Optional.of(Arrays.asList(
VALIDATE,
INITIALIZE,
// "generate-sources",
SOURCES,
// "process-sources",
// "generate-resources",
RESOURCES,
// "process-resources",
COMPILE,
// "process-classes",
READY,
// "generate-test-sources",
TEST_SOURCES,
// "process-test-sources",
// "generate-test-resources",
TEST_RESOURCES,
// "process-test-resources",
TEST_COMPILE,
// "process-test-classes",
TEST,
UNIT_TEST,
// "prepare-package",
PACKAGE,
BUILD,
// "pre-integration-test",
INTEGRATION_TEST,
// "post-integration-test",
VERIFY,
INSTALL,
DEPLOY,
"all"));
}
}
static class SiteLifecycle implements Lifecycle {

View File

@ -23,13 +23,9 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.maven.lifecycle.mapping.LifecyclePhase;
import static org.apache.maven.api.Lifecycle.AFTER;
import static org.apache.maven.api.Lifecycle.BEFORE;
/**
* Lifecycle definition, with eventual plugin bindings (when they are not packaging-specific).
*/
@ -46,9 +42,7 @@ public class Lifecycle {
org.apache.maven.api.services.LifecycleRegistry registry, org.apache.maven.api.Lifecycle lifecycle) {
this.lifecycle = lifecycle;
this.id = lifecycle.id();
this.phases = registry.computePhases(lifecycle).stream()
.flatMap(p -> Stream.of(BEFORE + p, p, AFTER + p))
.toList();
this.phases = registry.computePhases(lifecycle);
this.defaultPhases = getDefaultPhases(lifecycle);
}

View File

@ -25,7 +25,6 @@ import javax.xml.stream.XMLStreamException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@ -264,13 +263,10 @@ public class DefaultLifecycleExecutionPlanCalculator implements LifecycleExecuti
}
LifecycleMappingDelegate delegate;
if (Arrays.binarySearch(DefaultLifecycles.STANDARD_LIFECYCLES, lifecycle.getId()) >= 0) {
if (List.of(DefaultLifecycles.STANDARD_LIFECYCLES).contains(lifecycle.getId())) {
delegate = standardDelegate;
} else {
delegate = delegates.get(lifecycle.getId());
if (delegate == null) {
delegate = standardDelegate;
}
delegate = delegates.getOrDefault(lifecycle.getId(), standardDelegate);
}
return delegate.calculateLifecycleMappings(session, project, lifecycle, lifecyclePhase);

View File

@ -82,14 +82,23 @@ public class DefaultLifecycleMappingDelegate implements LifecycleMappingDelegate
lifecyclePhase = PhaseId.of(aliases.get(lifecyclePhase)).phase();
}
boolean passed = false;
for (String phase : lifecycle.getPhases()) {
Map<PhaseId, List<MojoExecution>> phaseBindings =
new TreeMap<>(Comparator.comparing(PhaseId::toString, new PhaseComparator(lifecycle.getPhases())));
mappings.put(phase, phaseBindings);
boolean include = true;
if (phase.equals(lifecyclePhase)) {
break;
passed = true;
} else if (passed) {
if (phase.startsWith(org.apache.maven.api.Lifecycle.AFTER)) {
String realPhase = phase.substring(org.apache.maven.api.Lifecycle.AFTER.length());
include = mappings.containsKey(org.apache.maven.api.Lifecycle.BEFORE + realPhase);
} else {
include = false;
}
}
if (include) {
Map<PhaseId, List<MojoExecution>> phaseBindings = new TreeMap<>(
Comparator.comparing(PhaseId::toString, new PhaseComparator(lifecycle.getPhases())));
mappings.put(phase, phaseBindings);
}
}
@ -163,7 +172,7 @@ public class DefaultLifecycleMappingDelegate implements LifecycleMappingDelegate
Map<String, Map<PhaseId, List<MojoExecution>>> mappings, String phase) {
if (phase != null) {
PhaseId id = PhaseId.of(phase);
return mappings.get(id.phase());
return mappings.get(id.executionPoint().prefix() + id.phase());
}
return null;
}

View File

@ -43,8 +43,8 @@ public class PhaseComparator implements Comparator<String> {
public int compare(String o1, String o2) {
PhaseId p1 = PhaseId.of(o1);
PhaseId p2 = PhaseId.of(o2);
int i1 = lifecyclePhases.indexOf(p1.phase());
int i2 = lifecyclePhases.indexOf(p2.phase());
int i1 = lifecyclePhases.indexOf(p1.executionPoint().prefix() + p1.phase());
int i2 = lifecyclePhases.indexOf(p2.executionPoint().prefix() + p2.phase());
if (i1 == -1 && i2 == -1) {
// unknown phases, leave in existing order
return 0;
@ -61,13 +61,6 @@ public class PhaseComparator implements Comparator<String> {
if (rv != 0) {
return rv;
}
// same phase, now compare execution points
i1 = p1.executionPoint().ordinal();
i2 = p2.executionPoint().ordinal();
rv = Integer.compare(i1, i2);
if (rv != 0) {
return rv;
}
// same execution point, now compare priorities
return Integer.compare(p1.priority(), p2.priority());
}

View File

@ -410,6 +410,9 @@ public class BuildPlanExecutor {
MojoDescriptor mojoDescriptor = getMojoDescriptor(project, plugin, goal);
String phase =
execution.getPhase() != null ? execution.getPhase() : mojoDescriptor.getPhase();
if (phase == null) {
continue;
}
String tmpResolvedPhase = plan.aliases().getOrDefault(phase, phase);
String resolvedPhase = tmpResolvedPhase.startsWith(AT)
? tmpResolvedPhase.substring(AT.length())
@ -680,9 +683,7 @@ public class BuildPlanExecutor {
+ " or a goal in the format <plugin-prefix>:<goal> or"
+ " <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: "
+ lifecycles.stream()
.flatMap(l -> l.orderedPhases()
.map(List::stream)
.orElseGet(() -> l.allPhases().map(Lifecycle.Phase::name)))
.flatMap(l -> l.allPhases().map(Lifecycle.Phase::name))
.collect(Collectors.joining(", "))
+ ".",
lifecyclePhase));
@ -735,6 +736,10 @@ public class BuildPlanExecutor {
? lifecyclePhase.substring(AT.length())
: AFTER + lifecyclePhase;
Set<BuildStep> toKeep = steps.get(endPhase).allPredecessors().collect(Collectors.toSet());
toKeep.addAll(toKeep.stream()
.filter(s -> s.name.startsWith(BEFORE))
.map(s -> steps.get(AFTER + s.name.substring(BEFORE.length())))
.toList());
steps.values().stream().filter(n -> !toKeep.contains(n)).forEach(BuildStep::skip);
plan.addProject(project, steps);

View File

@ -173,26 +173,21 @@ class LifecycleExecutorTest extends AbstractCoreMavenComponentTestCase {
// [09] jar:jar
// [10] install:install
//
assertEquals(10, executionPlan.size());
assertEquals("clean:clean", executionPlan.get(0).getMojoDescriptor().getFullGoalName());
assertEquals(
"resources:resources", executionPlan.get(1).getMojoDescriptor().getFullGoalName());
assertEquals(
"compiler:compile", executionPlan.get(2).getMojoDescriptor().getFullGoalName());
assertEquals(
"it:generate-metadata", executionPlan.get(3).getMojoDescriptor().getFullGoalName());
assertEquals(
"resources:testResources",
executionPlan.get(4).getMojoDescriptor().getFullGoalName());
assertEquals(
"compiler:testCompile", executionPlan.get(5).getMojoDescriptor().getFullGoalName());
assertEquals(
"it:generate-test-metadata",
executionPlan.get(6).getMojoDescriptor().getFullGoalName());
assertEquals("surefire:test", executionPlan.get(7).getMojoDescriptor().getFullGoalName());
assertEquals("jar:jar", executionPlan.get(8).getMojoDescriptor().getFullGoalName());
assertEquals("install:install", executionPlan.get(9).getMojoDescriptor().getFullGoalName());
assertListEquals(
List.of(
"clean:clean",
"resources:resources",
"compiler:compile",
"it:generate-metadata",
"resources:testResources",
"compiler:testCompile",
"it:generate-test-metadata",
"surefire:test",
"jar:jar",
"install:install"),
executionPlan.stream()
.map(plan -> plan.getMojoDescriptor().getFullGoalName())
.toList());
}
// We need to take in multiple lifecycles
@ -226,31 +221,29 @@ class LifecycleExecutorTest extends AbstractCoreMavenComponentTestCase {
// [16] install:install
//
assertEquals(16, executions.size());
assertEquals("clean:clean", executions.get(0).getMojoDescriptor().getFullGoalName());
assertEquals("it:xpp3-writer", executions.get(1).getMojoDescriptor().getFullGoalName());
assertEquals("it:java", executions.get(2).getMojoDescriptor().getFullGoalName());
assertEquals("it:xpp3-reader", executions.get(3).getMojoDescriptor().getFullGoalName());
assertEquals("it:xpp3-writer", executions.get(4).getMojoDescriptor().getFullGoalName());
assertEquals("it:java", executions.get(5).getMojoDescriptor().getFullGoalName());
assertEquals("it:xpp3-reader", executions.get(6).getMojoDescriptor().getFullGoalName());
assertEquals(
"resources:resources", executions.get(7).getMojoDescriptor().getFullGoalName());
assertEquals("compiler:compile", executions.get(8).getMojoDescriptor().getFullGoalName());
assertEquals("plugin:descriptor", executions.get(9).getMojoDescriptor().getFullGoalName());
assertEquals(
"resources:testResources",
executions.get(10).getMojoDescriptor().getFullGoalName());
assertEquals(
"compiler:testCompile", executions.get(11).getMojoDescriptor().getFullGoalName());
assertEquals("surefire:test", executions.get(12).getMojoDescriptor().getFullGoalName());
assertEquals("jar:jar", executions.get(13).getMojoDescriptor().getFullGoalName());
assertEquals(
"plugin:addPluginArtifactMetadata",
executions.get(14).getMojoDescriptor().getFullGoalName());
assertEquals("install:install", executions.get(15).getMojoDescriptor().getFullGoalName());
assertListEquals(
List.of(
"clean:clean",
"it:xpp3-writer",
"it:java",
"it:xpp3-reader",
"it:xpp3-writer",
"it:java",
"it:xpp3-reader",
"resources:resources",
"compiler:compile",
"plugin:descriptor",
"resources:testResources",
"compiler:testCompile",
"surefire:test",
"jar:jar",
"plugin:addPluginArtifactMetadata",
"install:install"),
executions.stream()
.map(execution -> execution.getMojoDescriptor().getFullGoalName())
.toList());
// Keep the separate configuration checks
assertEquals(
"src/main/mdo/remote-resources.mdo",
new MojoExecutionXPathContainer(executions.get(1)).getValue("configuration/models[1]/model"));
@ -278,24 +271,19 @@ class LifecycleExecutorTest extends AbstractCoreMavenComponentTestCase {
// [07] surefire:test
// [08] jar:jar
//
assertEquals(8, executionPlan.size());
assertEquals(
"resources:resources", executionPlan.get(0).getMojoDescriptor().getFullGoalName());
assertEquals(
"compiler:compile", executionPlan.get(1).getMojoDescriptor().getFullGoalName());
assertEquals(
"it:generate-metadata", executionPlan.get(2).getMojoDescriptor().getFullGoalName());
assertEquals(
"resources:testResources",
executionPlan.get(3).getMojoDescriptor().getFullGoalName());
assertEquals(
"compiler:testCompile", executionPlan.get(4).getMojoDescriptor().getFullGoalName());
assertEquals(
"it:generate-test-metadata",
executionPlan.get(5).getMojoDescriptor().getFullGoalName());
assertEquals("surefire:test", executionPlan.get(6).getMojoDescriptor().getFullGoalName());
assertEquals("jar:jar", executionPlan.get(7).getMojoDescriptor().getFullGoalName());
assertListEquals(
List.of(
"resources:resources",
"compiler:compile",
"it:generate-metadata",
"resources:testResources",
"compiler:testCompile",
"it:generate-test-metadata",
"surefire:test",
"jar:jar"),
executionPlan.stream()
.map(plan -> plan.getMojoDescriptor().getFullGoalName())
.toList());
}
@Test
@ -516,18 +504,14 @@ class LifecycleExecutorTest extends AbstractCoreMavenComponentTestCase {
"afterProjectExecutionSuccess project-basic" //
);
assertEventLog(expectedLog, log);
assertListEquals(expectedLog, log);
}
private static void assertEventLog(List<String> expectedList, List<String> actualList) {
private static void assertListEquals(List<String> expectedList, List<String> actualList) {
assertEquals(toString(expectedList), toString(actualList));
}
private static String toString(List<String> lines) {
StringBuilder sb = new StringBuilder();
for (String line : lines) {
sb.append(line).append('\n');
}
return sb.toString();
return String.join("\n", lines);
}
}

View File

@ -0,0 +1,65 @@
/*
* 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.io.File;
import org.junit.jupiter.api.Test;
/**
* This is a test set for <a href="https://issues.apache.org/jira/browse/MNG-8244">MNG-8244</a>.
*/
class MavenITmng8244PhaseAllTest extends AbstractMavenIntegrationTestCase {
MavenITmng8244PhaseAllTest() {
super("[4.0.0-rc-2,)");
}
/**
* Verify phase after:all phase is called
*/
@Test
void testPhaseAllWihConcurrentBuilder() throws Exception {
File testDir = extractResources("/mng-8244-phase-all");
Verifier verifier = newVerifier(testDir.getAbsolutePath());
verifier.setLogFileName("build-concurrent.txt");
verifier.addCliArguments("-b", "concurrent", "build");
verifier.execute();
verifier.verifyTextInLog("Hallo 'before:all' phase.");
verifier.verifyTextInLog("Hallo 'after:all' phase.");
}
/**
* Verify phase after:all phase is called
*/
@Test
void testPhaseAllWithLegacyBuilder() throws Exception {
File testDir = extractResources("/mng-8244-phase-all");
Verifier verifier = newVerifier(testDir.getAbsolutePath());
verifier.setLogFileName("build-legacy.txt");
verifier.addCliArguments("build");
verifier.execute();
verifier.verifyTextInLog("Hallo 'before:all' phase.");
verifier.verifyTextInLog("Hallo 'after:all' phase.");
}
}

View File

@ -100,6 +100,7 @@ public class TestSuiteOrdering implements ClassOrderer {
* the tests are to finishing. Newer tests are also more likely to fail, so this is
* a fail fast technique as well.
*/
suite.addTestSuite(MavenITmng8244PhaseAllTest.class);
suite.addTestSuite(MavenITmng8421MavenEncryptionTest.class);
suite.addTestSuite(MavenITmng8400CanonicalMavenHomeTest.class);
suite.addTestSuite(MavenITmng8385PropertyContributoSPITest.class);

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" root="true" xsi:schemaLocation="http://maven.apache.org/POM/4.1.0 https://maven.apache.org/xsd/maven-4.1.0.xsd">
<groupId>org.apache.maven.its</groupId>
<artifactId>mng-8244</artifactId>
<version>1.0.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>com.soebes.maven.plugins</groupId>
<artifactId>echo-maven-plugin</artifactId>
<version>0.5.0</version>
<executions>
<execution>
<id>before-clean</id>
<goals>
<goal>echo</goal>
</goals>
<phase>before:clean</phase>
<configuration>
<echos>
<echo>Hallo 'before:clean' phase.</echo>
</echos>
</configuration>
</execution>
<execution>
<id>after-clean</id>
<goals>
<goal>echo</goal>
</goals>
<phase>after:clean</phase>
<configuration>
<echos>
<echo>Hallo 'after:clean' phase.</echo>
</echos>
</configuration>
</execution>
<execution>
<id>before-build</id>
<goals>
<goal>echo</goal>
</goals>
<phase>before:build</phase>
<configuration>
<echos>
<echo>Hallo 'before:build' phase.</echo>
</echos>
</configuration>
</execution>
<execution>
<id>build</id>
<goals>
<goal>echo</goal>
</goals>
<phase>build</phase>
<configuration>
<echos>
<echo>Hallo 'build' phase.</echo>
</echos>
</configuration>
</execution>
<execution>
<id>after-build</id>
<goals>
<goal>echo</goal>
</goals>
<phase>after:build</phase>
<configuration>
<echos>
<echo>Hallo 'after:build' phase.</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.</echo>
</echos>
</configuration>
</execution>
<execution>
<id>all</id>
<goals>
<goal>echo</goal>
</goals>
<phase>all</phase>
<configuration>
<echos>
<echo>Hallo 'all' phase.</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.</echo>
</echos>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>