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

commit 9be08ccef8d6c59867f49ecb98e772776fc8f9c0
Author: Guillaume Nodet <gno...@gmail.com>
AuthorDate: Thu Apr 11 08:17:32 2024 +0200

    [MNG-8084] Include lifecycle in the API
---
 .../api/{MetadataStorage.java => Lifecycle.java}   |  50 ++++-
 .../main/java/org/apache/maven/api/PathScope.java  |   1 +
 .../maven/api/plugin/annotations/Execute.java      |  13 +-
 .../api/plugin/annotations/LifecyclePhase.java     |  74 -------
 .../apache/maven/api/plugin/annotations/Mojo.java  |   2 +-
 .../maven/api/services/LifecycleRegistry.java      |  34 +--
 .../apache/maven/api/spi/LifecycleProvider.java}   |  16 +-
 .../internal/impl/DefaultLifecycleRegistry.java    | 227 +++++++++++++++++++++
 .../internal/impl/ExtensibleEnumRegistries.java    |   9 +-
 .../java/org/apache/maven/internal/impl/Graph.java | 145 +++++++++++++
 .../org/apache/maven/internal/impl/Lifecycles.java |  73 +++++++
 .../apache/maven/lifecycle/DefaultLifecycles.java  |  16 +-
 .../java/org/apache/maven/lifecycle/Lifecycle.java |  29 ++-
 .../providers/CleanLifecycleProvider.java          |  47 -----
 .../providers/DefaultLifecycleProvider.java        |  70 -------
 .../lifecycle/providers/SiteLifecycleProvider.java |  48 -----
 .../maven/lifecycle/DefaultLifecyclesTest.java     |   5 +-
 .../internal/stub/DefaultLifecyclesStub.java       |   4 +-
 18 files changed, 559 insertions(+), 304 deletions(-)

diff --git 
a/api/maven-api-core/src/main/java/org/apache/maven/api/MetadataStorage.java 
b/api/maven-api-core/src/main/java/org/apache/maven/api/Lifecycle.java
similarity index 50%
copy from 
api/maven-api-core/src/main/java/org/apache/maven/api/MetadataStorage.java
copy to api/maven-api-core/src/main/java/org/apache/maven/api/Lifecycle.java
index a7b8075ada..c461ef0ad0 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/MetadataStorage.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Lifecycle.java
@@ -18,16 +18,56 @@
  */
 package org.apache.maven.api;
 
+import java.util.*;
+
 import org.apache.maven.api.annotations.Experimental;
+import org.apache.maven.api.annotations.Immutable;
+import org.apache.maven.api.model.Plugin;
 
 /**
- * Storage location for metadata
+ * Lifecycle definition
  *
  * @since 4.0.0
  */
 @Experimental
-public enum MetadataStorage {
-    GROUP,
-    ARTIFACT,
-    VERSION
+@Immutable
+public interface Lifecycle extends ExtensibleEnum {
+
+    String CLEAN = "clean";
+
+    String DEFAULT = "default";
+
+    String SITE = "site";
+
+    String WRAPPER = "wrapper";
+
+    /**
+     * Name or identifier of this lifecycle.
+     *
+     * @return the unique identifier for this lifecycle
+     */
+    @Override
+    String id();
+
+    /**
+     * Collection of phases for this lifecycle
+     */
+    Collection<Phase> phases();
+
+    /**
+     * 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.
+     */
+    interface Phase {
+        String name();
+
+        List<Plugin> plugins();
+    }
 }
diff --git 
a/api/maven-api-core/src/main/java/org/apache/maven/api/PathScope.java 
b/api/maven-api-core/src/main/java/org/apache/maven/api/PathScope.java
index 93b1c90545..b32dd3c172 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/PathScope.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/PathScope.java
@@ -46,6 +46,7 @@ import static org.apache.maven.api.ExtensibleEnums.pathScope;
 @Immutable
 public interface PathScope extends ExtensibleEnum {
 
+    // TODO: what if I simply want all dependencies ?
     @Nonnull
     ProjectScope projectScope();
 
diff --git 
a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/Execute.java
 
b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/Execute.java
index 903d224f38..f4830b87ae 100644
--- 
a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/Execute.java
+++ 
b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/Execute.java
@@ -41,21 +41,10 @@ import org.apache.maven.api.annotations.Nonnull;
 public @interface Execute {
     /**
      * Lifecycle phase to fork. Note that specifying a phase overrides 
specifying a goal.
-     * For custom lifecycle phase ids use {@link #customPhase()} instead.
-     * Only one of {@link #customPhase()} and {@link #phase()} must be set.
      * @return the phase
      */
     @Nonnull
-    LifecyclePhase phase() default LifecyclePhase.NONE;
-
-    /**
-     * Custom lifecycle phase to fork. Note that specifying a phase overrides 
specifying a goal.
-     * This element should only be used for non-standard phases. For standard 
phases rather use {@link #phase()}.
-     * Only one of {@link #customPhase()} and {@link #phase()} must be set.
-     * @return the custom phase id
-     */
-    @Nonnull
-    String customPhase() default "";
+    String phase() default "";
 
     /**
      * Goal to fork. Note that specifying a phase overrides specifying a goal. 
The specified <code>goal</code> must be
diff --git 
a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/LifecyclePhase.java
 
b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/LifecyclePhase.java
deleted file mode 100644
index b3ddb72f38..0000000000
--- 
a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/LifecyclePhase.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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 org.apache.maven.api.annotations.Experimental;
-
-/**
- * <a href="/ref/3.0.4/maven-core/lifecycles.html">Lifecycle phases</a>.
- *
- * @since 4.0.0
- */
-@Experimental
-public enum LifecyclePhase {
-    VALIDATE("validate"),
-    INITIALIZE("initialize"),
-    GENERATE_SOURCES("generate-sources"),
-    PROCESS_SOURCES("process-sources"),
-    GENERATE_RESOURCES("generate-resources"),
-    PROCESS_RESOURCES("process-resources"),
-    COMPILE("compile"),
-    PROCESS_CLASSES("process-classes"),
-    GENERATE_TEST_SOURCES("generate-test-sources"),
-    PROCESS_TEST_SOURCES("process-test-sources"),
-    GENERATE_TEST_RESOURCES("generate-test-resources"),
-    PROCESS_TEST_RESOURCES("process-test-resources"),
-    TEST_COMPILE("test-compile"),
-    PROCESS_TEST_CLASSES("process-test-classes"),
-    TEST("test"),
-    PREPARE_PACKAGE("prepare-package"),
-    PACKAGE("package"),
-    PRE_INTEGRATION_TEST("pre-integration-test"),
-    INTEGRATION_TEST("integration-test"),
-    POST_INTEGRATION_TEST("post-integration-test"),
-    VERIFY("verify"),
-    INSTALL("install"),
-    DEPLOY("deploy"),
-
-    PRE_CLEAN("pre-clean"),
-    CLEAN("clean"),
-    POST_CLEAN("post-clean"),
-
-    PRE_SITE("pre-site"),
-    SITE("site"),
-    POST_SITE("post-site"),
-    SITE_DEPLOY("site-deploy"),
-
-    NONE("");
-
-    private final String id;
-
-    LifecyclePhase(String id) {
-        this.id = id;
-    }
-
-    public String id() {
-        return this.id;
-    }
-}
diff --git 
a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/Mojo.java
 
b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/Mojo.java
index 4f01f0f6d2..a5768dfb4a 100644
--- 
a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/Mojo.java
+++ 
b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/Mojo.java
@@ -54,7 +54,7 @@ public @interface Mojo {
      * @return the default phase
      */
     @Nonnull
-    LifecyclePhase defaultPhase() default LifecyclePhase.NONE;
+    String defaultPhase() default "";
 
     /**
      * does your mojo requires a project to be executed?
diff --git 
a/maven-core/src/main/java/org/apache/maven/lifecycle/providers/WrapperLifecycleProvider.java
 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/LifecycleRegistry.java
similarity index 50%
rename from 
maven-core/src/main/java/org/apache/maven/lifecycle/providers/WrapperLifecycleProvider.java
rename to 
api/maven-api-core/src/main/java/org/apache/maven/api/services/LifecycleRegistry.java
index 29ddaea043..2718582a24 100644
--- 
a/maven-core/src/main/java/org/apache/maven/lifecycle/providers/WrapperLifecycleProvider.java
+++ 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/LifecycleRegistry.java
@@ -16,32 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.maven.lifecycle.providers;
+package org.apache.maven.api.services;
 
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
+import java.util.List;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
 
-/**
- * {@code wrapper} lifecycle provider.
- */
-@Named(WrapperLifecycleProvider.LIFECYCLE_ID)
-@Singleton
-public final class WrapperLifecycleProvider extends AbstractLifecycleProvider {
-    static final String LIFECYCLE_ID = "wrapper";
-
-    // START SNIPPET: wrapper
-    private static final String[] PHASES = {"wrapper"};
-
-    private static final String MAVEN_WRAPPER_PLUGIN_VERSION = "3.2.0";
+import org.apache.maven.api.Lifecycle;
 
-    private static final String[] BINDINGS = {
-        "wrapper", "org.apache.maven.plugins:maven-wrapper-plugin:" + 
MAVEN_WRAPPER_PLUGIN_VERSION + ":wrapper"
-    };
-    // END SNIPPET: wrapper
-
-    @Inject
-    public WrapperLifecycleProvider() {
-        super(LIFECYCLE_ID, PHASES, BINDINGS);
+public interface LifecycleRegistry extends ExtensibleEnumRegistry<Lifecycle>, 
Iterable<Lifecycle> {
+    default Stream<Lifecycle> stream() {
+        return StreamSupport.stream(spliterator(), false);
     }
+
+    List<String> computePhases(Lifecycle lifecycle);
 }
diff --git 
a/api/maven-api-core/src/main/java/org/apache/maven/api/MetadataStorage.java 
b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/LifecycleProvider.java
similarity index 80%
rename from 
api/maven-api-core/src/main/java/org/apache/maven/api/MetadataStorage.java
rename to 
api/maven-api-spi/src/main/java/org/apache/maven/api/spi/LifecycleProvider.java
index a7b8075ada..539c628299 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/MetadataStorage.java
+++ 
b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/LifecycleProvider.java
@@ -16,18 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.maven.api;
+package org.apache.maven.api.spi;
 
+import org.apache.maven.api.Lifecycle;
+import org.apache.maven.api.annotations.Consumer;
 import org.apache.maven.api.annotations.Experimental;
 
-/**
- * Storage location for metadata
- *
- * @since 4.0.0
- */
 @Experimental
-public enum MetadataStorage {
-    GROUP,
-    ARTIFACT,
-    VERSION
-}
+@Consumer
+public interface LifecycleProvider extends ExtensibleEnumProvider<Lifecycle> {}
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
new file mode 100644
index 0000000000..776f8d8a27
--- /dev/null
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultLifecycleRegistry.java
@@ -0,0 +1,227 @@
+/*
+ * 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.internal.impl;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.maven.api.Lifecycle;
+import org.apache.maven.api.services.LifecycleRegistry;
+import org.apache.maven.api.spi.LifecycleProvider;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.*;
+import static org.apache.maven.internal.impl.Lifecycles.*;
+
+/**
+ * TODO: this is session scoped as SPI can contribute.
+ */
+@Named
+@Singleton
+public class DefaultLifecycleRegistry
+        extends 
ExtensibleEnumRegistries.DefaultExtensibleEnumRegistry<Lifecycle, 
LifecycleProvider>
+        implements LifecycleRegistry {
+
+    public DefaultLifecycleRegistry() {
+        super(Collections.emptyList());
+    }
+
+    @Inject
+    public DefaultLifecycleRegistry(
+            List<LifecycleProvider> providers, Map<String, 
org.apache.maven.lifecycle.Lifecycle> lifecycles) {
+        super(
+                concat(providers, new LifecycleWrapperProvider(lifecycles)),
+                new CleanLifecycle(),
+                new DefaultLifecycle(),
+                new SiteLifecycle(),
+                new WrapperLifecycle());
+        // validate lifecycle
+        for (Lifecycle lifecycle : this) {
+            Set<String> set = new HashSet<>();
+            lifecycle.phases().forEach(phase -> {
+                if (!set.add(phase.name())) {
+                    throw new IllegalArgumentException(
+                            "Found duplicated phase '" + phase.name() + "' in 
'" + lifecycle.id() + "' lifecycle");
+                }
+            });
+        }
+    }
+
+    @Override
+    public Iterator<Lifecycle> iterator() {
+        return values.values().iterator();
+    }
+
+    @Override
+    public Stream<Lifecycle> stream() {
+        return values.values().stream();
+    }
+
+    static <T> List<T> concat(List<T> l, T t) {
+        List<T> nl = new ArrayList<>(l.size() + 1);
+        nl.addAll(l);
+        nl.add(t);
+        return nl;
+    }
+
+    @Override
+    public List<String> computePhases(Lifecycle lifecycle) {
+        return lifecycle.phases().stream().map(Lifecycle.Phase::name).toList();
+    }
+
+    static class LifecycleWrapperProvider implements LifecycleProvider {
+        private final Map<String, org.apache.maven.lifecycle.Lifecycle> 
lifecycles;
+
+        @Inject
+        LifecycleWrapperProvider(Map<String, 
org.apache.maven.lifecycle.Lifecycle> lifecycles) {
+            this.lifecycles = lifecycles;
+        }
+
+        @Override
+        public Collection<Lifecycle> provides() {
+            return 
lifecycles.values().stream().map(this::wrap).collect(Collectors.toList());
+        }
+
+        private Lifecycle wrap(org.apache.maven.lifecycle.Lifecycle lifecycle) 
{
+            return new Lifecycle() {
+                @Override
+                public String id() {
+                    return lifecycle.getId();
+                }
+
+                @Override
+                public Collection<Phase> phases() {
+                    // TODO: implement
+                    throw new UnsupportedOperationException();
+                }
+            };
+        }
+    }
+
+    static class CleanLifecycle implements Lifecycle {
+
+        private static final String MAVEN_CLEAN_PLUGIN_VERSION = "3.2.0";
+
+        @Override
+        public String id() {
+            return Lifecycle.CLEAN;
+        }
+
+        @Override
+        public Collection<Phase> 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"));
+        }
+    }
+
+    static class DefaultLifecycle implements Lifecycle {
+        @Override
+        public String id() {
+            return Lifecycle.DEFAULT;
+        }
+
+        @Override
+        public Collection<Phase> 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"));
+        }
+    }
+
+    static class SiteLifecycle implements Lifecycle {
+
+        private static final String MAVEN_SITE_PLUGIN_VERSION = "3.12.1";
+
+        @Override
+        public String id() {
+            return Lifecycle.SITE;
+        }
+
+        @Override
+        public Collection<Phase> phases() {
+            return asList(
+                    phase("pre-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")));
+        }
+    }
+
+    static class WrapperLifecycle implements Lifecycle {
+
+        private static final String MAVEN_WRAPPER_PLUGIN_VERSION = "3.2.0";
+
+        @Override
+        public String id() {
+            return WRAPPER;
+        }
+
+        @Override
+        public Collection<Phase> phases() {
+            return singleton(phase(
+                    "wrapper",
+                    plugin(
+                            "org.apache.maven.plugins:maven-wrapper-plugin:" + 
MAVEN_WRAPPER_PLUGIN_VERSION
+                                    + ":wrapper",
+                            "wrapper")));
+        }
+    }
+}
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/impl/ExtensibleEnumRegistries.java
 
b/maven-core/src/main/java/org/apache/maven/internal/impl/ExtensibleEnumRegistries.java
index d512e74b46..e661a40397 100644
--- 
a/maven-core/src/main/java/org/apache/maven/internal/impl/ExtensibleEnumRegistries.java
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/impl/ExtensibleEnumRegistries.java
@@ -25,6 +25,7 @@ import javax.inject.Singleton;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -33,6 +34,8 @@ import org.apache.maven.api.*;
 import org.apache.maven.api.services.*;
 import org.apache.maven.api.spi.*;
 
+import static org.apache.maven.internal.impl.Utils.nonNull;
+
 public class ExtensibleEnumRegistries {
 
     @Named
@@ -76,17 +79,17 @@ public class ExtensibleEnumRegistries {
     static class DefaultExtensibleEnumRegistry<T extends ExtensibleEnum, P 
extends ExtensibleEnumProvider<T>>
             implements ExtensibleEnumRegistry<T> {
 
-        private final Map<String, T> values;
+        protected final Map<String, T> values;
 
         DefaultExtensibleEnumRegistry(List<P> providers, T... builtinValues) {
             values = Stream.<T>concat(
                             Stream.of(builtinValues), 
providers.stream().flatMap(p -> p.provides().stream()))
-                    .collect(Collectors.toMap(t -> t.id(), t -> t));
+                    .collect(Collectors.toUnmodifiableMap(ExtensibleEnum::id, 
Function.identity()));
         }
 
         @Override
         public Optional<T> lookup(String id) {
-            return Optional.ofNullable(values.get(id));
+            return Optional.ofNullable(values.get(nonNull(id, "id")));
         }
     }
 }
diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/Graph.java 
b/maven-core/src/main/java/org/apache/maven/internal/impl/Graph.java
new file mode 100644
index 0000000000..6bbdb51ab8
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/internal/impl/Graph.java
@@ -0,0 +1,145 @@
+/*
+ * 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.internal.impl;
+
+import java.util.*;
+
+import org.apache.maven.project.CycleDetectedException;
+
+class Graph {
+    private enum DfsState {
+        VISITING,
+        VISITED
+    }
+
+    final Map<String, Vertex> vertices = new LinkedHashMap<>();
+
+    public Vertex getVertex(String id) {
+        return vertices.get(id);
+    }
+
+    public Collection<Vertex> getVertices() {
+        return vertices.values();
+    }
+
+    Vertex addVertex(String label) {
+        return vertices.computeIfAbsent(label, Vertex::new);
+    }
+
+    void addEdge(Vertex from, Vertex to) throws CycleDetectedException {
+        from.children.add(to);
+        to.parents.add(from);
+        List<String> cycle = findCycle(to);
+        if (cycle != null) {
+            // remove edge which introduced cycle
+            removeEdge(from, to);
+            throw new CycleDetectedException(
+                    "Edge between '" + from.label + "' and '" + to.label + "' 
introduces to cycle in the graph", cycle);
+        }
+    }
+
+    void removeEdge(Vertex from, Vertex to) {
+        from.children.remove(to);
+        to.parents.remove(from);
+    }
+
+    List<String> visitAll() {
+        return visitAll(vertices.values(), new HashMap<>(), new ArrayList<>());
+    }
+
+    List<String> findCycle(Vertex vertex) {
+        return visitCycle(Collections.singleton(vertex), new HashMap<>(), new 
LinkedList<>());
+    }
+
+    private static List<String> visitAll(
+            Collection<Vertex> children, Map<Vertex, DfsState> stateMap, 
List<String> list) {
+        for (Vertex v : children) {
+            DfsState state = stateMap.putIfAbsent(v, DfsState.VISITING);
+            if (state == null) {
+                visitAll(v.children, stateMap, list);
+                stateMap.put(v, DfsState.VISITED);
+                list.add(v.label);
+            }
+        }
+        return list;
+    }
+
+    private static List<String> visitCycle(
+            Collection<Vertex> children, Map<Vertex, DfsState> stateMap, 
LinkedList<String> cycle) {
+        for (Vertex v : children) {
+            DfsState state = stateMap.putIfAbsent(v, DfsState.VISITING);
+            if (state == null) {
+                cycle.addLast(v.label);
+                List<String> ret = visitCycle(v.children, stateMap, cycle);
+                if (ret != null) {
+                    return ret;
+                }
+                cycle.removeLast();
+                stateMap.put(v, DfsState.VISITED);
+            } else if (state == DfsState.VISITING) {
+                // we are already visiting this vertex, this mean we have a 
cycle
+                int pos = cycle.lastIndexOf(v.label);
+                List<String> ret = cycle.subList(pos, cycle.size());
+                ret.add(v.label);
+                return ret;
+            }
+        }
+        return null;
+    }
+
+    static class Vertex {
+        final String label;
+        final List<Vertex> children = new ArrayList<>();
+        final List<Vertex> parents = new ArrayList<>();
+
+        Vertex(String label) {
+            this.label = label;
+        }
+
+        String getLabel() {
+            return label;
+        }
+
+        List<Vertex> getChildren() {
+            return children;
+        }
+
+        List<Vertex> getParents() {
+            return parents;
+        }
+    }
+
+    static class CycleDetectedException extends RuntimeException {
+        private final List<String> cycle;
+
+        CycleDetectedException(String message, List<String> cycle) {
+            super(message);
+            this.cycle = cycle;
+        }
+
+        public List<String> getCycle() {
+            return cycle;
+        }
+
+        @Override
+        public String getMessage() {
+            return super.getMessage() + " " + String.join(" --> ", cycle);
+        }
+    }
+}
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
new file mode 100644
index 0000000000..42da23b290
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/internal/impl/Lifecycles.java
@@ -0,0 +1,73 @@
+/*
+ * 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.internal.impl;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.maven.api.Lifecycle;
+import org.apache.maven.api.model.Plugin;
+import org.apache.maven.api.model.PluginExecution;
+
+public class Lifecycles {
+
+    static Lifecycle.Phase phase(String name) {
+        return new DefaultPhase(name, Collections.emptyList(), 
Collections.emptyList());
+    }
+
+    static Lifecycle.Phase phase(String name, Plugin plugin) {
+        return new DefaultPhase(name, Collections.singletonList(plugin), 
Collections.emptyList());
+    }
+
+    static Plugin plugin(String coord, String phase) {
+        String[] c = coord.split(":");
+        return Plugin.newBuilder()
+                .groupId(c[0])
+                .artifactId(c[1])
+                .version(c[2])
+                
.executions(Collections.singletonList(PluginExecution.newBuilder()
+                        .id("default-" + c[3])
+                        .phase(phase)
+                        .goals(Collections.singletonList(c[3]))
+                        .build()))
+                .build();
+    }
+
+    static class DefaultPhase implements Lifecycle.Phase {
+        private final String name;
+        private final List<Plugin> plugins;
+        private final List<Lifecycle.Phase> phases;
+
+        DefaultPhase(String name, List<Plugin> plugins, List<Lifecycle.Phase> 
phases) {
+            this.name = name;
+            this.plugins = plugins;
+            this.phases = phases;
+        }
+
+        @Override
+        public String name() {
+            return name;
+        }
+
+        @Override
+        public List<Plugin> plugins() {
+            return plugins;
+        }
+    }
+}
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 ed9313db75..3987985cbe 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
@@ -30,6 +30,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.stream.Collectors;
 
+import org.apache.maven.api.services.LifecycleRegistry;
 import org.apache.maven.api.services.Lookup;
 import org.apache.maven.api.services.LookupException;
 import org.slf4j.Logger;
@@ -51,24 +52,29 @@ public class DefaultLifecycles {
 
     private final Lookup lookup;
 
+    private final LifecycleRegistry registry;
+
     private Map<String, Lifecycle> customLifecycles;
 
     public DefaultLifecycles() {
         this.lookup = null;
+        this.registry = null;
     }
 
     /**
-     * @deprecated Rely on {@link #DefaultLifecycles(Lookup)} instead
+     * @deprecated Use {@link #DefaultLifecycles(LifecycleRegistry,Lookup)} 
instead
      */
     @Deprecated
     public DefaultLifecycles(Map<String, Lifecycle> lifecycles, 
org.codehaus.plexus.logging.Logger logger) {
         this.customLifecycles = lifecycles;
         this.lookup = null;
+        this.registry = null;
     }
 
     @Inject
-    public DefaultLifecycles(Lookup lookup) {
+    public DefaultLifecycles(LifecycleRegistry registry, Lookup lookup) {
         this.lookup = lookup;
+        this.registry = registry;
     }
 
     /**
@@ -149,7 +155,11 @@ public class DefaultLifecycles {
 
         // Lifecycles cannot be cached as extensions might add custom 
lifecycles later in the execution.
         try {
-            return lookup.lookupMap(Lifecycle.class);
+            Map<String, Lifecycle> lifecycles = new 
HashMap<>(lookup.lookupMap(Lifecycle.class));
+            for (org.apache.maven.api.Lifecycle lf : registry) {
+                lifecycles.put(lf.id(), new Lifecycle(registry, lf));
+            }
+            return lifecycles;
         } 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 b3b63cbaaa..089e412ad5 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
@@ -18,8 +18,8 @@
  */
 package org.apache.maven.lifecycle;
 
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.stream.Collectors;
 
 import org.apache.maven.lifecycle.mapping.LifecyclePhase;
 
@@ -35,6 +35,14 @@ public class Lifecycle {
         this.defaultPhases = defaultPhases;
     }
 
+    public 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);
+        this.defaultPhases = getDefaultPhases(lifecycle);
+    }
+
     // <lifecycle>
     //   <id>clean</id>
     //   <phases>
@@ -53,12 +61,25 @@ public class Lifecycle {
 
     private Map<String, LifecyclePhase> defaultPhases;
 
+    private org.apache.maven.api.Lifecycle lifecycle;
+
     public String getId() {
-        return this.id;
+        return id;
     }
 
     public List<String> getPhases() {
-        return this.phases;
+        return phases;
+    }
+
+    static Map<String, LifecyclePhase> 
getDefaultPhases(org.apache.maven.api.Lifecycle lifecycle) {
+        Map<String, List<String>> goals = new HashMap<>();
+        lifecycle.phases().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()
+                                        + ":" + goal)))));
+        return goals.entrySet().stream()
+                .collect(Collectors.toMap(Map.Entry::getKey, e -> new 
LifecyclePhase(String.join(",", e.getValue()))));
     }
 
     public Map<String, LifecyclePhase> getDefaultLifecyclePhases() {
diff --git 
a/maven-core/src/main/java/org/apache/maven/lifecycle/providers/CleanLifecycleProvider.java
 
b/maven-core/src/main/java/org/apache/maven/lifecycle/providers/CleanLifecycleProvider.java
deleted file mode 100644
index cd8873d1b9..0000000000
--- 
a/maven-core/src/main/java/org/apache/maven/lifecycle/providers/CleanLifecycleProvider.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.providers;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-
-/**
- * {@code clean} lifecycle provider.
- */
-@Named(CleanLifecycleProvider.LIFECYCLE_ID)
-@Singleton
-public final class CleanLifecycleProvider extends AbstractLifecycleProvider {
-    static final String LIFECYCLE_ID = "clean";
-
-    // START SNIPPET: clean
-    private static final String[] PHASES = {"pre-clean", "clean", 
"post-clean"};
-
-    private static final String MAVEN_CLEAN_PLUGIN_VERSION = "3.2.0";
-
-    private static final String[] BINDINGS = {
-        "clean", "org.apache.maven.plugins:maven-clean-plugin:" + 
MAVEN_CLEAN_PLUGIN_VERSION + ":clean"
-    };
-    // END SNIPPET: clean
-
-    @Inject
-    public CleanLifecycleProvider() {
-        super(LIFECYCLE_ID, PHASES, BINDINGS);
-    }
-}
diff --git 
a/maven-core/src/main/java/org/apache/maven/lifecycle/providers/DefaultLifecycleProvider.java
 
b/maven-core/src/main/java/org/apache/maven/lifecycle/providers/DefaultLifecycleProvider.java
deleted file mode 100644
index 9f7602ab69..0000000000
--- 
a/maven-core/src/main/java/org/apache/maven/lifecycle/providers/DefaultLifecycleProvider.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.providers;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-
-/**
- * {@code default} lifecycle provider.
- */
-@Named(DefaultLifecycleProvider.LIFECYCLE_ID)
-@Singleton
-public final class DefaultLifecycleProvider extends AbstractLifecycleProvider {
-    static final String LIFECYCLE_ID = "default";
-
-    // START SNIPPET: default
-    private static final String[] PHASES = {
-        "validate",
-        "initialize",
-        "generate-sources",
-        "process-sources",
-        "generate-resources",
-        "process-resources",
-        "compile",
-        "process-classes",
-        "generate-test-sources",
-        "process-test-sources",
-        "generate-test-resources",
-        "process-test-resources",
-        "test-compile",
-        "process-test-classes",
-        "test",
-        "prepare-package",
-        "package",
-        "pre-integration-test",
-        "integration-test",
-        "post-integration-test",
-        "verify",
-        "install",
-        "deploy"
-    };
-    // END SNIPPET: default
-
-    @Inject
-    public DefaultLifecycleProvider() {
-        super(
-                LIFECYCLE_ID,
-                PHASES,
-                null // no global plugin bindings for default lifecycle: they 
are defined per-packaging in separate
-                // providers
-                );
-    }
-}
diff --git 
a/maven-core/src/main/java/org/apache/maven/lifecycle/providers/SiteLifecycleProvider.java
 
b/maven-core/src/main/java/org/apache/maven/lifecycle/providers/SiteLifecycleProvider.java
deleted file mode 100644
index 8dd6439b8a..0000000000
--- 
a/maven-core/src/main/java/org/apache/maven/lifecycle/providers/SiteLifecycleProvider.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.providers;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-
-/**
- * {@code site} lifecycle provider.
- */
-@Named(SiteLifecycleProvider.LIFECYCLE_ID)
-@Singleton
-public final class SiteLifecycleProvider extends AbstractLifecycleProvider {
-    static final String LIFECYCLE_ID = "site";
-
-    // START SNIPPET: site
-    private static final String[] PHASES = {"pre-site", "site", "post-site", 
"site-deploy"};
-
-    private static final String MAVEN_SITE_PLUGIN_VERSION = "3.12.1";
-
-    private static final String[] BINDINGS = {
-        "site", "org.apache.maven.plugins:maven-site-plugin:" + 
MAVEN_SITE_PLUGIN_VERSION + ":site",
-        "site-deploy", "org.apache.maven.plugins:maven-site-plugin:" + 
MAVEN_SITE_PLUGIN_VERSION + ":deploy"
-    };
-    // END SNIPPET: site
-
-    @Inject
-    public SiteLifecycleProvider() {
-        super(LIFECYCLE_ID, PHASES, BINDINGS);
-    }
-}
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 fe42923898..99b8793eae 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
@@ -27,6 +27,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
+import org.apache.maven.internal.impl.DefaultLifecycleRegistry;
 import org.apache.maven.internal.impl.DefaultLookup;
 import org.codehaus.plexus.PlexusContainer;
 import 
org.codehaus.plexus.component.repository.exception.ComponentLookupException;
@@ -94,7 +95,9 @@ class DefaultLifecyclesTest {
         PlexusContainer mockedPlexusContainer = mock(PlexusContainer.class);
         
when(mockedPlexusContainer.lookupMap(Lifecycle.class)).thenReturn(lifeCycles);
 
-        DefaultLifecycles dl = new DefaultLifecycles(new 
DefaultLookup(mockedPlexusContainer));
+        DefaultLifecycles dl = new DefaultLifecycles(
+                new DefaultLifecycleRegistry(Collections.emptyList(), 
Collections.emptyMap()),
+                new DefaultLookup(mockedPlexusContainer));
 
         assertThat(dl.getLifeCycles().get(0).getId(), is("clean"));
         assertThat(dl.getLifeCycles().get(1).getId(), is("default"));
diff --git 
a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/DefaultLifecyclesStub.java
 
b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/DefaultLifecyclesStub.java
index 9260b8332c..65c0b65570 100644
--- 
a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/DefaultLifecyclesStub.java
+++ 
b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/DefaultLifecyclesStub.java
@@ -24,6 +24,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.maven.internal.impl.DefaultLifecycleRegistry;
 import org.apache.maven.internal.impl.DefaultLookup;
 import org.apache.maven.lifecycle.DefaultLifecycles;
 import org.apache.maven.lifecycle.Lifecycle;
@@ -71,6 +72,7 @@ public class DefaultLifecyclesStub {
         PlexusContainer mockedContainer = mock(PlexusContainer.class);
         
when(mockedContainer.lookupMap(Lifecycle.class)).thenReturn(lifeCycles);
 
-        return new DefaultLifecycles(new DefaultLookup(mockedContainer));
+        DefaultLifecycleRegistry reg = new DefaultLifecycleRegistry();
+        return new DefaultLifecycles(reg, new DefaultLookup(mockedContainer));
     }
 }


Reply via email to