From acec540547bca289b0579ab0c8f371004b8903f1 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 29 Aug 2024 06:48:54 +0100 Subject: [PATCH] [MNG-8052] New lifecycle for Maven 4 (#1448) --- .../java/org/apache/maven/api/Lifecycle.java | 124 +++++++- .../maven/api/plugin/annotations/After.java | 66 +++++ .../maven/api/services/LifecycleRegistry.java | 3 + api/maven-api-plugin/pom.xml | 2 +- .../src/main/mdo/lifecycle.mdo | 67 ++++- .../internal/impl/standalone/ApiRunner.java | 5 + .../EmptyLifecycleBindingsInjector.java | 10 + .../impl/DefaultLifecycleRegistry.java | 279 ++++++++++++++---- .../maven/internal/impl/Lifecycles.java | 124 +++++++- .../maven/lifecycle/DefaultLifecycles.java | 32 +- .../org/apache/maven/lifecycle/Lifecycle.java | 23 +- ...faultLifecycleExecutionPlanCalculator.java | 9 +- .../DefaultLifecycleMappingDelegate.java | 57 +++- .../lifecycle/internal/PhaseComparator.java | 74 +++++ .../internal/PhaseExecutionPoint.java | 50 ++++ .../maven/lifecycle/internal/PhaseId.java | 142 +++++++++ .../lifecycle/internal/PhaseRecorder.java | 9 +- .../lifecycle/DefaultLifecyclesTest.java | 6 +- .../lifecycle/LifecycleExecutorTest.java | 2 + .../EmptyLifecycleBindingsInjector.java | 10 + maven-plugin-api/pom.xml | 2 +- src/mdo/reader-stax.vm | 5 + src/mdo/writer-stax.vm | 5 + src/mdo/writer.vm | 5 + 24 files changed, 1001 insertions(+), 110 deletions(-) create mode 100644 api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/After.java create mode 100644 maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseComparator.java create mode 100644 maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseExecutionPoint.java create mode 100644 maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseId.java 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 fcee471eac..40d2715e33 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 @@ -21,9 +21,11 @@ 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; import org.apache.maven.api.annotations.Immutable; +import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.model.Plugin; /** @@ -41,14 +43,20 @@ import org.apache.maven.api.model.Plugin; @Immutable public interface Lifecycle extends ExtensibleEnum { + // ========================= + // Maven defined lifecycles + // ========================= String CLEAN = "clean"; - String DEFAULT = "default"; - String SITE = "site"; - String WRAPPER = "wrapper"; + // ====================== + // Phase qualifiers + // ====================== + String BEFORE = "before:"; + String AFTER = "after:"; + /** * Name or identifier of this lifecycle. * @@ -62,6 +70,18 @@ public interface Lifecycle extends ExtensibleEnum { */ Collection phases(); + /** + * Stream of phases containing all child phases recursively. + */ + default Stream allPhases() { + return phases().stream().flatMap(Phase::allPhases); + } + + /** + * Collection of aliases. + */ + Collection aliases(); + /** * Pre-ordered list of phases. * If not provided, a default order will be computed. @@ -72,10 +92,108 @@ public interface Lifecycle extends ExtensibleEnum { /** * A phase in the lifecycle. + * + * A phase is identified by its name. It also contains a list of plugins bound to that phase, + * a list of {@link Link links}, and a list of sub-phases. This forms a tree of phases. */ interface Phase { + + // ====================== + // Maven defined phases + // ====================== + String BUILD = "build"; + String INITIALIZE = "initialize"; + String VALIDATE = "validate"; + String SOURCES = "sources"; + String RESOURCES = "resources"; + String COMPILE = "compile"; + String READY = "ready"; + String PACKAGE = "package"; + String VERIFY = "verify"; + String UNIT_TEST = "unit-test"; + String TEST_SOURCES = "test-sources"; + String TEST_RESOURCES = "test-resources"; + String TEST_COMPILE = "test-compile"; + String TEST = "test"; + String INTEGRATION_TEST = "integration-test"; + String INSTALL = "install"; + String DEPLOY = "deploy"; + String CLEAN = "clean"; + + @Nonnull String name(); + @Nonnull List plugins(); + + @Nonnull + Collection links(); + + /** + * {@return the list of sub-phases} + */ + @Nonnull + List phases(); + + @Nonnull + Stream allPhases(); + } + + /** + * A phase alias, mostly used to support the Maven 3 phases which are mapped + * to dynamic phases in Maven 4. + */ + interface Alias { + String v3Phase(); + + String v4Phase(); + } + + /** + * A link from a phase to another phase, consisting of a type which can be + * {@link Kind#BEFORE} or {@link Kind#AFTER}, and a {@link Pointer} to + * another phase. + */ + interface Link { + enum Kind { + BEFORE, + AFTER + } + + Kind kind(); + + Pointer pointer(); + } + + interface Pointer { + enum Type { + PROJECT, + DEPENDENCIES, + CHILDREN + } + + String phase(); + + Type type(); + } + + interface PhasePointer extends Pointer { + default Type type() { + return Type.PROJECT; + } + } + + interface DependenciesPointer extends Pointer { + String scope(); // default: all + + default Type type() { + return Type.DEPENDENCIES; + } + } + + interface ChildrenPointer extends Pointer { + default Type type() { + return Type.CHILDREN; + } } } diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/After.java b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/After.java new file mode 100644 index 0000000000..697b9d4802 --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/After.java @@ -0,0 +1,66 @@ +/* + * 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.api.plugin.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apache.maven.api.annotations.Experimental; + +/** + * Specifies that the mojo should be run after the specific phase. + * + * @since 4.0.0 + */ +@Experimental +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +public @interface After { + + /** + * Type of pointer. + * @see org.apache.maven.api.Lifecycle.Pointer.Type + */ + enum Type { + PROJECT, + DEPENDENCIES, + CHILDREN + } + + /** + * The phase name. + */ + String phase(); + + /** + * The type of this pointer. + */ + Type type(); + + /** + * The scope for dependencies, only if {@code type() == Type.Dependencies}. + */ + String scope() default ""; +} diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/LifecycleRegistry.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/LifecycleRegistry.java index 05970e76fc..55efc16ebc 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/LifecycleRegistry.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/LifecycleRegistry.java @@ -18,6 +18,7 @@ */ package org.apache.maven.api.services; +import java.util.List; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -28,4 +29,6 @@ public interface LifecycleRegistry extends ExtensibleEnumRegistry, It default Stream stream() { return StreamSupport.stream(spliterator(), false); } + + List computePhases(Lifecycle lifecycle); } diff --git a/api/maven-api-plugin/pom.xml b/api/maven-api-plugin/pom.xml index 9a9a876c99..b5a8bdab7c 100644 --- a/api/maven-api-plugin/pom.xml +++ b/api/maven-api-plugin/pom.xml @@ -94,7 +94,7 @@ under the License. generate-sources ${project.basedir}/../../src/mdo - 1.0.0 + 2.0.0 src/main/mdo/lifecycle.mdo diff --git a/api/maven-api-plugin/src/main/mdo/lifecycle.mdo b/api/maven-api-plugin/src/main/mdo/lifecycle.mdo index f469eea440..8196e3aa50 100644 --- a/api/maven-api-plugin/src/main/mdo/lifecycle.mdo +++ b/api/maven-api-plugin/src/main/mdo/lifecycle.mdo @@ -30,12 +30,12 @@ under the License. LifecycleConfiguration - 1.0.0 + 1.0.0+ Root element of the {@code lifecycle.xml} file. lifecycles - 1.0.0 + 1.0.0+ Lifecycle * @@ -45,19 +45,19 @@ under the License. Lifecycle - 1.0.0 + 1.0.0+ A custom lifecycle mapping definition. id true - 1.0.0 + 1.0.0+ String The ID of this lifecycle, for identification in the mojo descriptor. phases - 1.0.0 + 1.0.0+ The phase mappings for this lifecycle. Phase @@ -68,19 +68,35 @@ under the License. Phase - 1.0.0 + 1.0.0+ A phase mapping definition. id true - 1.0.0 + 1.0.0+ String - The ID of this phase, e.g., <code>generate-sources</code>. + The ID of this phase, e.g., {@code generate-sources}. + + + executionPoint + false + 2.0.0+ + String + + + + + priority + false + 2.0.0+ + int + 0 + If specified, identifies a within phase prioritization of executions. executions - 1.0.0 + 1.0.0+ The goals to execute within the phase. Execution @@ -89,26 +105,51 @@ under the License. configuration - 1.0.0 + 1.0.0+ DOM Configuration to pass to all goals run in this phase. + + + 2.0.0+ + + + Execution - 1.0.0 + 1.0.0+ A set of goals to execute. configuration - 1.0.0 + 1.0.0+ DOM Configuration to pass to the goals. goals - 1.0.0 + 1.0.0+ The goals to execute. String diff --git a/maven-api-impl/src/test/java/org/apache/maven/internal/impl/standalone/ApiRunner.java b/maven-api-impl/src/test/java/org/apache/maven/internal/impl/standalone/ApiRunner.java index 3c6c9433de..3806ea1e32 100644 --- a/maven-api-impl/src/test/java/org/apache/maven/internal/impl/standalone/ApiRunner.java +++ b/maven-api-impl/src/test/java/org/apache/maven/internal/impl/standalone/ApiRunner.java @@ -364,6 +364,11 @@ public class ApiRunner { public Optional lookup(String id) { return Optional.empty(); } + + @Override + public List computePhases(Lifecycle lifecycle) { + return List.of(); + } }; } diff --git a/maven-compat/src/test/java/org/apache/maven/project/EmptyLifecycleBindingsInjector.java b/maven-compat/src/test/java/org/apache/maven/project/EmptyLifecycleBindingsInjector.java index 513bb0236b..970b74a750 100644 --- a/maven-compat/src/test/java/org/apache/maven/project/EmptyLifecycleBindingsInjector.java +++ b/maven-compat/src/test/java/org/apache/maven/project/EmptyLifecycleBindingsInjector.java @@ -62,6 +62,11 @@ public class EmptyLifecycleBindingsInjector extends DefaultLifecycleBindingsInje public Optional lookup(String id) { return Optional.empty(); } + + @Override + public List computePhases(Lifecycle lifecycle) { + return List.of(); + } }; private static final PackagingRegistry emptyPackagingRegistry = new PackagingRegistry() { @@ -140,6 +145,11 @@ public class EmptyLifecycleBindingsInjector extends DefaultLifecycleBindingsInje protected LifecycleRegistry getDelegate() { return lifecycleRegistry; } + + @Override + public List computePhases(Lifecycle lifecycle) { + return List.of(); + } } static class WrapperPackagingRegistry implements PackagingRegistry { diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultLifecycleRegistry.java b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultLifecycleRegistry.java index acd8fd74c2..6a401504c5 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultLifecycleRegistry.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultLifecycleRegistry.java @@ -24,6 +24,7 @@ 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; @@ -47,8 +48,26 @@ import org.apache.maven.lifecycle.mapping.LifecyclePhase; import org.codehaus.plexus.PlexusContainer; import org.codehaus.plexus.component.repository.exception.ComponentLookupException; -import static java.util.Arrays.asList; -import static java.util.Collections.singleton; +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.INITIALIZE; +import static org.apache.maven.api.Lifecycle.Phase.INSTALL; +import static org.apache.maven.api.Lifecycle.Phase.INTEGRATION_TEST; +import static org.apache.maven.api.Lifecycle.Phase.PACKAGE; +import static org.apache.maven.api.Lifecycle.Phase.READY; +import static org.apache.maven.api.Lifecycle.Phase.RESOURCES; +import static org.apache.maven.api.Lifecycle.Phase.SOURCES; +import static org.apache.maven.api.Lifecycle.Phase.TEST; +import static org.apache.maven.api.Lifecycle.Phase.TEST_COMPILE; +import static org.apache.maven.api.Lifecycle.Phase.TEST_RESOURCES; +import static org.apache.maven.api.Lifecycle.Phase.TEST_SOURCES; +import static org.apache.maven.api.Lifecycle.Phase.UNIT_TEST; +import static org.apache.maven.api.Lifecycle.Phase.VALIDATE; +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.dependencies; import static org.apache.maven.internal.impl.Lifecycles.phase; import static org.apache.maven.internal.impl.Lifecycles.plugin; @@ -59,6 +78,8 @@ import static org.apache.maven.internal.impl.Lifecycles.plugin; @Singleton public class DefaultLifecycleRegistry implements LifecycleRegistry { + private static final String MAVEN_PLUGINS = "org.apache.maven.plugins:"; + private final List providers; public DefaultLifecycleRegistry() { @@ -73,7 +94,7 @@ public class DefaultLifecycleRegistry implements LifecycleRegistry { // validate lifecycle for (Lifecycle lifecycle : this) { Set set = new HashSet<>(); - lifecycle.phases().forEach(phase -> { + lifecycle.allPhases().forEach(phase -> { if (!set.add(phase.name())) { throw new IllegalArgumentException( "Found duplicated phase '" + phase.name() + "' in '" + lifecycle.id() + "' lifecycle"); @@ -97,6 +118,57 @@ public class DefaultLifecycleRegistry implements LifecycleRegistry { return stream().filter(lf -> Objects.equals(id, lf.id())).findAny(); } + public List computePhases(Lifecycle lifecycle) { + Graph graph = new Graph(); + lifecycle.phases().forEach(phase -> addPhase(graph, null, null, phase)); + List allPhases = graph.visitAll(); + Collections.reverse(allPhases); + List computed = + allPhases.stream().filter(s -> !s.startsWith("$")).collect(Collectors.toList()); + List given = lifecycle.orderedPhases().orElse(null); + if (given != null) { + if (given.size() != computed.size()) { + Set s1 = + given.stream().filter(s -> !computed.contains(s)).collect(Collectors.toSet()); + Set 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 ep1 = graph.addVertex("$$" + phase.name()); + Graph.Vertex ep2 = graph.addVertex(phase.name()); + Graph.Vertex ep3 = graph.addVertex("$$$" + phase.name()); + graph.addEdge(ep0, ep1); + graph.addEdge(ep1, ep2); + graph.addEdge(ep2, ep3); + if (before != null) { + graph.addEdge(before, ep0); + } + if (after != null) { + graph.addEdge(ep3, after); + } + phase.links().forEach(link -> { + if (link.pointer().type() == Lifecycle.Pointer.Type.PROJECT) { + if (link.kind() == Lifecycle.Link.Kind.AFTER) { + graph.addEdge(graph.addVertex(link.pointer().phase()), ep0); + } else { + graph.addEdge(ep3, graph.addVertex("$" + link.pointer().phase())); + } + } + }); + phase.phases().forEach(child -> addPhase(graph, ep1, ep2, child)); + } + @Named @Singleton public static class LifecycleWrapperProvider implements LifecycleProvider { @@ -140,6 +212,17 @@ public class DefaultLifecycleRegistry implements LifecycleRegistry { return name; } + @Override + public List phases() { + return List.of(); + } + + @Override + public Stream allPhases() { + return Stream.concat( + Stream.of(this), phases().stream().flatMap(Lifecycle.Phase::allPhases)); + } + @Override public List plugins() { Map lfPhases = lifecycle.getDefaultLifecyclePhases(); @@ -151,16 +234,26 @@ public class DefaultLifecycleRegistry implements LifecycleRegistry { } return List.of(); } + + @Override + public Collection links() { + return List.of(); + } }) .toList(); } + + @Override + public Collection aliases() { + return Collections.emptyList(); + } }; } } static class WrappedLifecycle extends org.apache.maven.lifecycle.Lifecycle { - WrappedLifecycle(Lifecycle lifecycle) { - super(lifecycle); + WrappedLifecycle(LifecycleRegistry registry, Lifecycle lifecycle) { + super(registry, lifecycle); } } @@ -178,7 +271,7 @@ public class DefaultLifecycleRegistry implements LifecycleRegistry { public org.apache.maven.lifecycle.Lifecycle get() { try { LifecycleRegistry registry = lookup.lookup(LifecycleRegistry.class); - return new WrappedLifecycle(registry.require(name)); + return new WrappedLifecycle(registry, registry.require(name)); } catch (ComponentLookupException e) { throw new LookupException(e); } @@ -187,6 +280,7 @@ public class DefaultLifecycleRegistry implements LifecycleRegistry { @Singleton @Named(Lifecycle.CLEAN) + @SuppressWarnings("unused") static class CleanLifecycleProvider extends BaseLifecycleProvider { CleanLifecycleProvider() { super(Lifecycle.CLEAN); @@ -195,6 +289,7 @@ public class DefaultLifecycleRegistry implements LifecycleRegistry { @Singleton @Named(Lifecycle.DEFAULT) + @SuppressWarnings("unused") static class DefaultLifecycleProvider extends BaseLifecycleProvider { DefaultLifecycleProvider() { super(Lifecycle.DEFAULT); @@ -203,6 +298,7 @@ public class DefaultLifecycleRegistry implements LifecycleRegistry { @Singleton @Named(Lifecycle.SITE) + @SuppressWarnings("unused") static class SiteLifecycleProvider extends BaseLifecycleProvider { SiteLifecycleProvider() { super(Lifecycle.SITE); @@ -211,6 +307,7 @@ public class DefaultLifecycleRegistry implements LifecycleRegistry { @Singleton @Named(Lifecycle.WRAPPER) + @SuppressWarnings("unused") static class WrapperLifecycleProvider extends BaseLifecycleProvider { WrapperLifecycleProvider() { super(Lifecycle.WRAPPER); @@ -228,15 +325,16 @@ public class DefaultLifecycleRegistry implements LifecycleRegistry { @Override public Collection phases() { - return asList( - phase("pre-clean"), - phase( - "clean", - plugin( - "org.apache.maven.plugins:maven-clean-plugin:" + MAVEN_CLEAN_PLUGIN_VERSION - + ":clean", - "clean")), - phase("post-clean")); + return List.of(phase( + Phase.CLEAN, + plugin( + MAVEN_PLUGINS + "maven-clean-plugin:" + MAVEN_CLEAN_PLUGIN_VERSION + ":clean", + Phase.CLEAN))); + } + + @Override + public Collection aliases() { + return List.of(alias("pre-clean", BEFORE + Phase.CLEAN), alias("post-clean", AFTER + Phase.CLEAN)); } } @@ -248,36 +346,101 @@ public class DefaultLifecycleRegistry implements LifecycleRegistry { @Override public Collection phases() { - return asList( - phase("validate"), - phase("initialize"), - phase("generate-sources"), - phase("process-sources"), - phase("generate-resources"), - phase("process-resources"), - phase("compile"), - phase("process-classes"), - phase("generate-test-sources"), - phase("process-test-sources"), - phase("generate-test-resources"), - phase("process-test-resources"), - phase("test-compile"), - phase("process-test-classes"), - phase("test"), - phase("prepare-package"), - phase("package"), - phase("pre-integration-test"), - phase("integration-test"), - phase("post-integration-test"), - phase("verify"), - phase("install"), - phase("deploy")); + return List.of(phase( + "all", + phase(INITIALIZE, phase(VALIDATE)), + 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), + phase( + UNIT_TEST, + phase(TEST_SOURCES), + phase(TEST_RESOURCES), + phase( + TEST_COMPILE, + after(TEST_SOURCES), + after(READY), + dependencies("test-only", READY)), + phase( + TEST, + after(TEST_COMPILE), + after(TEST_RESOURCES), + dependencies("test", READY))), + phase(INTEGRATION_TEST)), + phase(INSTALL, after(PACKAGE)), + phase(DEPLOY, after(PACKAGE)))); + } + + @Override + public Collection aliases() { + return List.of( + alias("generate-sources", SOURCES), + alias("process-sources", AFTER + SOURCES), + alias("generate-resources", RESOURCES), + alias("process-resources", AFTER + RESOURCES), + alias("process-classes", AFTER + COMPILE), + alias("generate-test-sources", TEST_SOURCES), + alias("process-test-sources", AFTER + TEST_SOURCES), + alias("generate-test-resources", TEST_RESOURCES), + alias("process-test-resources", AFTER + TEST_RESOURCES), + alias("process-test-classes", AFTER + TEST_COMPILE), + alias("prepare-package", BEFORE + PACKAGE), + alias("pre-integration-test", BEFORE + INTEGRATION_TEST), + alias("post-integration-test", AFTER + INTEGRATION_TEST)); + } + + @Override + public Optional> 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 { private static final String MAVEN_SITE_PLUGIN_VERSION = "3.12.1"; + private static final String MAVEN_SITE_PLUGIN = + MAVEN_PLUGINS + "maven-site-plugin:" + MAVEN_SITE_PLUGIN_VERSION + ":"; + private static final String PHASE_SITE = "site"; + private static final String PHASE_SITE_DEPLOY = "site-deploy"; @Override public String id() { @@ -286,26 +449,24 @@ public class DefaultLifecycleRegistry implements LifecycleRegistry { @Override public Collection phases() { - return asList( - phase("pre-site"), + return List.of( + phase(PHASE_SITE, plugin(MAVEN_SITE_PLUGIN + "site", PHASE_SITE)), phase( - "site", - plugin( - "org.apache.maven.plugins:maven-site-plugin:" + MAVEN_SITE_PLUGIN_VERSION + ":site", - "site")), - phase("post-site"), - phase( - "site-deploy", - plugin( - "org.apache.maven.plugins:maven-site-plugin:" + MAVEN_SITE_PLUGIN_VERSION - + ":deploy", - "site-deploy"))); + PHASE_SITE_DEPLOY, + after(PHASE_SITE), + plugin(MAVEN_SITE_PLUGIN + "deploy", PHASE_SITE_DEPLOY))); + } + + @Override + public Collection aliases() { + return List.of(alias("pre-site", BEFORE + PHASE_SITE), alias("post-site", AFTER + PHASE_SITE)); } } static class WrapperLifecycle implements Lifecycle { private static final String MAVEN_WRAPPER_PLUGIN_VERSION = "3.2.0"; + private static final String PHASE_WRAPPER = "wrapper"; @Override public String id() { @@ -314,12 +475,16 @@ public class DefaultLifecycleRegistry implements LifecycleRegistry { @Override public Collection phases() { - return singleton(phase( - "wrapper", + return List.of(phase( + PHASE_WRAPPER, plugin( - "org.apache.maven.plugins:maven-wrapper-plugin:" + MAVEN_WRAPPER_PLUGIN_VERSION - + ":wrapper", - "wrapper"))); + MAVEN_PLUGINS + "maven-wrapper-plugin:" + MAVEN_WRAPPER_PLUGIN_VERSION + ":wrapper", + PHASE_WRAPPER))); + } + + @Override + public Collection aliases() { + return List.of(); } } } diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/Lifecycles.java b/maven-core/src/main/java/org/apache/maven/internal/impl/Lifecycles.java index b2e356f6ad..213f87d7b2 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/Lifecycles.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/Lifecycles.java @@ -18,21 +18,52 @@ */ package org.apache.maven.internal.impl; +import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.stream.Stream; import org.apache.maven.api.Lifecycle; import org.apache.maven.api.model.Plugin; import org.apache.maven.api.model.PluginExecution; +import static java.util.Arrays.asList; + public class Lifecycles { static Lifecycle.Phase phase(String name) { - return new DefaultPhase(name, Collections.emptyList(), Collections.emptyList()); + return new DefaultPhase(name, Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + } + + static Lifecycle.Phase phase(String name, Lifecycle.Phase... phases) { + return new DefaultPhase(name, Collections.emptyList(), Collections.emptyList(), asList(phases)); + } + + static Lifecycle.Phase phase(String name, Lifecycle.Link link, Lifecycle.Phase... phases) { + return new DefaultPhase(name, Collections.emptyList(), Collections.singletonList(link), asList(phases)); } static Lifecycle.Phase phase(String name, Plugin plugin) { - return new DefaultPhase(name, Collections.singletonList(plugin), Collections.emptyList()); + return new DefaultPhase( + name, Collections.singletonList(plugin), Collections.emptyList(), Collections.emptyList()); + } + + static Lifecycle.Phase phase(String name, Lifecycle.Link link, Plugin plugin) { + return new DefaultPhase( + name, Collections.singletonList(plugin), Collections.singletonList(link), Collections.emptyList()); + } + + static Lifecycle.Phase phase(String name, Lifecycle.Link link1, Lifecycle.Link link2, Lifecycle.Phase... phases) { + return new DefaultPhase(name, Collections.emptyList(), asList(link1, link2), asList(phases)); + } + + static Lifecycle.Phase phase( + String name, Lifecycle.Link link1, Lifecycle.Link link2, Lifecycle.Link link3, Lifecycle.Phase... phases) { + return new DefaultPhase(name, Collections.emptyList(), asList(link1, link2, link3), asList(phases)); + } + + static Lifecycle.Phase phase(String name, Collection links, Lifecycle.Phase... phases) { + return new DefaultPhase(name, Collections.emptyList(), links, asList(phases)); } static Plugin plugin(String coords, String phase) { @@ -49,14 +80,66 @@ public class Lifecycles { .build(); } + /** Indicates the phase is after the phases given in arguments */ + static Lifecycle.Link after(String b) { + return new Lifecycle.Link() { + @Override + public Kind kind() { + return Kind.AFTER; + } + + @Override + public Lifecycle.Pointer pointer() { + return new Lifecycle.PhasePointer() { + @Override + public String phase() { + return b; + } + }; + } + }; + } + + /** Indicates the phase is after the phases for the dependencies in the given scope */ + static Lifecycle.Link dependencies(String scope, String phase) { + return new Lifecycle.Link() { + @Override + public Kind kind() { + return Kind.AFTER; + } + + @Override + public Lifecycle.Pointer pointer() { + return new Lifecycle.DependenciesPointer() { + @Override + public String phase() { + return phase; + } + + @Override + public String scope() { + return scope; + } + }; + } + }; + } + + static Lifecycle.Alias alias(String v3Phase, String v4Phase) { + return new DefaultAlias(v3Phase, v4Phase); + } + static class DefaultPhase implements Lifecycle.Phase { private final String name; private final List plugins; + private final Collection links; private final List phases; - DefaultPhase(String name, List plugins, List phases) { + DefaultPhase( + String name, List plugins, Collection links, List phases) { this.name = name; this.plugins = plugins; + this.links = links; this.phases = phases; } @@ -69,5 +152,40 @@ public class Lifecycles { public List plugins() { return plugins; } + + @Override + public Collection links() { + return links; + } + + @Override + public List phases() { + return phases; + } + + @Override + public Stream allPhases() { + return Stream.concat(Stream.of(this), phases().stream().flatMap(Lifecycle.Phase::allPhases)); + } + } + + static class DefaultAlias implements Lifecycle.Alias { + private final String v3Phase; + private final String v4Phase; + + DefaultAlias(String v3Phase, String v4Phase) { + this.v3Phase = v3Phase; + this.v4Phase = v4Phase; + } + + @Override + public String v3Phase() { + return v3Phase; + } + + @Override + public String v4Phase() { + return v4Phase; + } } } diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultLifecycles.java b/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultLifecycles.java index 91e1adbc64..460cf5b9e4 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultLifecycles.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultLifecycles.java @@ -56,6 +56,8 @@ public class DefaultLifecycles { private Map customLifecycles; + private boolean lifecyclesPrinted; + public DefaultLifecycles() { this.lookup = null; this.registry = null; @@ -94,20 +96,23 @@ public class DefaultLifecycles { * @return A map of lifecycles, indexed on id */ public Map getPhaseToLifecycleMap() { + if (logger.isDebugEnabled() && !lifecyclesPrinted) { + for (Lifecycle lifecycle : getLifeCycles()) { + logger.debug("Lifecycle {}", lifecycle); + } + lifecyclesPrinted = true; + } + // If people are going to make their own lifecycles then we need to tell people how to namespace them correctly // so that they don't interfere with internally defined lifecycles. Map phaseToLifecycleMap = new HashMap<>(); for (Lifecycle lifecycle : getLifeCycles()) { - logger.debug("Lifecycle {}", lifecycle); - for (String phase : lifecycle.getPhases()) { // The first definition wins. - if (!phaseToLifecycleMap.containsKey(phase)) { - phaseToLifecycleMap.put(phase, lifecycle); - } else if (logger.isWarnEnabled()) { - Lifecycle original = phaseToLifecycleMap.get(phase); + Lifecycle original = phaseToLifecycleMap.put(phase, lifecycle); + if (original != null && logger.isWarnEnabled()) { logger.warn( "Duplicated lifecycle phase {}. Defined in {} but also in {}", phase, @@ -115,6 +120,19 @@ public class DefaultLifecycles { lifecycle.getId()); } } + if (lifecycle.getDelegate() != null) { + for (org.apache.maven.api.Lifecycle.Alias alias : + lifecycle.getDelegate().aliases()) { + Lifecycle original = phaseToLifecycleMap.put(alias.v3Phase(), lifecycle); + if (original != null && logger.isWarnEnabled()) { + logger.warn( + "Duplicated lifecycle phase {}. Defined in {} but also in {}", + alias.v3Phase(), + original.getId(), + lifecycle.getId()); + } + } + } } return phaseToLifecycleMap; @@ -156,7 +174,7 @@ public class DefaultLifecycles { // Lifecycles cannot be cached as extensions might add custom lifecycles later in the execution. try { return registry != null - ? registry.stream().collect(Collectors.toMap(lf -> lf.id(), lf -> new Lifecycle(lf))) + ? registry.stream().collect(Collectors.toMap(lf -> lf.id(), lf -> new Lifecycle(registry, lf))) : Map.of(); } catch (LookupException e) { throw new IllegalStateException("Unable to lookup lifecycles from the plexus container", e); diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/Lifecycle.java b/maven-core/src/main/java/org/apache/maven/lifecycle/Lifecycle.java index d953d9f669..cc05a32ca6 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/Lifecycle.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/Lifecycle.java @@ -23,9 +23,13 @@ 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). */ @@ -38,11 +42,12 @@ public class Lifecycle { this.defaultPhases = defaultPhases; } - public Lifecycle(org.apache.maven.api.Lifecycle lifecycle) { + public Lifecycle( + org.apache.maven.api.services.LifecycleRegistry registry, org.apache.maven.api.Lifecycle lifecycle) { this.lifecycle = lifecycle; this.id = lifecycle.id(); - this.phases = lifecycle.phases().stream() - .map(org.apache.maven.api.Lifecycle.Phase::name) + this.phases = registry.computePhases(lifecycle).stream() + .flatMap(p -> Stream.of(BEFORE + p, p, AFTER + p)) .toList(); this.defaultPhases = getDefaultPhases(lifecycle); } @@ -71,13 +76,17 @@ public class Lifecycle { return id; } + public org.apache.maven.api.Lifecycle getDelegate() { + return lifecycle; + } + public List getPhases() { return phases; } static Map getDefaultPhases(org.apache.maven.api.Lifecycle lifecycle) { Map> goals = new HashMap<>(); - lifecycle.phases().forEach(phase -> phase.plugins() + lifecycle.allPhases().forEach(phase -> phase.plugins() .forEach(plugin -> plugin.getExecutions().forEach(exec -> exec.getGoals() .forEach(goal -> goals.computeIfAbsent(phase.name(), n -> new ArrayList<>()) .add(plugin.getGroupId() + ":" + plugin.getArtifactId() + ":" + plugin.getVersion() @@ -97,6 +106,10 @@ public class Lifecycle { @Override public String toString() { - return id + " -> " + phases; + return id + " -> " + + lifecycle + .allPhases() + .map(org.apache.maven.api.Lifecycle.Phase::name) + .collect(Collectors.joining(", ", "[", "]")); } } diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculator.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculator.java index e74bbf37f0..35eadd54c3 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculator.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculator.java @@ -475,7 +475,14 @@ public class DefaultLifecycleExecutionPlanCalculator implements LifecycleExecuti } for (Phase phase : lifecycleOverlay.getPhases()) { - List forkedExecutions = lifecycleMappings.get(phase.getId()); + String phaseId = defaultLifecycles.getLifeCycles().stream() + .flatMap(l -> l.getDelegate().aliases().stream()) + .filter(a -> phase.getId().equals(a.v3Phase())) + .findFirst() + .map(a -> a.v4Phase()) + .orElse(phase.getId()); + + List forkedExecutions = lifecycleMappings.get(phaseId); if (forkedExecutions != null) { for (Execution execution : phase.getExecutions()) { diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleMappingDelegate.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleMappingDelegate.java index 6ce06531e5..43598103f1 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleMappingDelegate.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleMappingDelegate.java @@ -23,10 +23,12 @@ import javax.inject.Named; import javax.inject.Singleton; import java.util.ArrayList; +import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; +import java.util.stream.Collectors; import org.apache.maven.execution.MavenSession; import org.apache.maven.lifecycle.Lifecycle; @@ -70,10 +72,19 @@ public class DefaultLifecycleMappingDelegate implements LifecycleMappingDelegate * is interested in, i.e. all phases up to and including the specified phase. */ - Map>> mappings = new LinkedHashMap<>(); + Map>> mappings = + new TreeMap<>(new PhaseComparator(lifecycle.getPhases())); + + Map aliases = lifecycle.getDelegate().aliases().stream() + .collect(Collectors.toMap(a -> a.v3Phase(), a -> a.v4Phase())); + + if (aliases.containsKey(lifecyclePhase)) { + lifecyclePhase = PhaseId.of(aliases.get(lifecyclePhase)).phase(); + } for (String phase : lifecycle.getPhases()) { - Map> phaseBindings = new TreeMap<>(); + Map> phaseBindings = + new TreeMap<>(Comparator.comparing(PhaseId::toString, new PhaseComparator(lifecycle.getPhases()))); mappings.put(phase, phaseBindings); @@ -93,13 +104,21 @@ public class DefaultLifecycleMappingDelegate implements LifecycleMappingDelegate for (PluginExecution execution : plugin.getExecutions()) { // if the phase is specified then I don't have to go fetch the plugin yet and pull it down // to examine the phase it is associated to. - if (execution.getPhase() != null) { - Map> phaseBindings = mappings.get(execution.getPhase()); + String phase = execution.getPhase(); + if (aliases.containsKey(phase)) { + phase = aliases.get(phase); + } + if (phase != null) { + Map> phaseBindings = getPhaseBindings(mappings, phase); if (phaseBindings != null) { for (String goal : execution.getGoals()) { MojoExecution mojoExecution = new MojoExecution(plugin, goal, execution.getId()); - mojoExecution.setLifecyclePhase(execution.getPhase()); - addMojoExecution(phaseBindings, mojoExecution, execution.getPriority()); + mojoExecution.setLifecyclePhase(phase); + PhaseId phaseId = PhaseId.of(phase); + if (phaseId.priority() == 0) { + phaseId = PhaseId.of(phase + "[" + execution.getPriority() + "]"); + } + addMojoExecution(phaseBindings, mojoExecution, phaseId); } } } @@ -109,11 +128,16 @@ public class DefaultLifecycleMappingDelegate implements LifecycleMappingDelegate MojoDescriptor mojoDescriptor = pluginManager.getMojoDescriptor( plugin, goal, project.getRemotePluginRepositories(), session.getRepositorySession()); - Map> phaseBindings = mappings.get(mojoDescriptor.getPhase()); + phase = mojoDescriptor.getPhase(); + if (aliases.containsKey(phase)) { + phase = aliases.get(phase); + } + Map> phaseBindings = getPhaseBindings(mappings, phase); if (phaseBindings != null) { MojoExecution mojoExecution = new MojoExecution(mojoDescriptor, execution.getId()); - mojoExecution.setLifecyclePhase(mojoDescriptor.getPhase()); - addMojoExecution(phaseBindings, mojoExecution, execution.getPriority()); + mojoExecution.setLifecyclePhase(phase); + PhaseId phaseId = PhaseId.of(phase + "[" + execution.getPriority() + "]"); + addMojoExecution(phaseBindings, mojoExecution, phaseId); } } } @@ -122,7 +146,7 @@ public class DefaultLifecycleMappingDelegate implements LifecycleMappingDelegate Map> lifecycleMappings = new LinkedHashMap<>(); - for (Map.Entry>> entry : mappings.entrySet()) { + for (Map.Entry>> entry : mappings.entrySet()) { List mojoExecutions = new ArrayList<>(); for (List executions : entry.getValue().values()) { @@ -135,9 +159,18 @@ public class DefaultLifecycleMappingDelegate implements LifecycleMappingDelegate return lifecycleMappings; } + private Map> getPhaseBindings( + Map>> mappings, String phase) { + if (phase != null) { + PhaseId id = PhaseId.of(phase); + return mappings.get(id.phase()); + } + return null; + } + private void addMojoExecution( - Map> phaseBindings, MojoExecution mojoExecution, int priority) { - List mojoExecutions = phaseBindings.computeIfAbsent(priority, k -> new ArrayList<>()); + Map> phaseBindings, MojoExecution mojoExecution, PhaseId phaseId) { + List mojoExecutions = phaseBindings.computeIfAbsent(phaseId, k -> new ArrayList<>()); mojoExecutions.add(mojoExecution); } diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseComparator.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseComparator.java new file mode 100644 index 0000000000..7c8004b8c3 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseComparator.java @@ -0,0 +1,74 @@ +/* + * 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.lifecycle.internal; + +import java.util.Comparator; +import java.util.List; + +/** + * Compares phases within the context of a specific lifecycle with secondary sorting based on the {@link PhaseId}. + */ +public class PhaseComparator implements Comparator { + /** + * The lifecycle phase ordering. + */ + private final List lifecyclePhases; + + /** + * Constructor. + * + * @param lifecyclePhases the lifecycle phase ordering. + */ + public PhaseComparator(List lifecyclePhases) { + this.lifecyclePhases = lifecyclePhases; + } + + @Override + 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()); + if (i1 == -1 && i2 == -1) { + // unknown phases, leave in existing order + return 0; + } + if (i1 == -1) { + // second one is known, so it comes first + return 1; + } + if (i2 == -1) { + // first one is known, so it comes first + return -1; + } + int rv = Integer.compare(i1, i2); + 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()); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseExecutionPoint.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseExecutionPoint.java new file mode 100644 index 0000000000..78e8981c91 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseExecutionPoint.java @@ -0,0 +1,50 @@ +/* + * 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.lifecycle.internal; + +/** + * Represents where a dynamic phase should be executed within a static phase. + */ +public enum PhaseExecutionPoint { + /** + * Execution must occur before any executions of the phase proper. Failure of any {@code #BEFORE} dynamic phase + * execution will prevent the {@link #AT} phase but will not prevent any {@link #AFTER} dynamic phases. + */ + BEFORE("before:"), + /** + * Execution is the execution of the phase proper. Failure of any {@code #AT} dynamic phase execution will fail + * the phase. Any {@link #AFTER} phases will still be executed. + */ + AT(""), + /** + * Guaranteed execution dynamic phases on completion of the static phase. All {@code #AFTER} dynamic phases will + * be executed provided at least one {@link #BEFORE} or {@link #AT} dynamic phase has started execution. + */ + AFTER("after:"); + + private final String prefix; + + PhaseExecutionPoint(String prefix) { + this.prefix = prefix; + } + + public String prefix() { + return prefix; + } +} diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseId.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseId.java new file mode 100644 index 0000000000..b52a989b7c --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseId.java @@ -0,0 +1,142 @@ +/* + * 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.lifecycle.internal; + +import java.util.Map; +import java.util.Objects; +import java.util.WeakHashMap; + +/** + * Represents a parsed phase identifier. + */ +public class PhaseId { + /** + * Interned {@link PhaseId} instances. + */ + private static final Map INSTANCES = new WeakHashMap<>(); + + /** + * The execution point of this {@link PhaseId}. + */ + private final PhaseExecutionPoint executionPoint; + + /** + * The static phase that this dynamic phase belongs to. + */ + private final String phase; + + /** + * The priority of this dynamic phase within the static phase. + */ + private final int priority; + + /** + * Parses the phase identifier. + * + * @param phase the phase identifier. + * @return the {@link PhaseId}. + */ + public static synchronized PhaseId of(String phase) { + return INSTANCES.computeIfAbsent(phase, PhaseId::new); + } + + /** + * Constructor. + * + * @param phase the phase identifier string. + */ + private PhaseId(String phase) { + int phaseStart; + if (phase.startsWith(PhaseExecutionPoint.BEFORE.prefix())) { + executionPoint = PhaseExecutionPoint.BEFORE; + phaseStart = PhaseExecutionPoint.BEFORE.prefix().length(); + } else if (phase.startsWith(PhaseExecutionPoint.AFTER.prefix())) { + executionPoint = PhaseExecutionPoint.AFTER; + phaseStart = PhaseExecutionPoint.AFTER.prefix().length(); + } else { + executionPoint = PhaseExecutionPoint.AT; + phaseStart = 0; + } + int phaseEnd = phase.indexOf('['); + if (phaseEnd == -1) { + priority = 0; + this.phase = phase.substring(phaseStart); + } else { + int priorityEnd = phase.lastIndexOf(']'); + boolean hasPriority; + int priority; + if (priorityEnd < phaseEnd + 1) { + priority = 0; + hasPriority = false; + } else { + try { + priority = Integer.parseInt(phase.substring(phaseEnd + 1, priorityEnd)); + hasPriority = true; + } catch (NumberFormatException e) { + // priority must be an integer + priority = 0; + hasPriority = false; + } + } + if (hasPriority) { + this.phase = phase.substring(phaseStart, phaseEnd); + this.priority = priority; + } else { + this.phase = phase.substring(phaseStart); + this.priority = 0; + } + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o == null || getClass() != o.getClass()) { + return false; + } else { + PhaseId phaseId = (PhaseId) o; + return Objects.equals(executionPoint(), phaseId.executionPoint()) + && Objects.equals(phase(), phaseId.phase()) + && Objects.equals(priority(), phaseId.priority()); + } + } + + @Override + public int hashCode() { + return Objects.hash(executionPoint(), phase(), priority()); + } + + @Override + public String toString() { + return executionPoint().prefix() + phase() + (priority() != 0 ? "[" + priority() + ']' : ""); + } + + public PhaseExecutionPoint executionPoint() { + return executionPoint; + } + + public String phase() { + return phase; + } + + public int priority() { + return priority; + } +} diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseRecorder.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseRecorder.java index c037af5c4d..a5f8f46825 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseRecorder.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseRecorder.java @@ -38,11 +38,12 @@ public class PhaseRecorder { String lifecyclePhase = mojoExecution.getLifecyclePhase(); if (lifecyclePhase != null) { + PhaseId phaseId = PhaseId.of(lifecyclePhase); if (lastLifecyclePhase == null) { - lastLifecyclePhase = lifecyclePhase; - } else if (!lifecyclePhase.equals(lastLifecyclePhase)) { + lastLifecyclePhase = phaseId.phase(); + } else if (!phaseId.phase().equals(lastLifecyclePhase)) { project.addLifecyclePhase(lastLifecyclePhase); - lastLifecyclePhase = lifecyclePhase; + lastLifecyclePhase = phaseId.phase(); } } @@ -56,6 +57,6 @@ public class PhaseRecorder { if (lifecyclePhase == null) { return lastLifecyclePhase != null; } - return !lifecyclePhase.equals(lastLifecyclePhase); + return !PhaseId.of(lifecyclePhase).phase().equals(lastLifecyclePhase); } } diff --git a/maven-core/src/test/java/org/apache/maven/lifecycle/DefaultLifecyclesTest.java b/maven-core/src/test/java/org/apache/maven/lifecycle/DefaultLifecyclesTest.java index ba12bcade0..38be02adae 100644 --- a/maven-core/src/test/java/org/apache/maven/lifecycle/DefaultLifecyclesTest.java +++ b/maven-core/src/test/java/org/apache/maven/lifecycle/DefaultLifecyclesTest.java @@ -59,7 +59,7 @@ class DefaultLifecyclesTest { void testDefaultLifecycle() { final Lifecycle lifecycle = getLifeCycleById("default"); assertThat(lifecycle.getId(), is("default")); - assertThat(lifecycle.getPhases(), hasSize(23)); + assertThat(lifecycle.getPhases(), hasSize(54)); } @Test @@ -73,14 +73,14 @@ class DefaultLifecyclesTest { void testSiteLifecycle() { final Lifecycle lifecycle = getLifeCycleById("site"); assertThat(lifecycle.getId(), is("site")); - assertThat(lifecycle.getPhases(), hasSize(4)); + assertThat(lifecycle.getPhases(), hasSize(6)); } @Test void testWrapperLifecycle() { final Lifecycle lifecycle = getLifeCycleById("wrapper"); assertThat(lifecycle.getId(), is("wrapper")); - assertThat(lifecycle.getPhases(), hasSize(1)); + assertThat(lifecycle.getPhases(), hasSize(3)); } @Test diff --git a/maven-core/src/test/java/org/apache/maven/lifecycle/LifecycleExecutorTest.java b/maven-core/src/test/java/org/apache/maven/lifecycle/LifecycleExecutorTest.java index 883530e702..6673e0567b 100644 --- a/maven-core/src/test/java/org/apache/maven/lifecycle/LifecycleExecutorTest.java +++ b/maven-core/src/test/java/org/apache/maven/lifecycle/LifecycleExecutorTest.java @@ -152,6 +152,7 @@ class LifecycleExecutorTest extends AbstractCoreMavenComponentTestCase { } // We need to take in multiple lifecycles + @Test public void testCalculationOfBuildPlanTasksOfTheCleanLifecycleAndTheInstallLifecycle() throws Exception { File pom = getProject("project-with-additional-lifecycle-elements"); MavenSession session = createMavenSession(pom); @@ -195,6 +196,7 @@ class LifecycleExecutorTest extends AbstractCoreMavenComponentTestCase { } // We need to take in multiple lifecycles + @Test public void testCalculationOfBuildPlanWithMultipleExecutionsOfModello() throws Exception { File pom = getProject("project-with-multiple-executions"); MavenSession session = createMavenSession(pom); diff --git a/maven-core/src/test/java/org/apache/maven/project/EmptyLifecycleBindingsInjector.java b/maven-core/src/test/java/org/apache/maven/project/EmptyLifecycleBindingsInjector.java index 747f2b8c3f..3a7bc1a2bf 100644 --- a/maven-core/src/test/java/org/apache/maven/project/EmptyLifecycleBindingsInjector.java +++ b/maven-core/src/test/java/org/apache/maven/project/EmptyLifecycleBindingsInjector.java @@ -61,6 +61,11 @@ public class EmptyLifecycleBindingsInjector extends DefaultLifecycleBindingsInje public Optional lookup(String id) { return Optional.empty(); } + + @Override + public List computePhases(Lifecycle lifecycle) { + return List.of(); + } }; private static final PackagingRegistry emptyPackagingRegistry = new PackagingRegistry() { @@ -139,6 +144,11 @@ public class EmptyLifecycleBindingsInjector extends DefaultLifecycleBindingsInje protected LifecycleRegistry getDelegate() { return lifecycleRegistry; } + + @Override + public List computePhases(Lifecycle lifecycle) { + return List.of(); + } } static class WrapperPackagingRegistry implements PackagingRegistry { diff --git a/maven-plugin-api/pom.xml b/maven-plugin-api/pom.xml index 148f3dc375..f3b63da0cb 100644 --- a/maven-plugin-api/pom.xml +++ b/maven-plugin-api/pom.xml @@ -117,7 +117,7 @@ under the License. ../api/maven-api-plugin/src/main/mdo/lifecycle.mdo - 1.0.0 + 2.0.0 diff --git a/src/mdo/reader-stax.vm b/src/mdo/reader-stax.vm index 0d3136d9f9..4b30a6a9b7 100644 --- a/src/mdo/reader-stax.vm +++ b/src/mdo/reader-stax.vm @@ -551,6 +551,9 @@ public class ${className} { #foreach ( $field in $allFields ) #if ( $Helper.xmlFieldMetadata( $field ).attribute ) #set ( $fieldTagName = $Helper.xmlFieldMetadata( $field ).tagName ) + #if ( ! $fieldTagName ) + #set ( $fieldTagName = $field.name ) + #end #set ( $fieldCapName = $Helper.capitalise( $field.name ) ) } else if ("$fieldTagName".equals(name)) { #if ( $locationTracking ) @@ -562,6 +565,8 @@ public class ${className} { ${classLcapName}.${field.name}(interpolatedTrimmed(value, "$fieldTagName")); #elseif ( $field.type == "boolean" || $field.type == "Boolean" ) ${classLcapName}.${field.name}(getBooleanValue(interpolatedTrimmed(value, "$fieldTagName"), "$fieldTagName", parser, ${field.defaultValue})); + #elseif ( $field.type == "int" || $field.type == "Integer" ) + ${classLcapName}.${field.name}(getIntegerValue(interpolatedTrimmed(value, "$fieldTagName"), "$fieldTagName", parser, strict, ${field.defaultValue})); #else // TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity} #end diff --git a/src/mdo/writer-stax.vm b/src/mdo/writer-stax.vm index 82e7c51f26..c479e91cc6 100644 --- a/src/mdo/writer-stax.vm +++ b/src/mdo/writer-stax.vm @@ -227,6 +227,9 @@ public class ${className} { #foreach ( $field in $allFields ) #if ( $Helper.xmlFieldMetadata( $field ).attribute && ! $Helper.xmlFieldMetadata( $field ).format ) #set ( $fieldTagName = $Helper.xmlFieldMetadata( $field ).tagName ) + #if ( ! $fieldTagName ) + #set ( $fieldTagName = $field.name ) + #end #set ( $fieldCapName = $Helper.capitalise( $field.name ) ) #if ( $field.type == "String" ) writeAttr("$fieldTagName", ${classLcapName}.get${fieldCapName}(), serializer); @@ -237,6 +240,8 @@ public class ${className} { #else writeAttr("$fieldTagName", ${classLcapName}.is${fieldCapName}() ? "true" : null, serializer); #end + #elseif ( $field.type == "int" || $field.type == "Integer" ) + writeAttr("$fieldTagName", Integer.toString(${classLcapName}.get${fieldCapName}()), serializer); #else // TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity} #end diff --git a/src/mdo/writer.vm b/src/mdo/writer.vm index 33a3689993..6c3d28b5a0 100644 --- a/src/mdo/writer.vm +++ b/src/mdo/writer.vm @@ -152,6 +152,9 @@ public class ${className} { #foreach ( $field in $allFields ) #if ( $Helper.xmlFieldMetadata( $field ).attribute ) #set ( $fieldTagName = $Helper.xmlFieldMetadata( $field ).tagName ) + #if ( ! $fieldTagName ) + #set ( $fieldTagName = $field.name ) + #end #set ( $fieldCapName = $Helper.capitalise( $field.name ) ) #if ( $field.type == "String" ) writeAttr("$fieldTagName", ${classLcapName}.get${fieldCapName}(), serializer); @@ -162,6 +165,8 @@ public class ${className} { #else writeAttr("$fieldTagName", ${classLcapName}.is${fieldCapName}() ? "true" : null, serializer); #end + #elseif ( $field.type == "int" || $field.type == "Integer" ) + writeAttr("$fieldTagName", Integer.toString(${classLcapName}.get${fieldCapName}()), serializer); #else // TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity} #end