View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.internal.impl;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Provider;
24  import javax.inject.Singleton;
25  
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.HashSet;
31  import java.util.Iterator;
32  import java.util.LinkedHashMap;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Objects;
36  import java.util.Optional;
37  import java.util.Set;
38  import java.util.stream.Collectors;
39  import java.util.stream.Stream;
40  
41  import org.apache.maven.api.Lifecycle;
42  import org.apache.maven.api.model.InputLocation;
43  import org.apache.maven.api.model.InputSource;
44  import org.apache.maven.api.model.Plugin;
45  import org.apache.maven.api.services.LifecycleRegistry;
46  import org.apache.maven.api.services.LookupException;
47  import org.apache.maven.api.spi.ExtensibleEnumProvider;
48  import org.apache.maven.api.spi.LifecycleProvider;
49  import org.apache.maven.lifecycle.mapping.LifecyclePhase;
50  import org.codehaus.plexus.PlexusContainer;
51  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
52  
53  import static org.apache.maven.api.Lifecycle.Phase.BUILD;
54  import static org.apache.maven.api.Lifecycle.Phase.COMPILE;
55  import static org.apache.maven.api.Lifecycle.Phase.DEPLOY;
56  import static org.apache.maven.api.Lifecycle.Phase.INITIALIZE;
57  import static org.apache.maven.api.Lifecycle.Phase.INSTALL;
58  import static org.apache.maven.api.Lifecycle.Phase.INTEGRATION_TEST;
59  import static org.apache.maven.api.Lifecycle.Phase.PACKAGE;
60  import static org.apache.maven.api.Lifecycle.Phase.READY;
61  import static org.apache.maven.api.Lifecycle.Phase.RESOURCES;
62  import static org.apache.maven.api.Lifecycle.Phase.SOURCES;
63  import static org.apache.maven.api.Lifecycle.Phase.TEST;
64  import static org.apache.maven.api.Lifecycle.Phase.TEST_COMPILE;
65  import static org.apache.maven.api.Lifecycle.Phase.TEST_RESOURCES;
66  import static org.apache.maven.api.Lifecycle.Phase.TEST_SOURCES;
67  import static org.apache.maven.api.Lifecycle.Phase.UNIT_TEST;
68  import static org.apache.maven.api.Lifecycle.Phase.VALIDATE;
69  import static org.apache.maven.api.Lifecycle.Phase.VERIFY;
70  import static org.apache.maven.internal.impl.Lifecycles.after;
71  import static org.apache.maven.internal.impl.Lifecycles.alias;
72  import static org.apache.maven.internal.impl.Lifecycles.dependencies;
73  import static org.apache.maven.internal.impl.Lifecycles.phase;
74  import static org.apache.maven.internal.impl.Lifecycles.plugin;
75  
76  /**
77   * TODO: this is session scoped as SPI can contribute.
78   */
79  @Named
80  @Singleton
81  public class DefaultLifecycleRegistry implements LifecycleRegistry {
82  
83      private static final String MAVEN_PLUGINS = "org.apache.maven.plugins:";
84  
85      public static final String DEFAULT_LIFECYCLE_MODELID = "org.apache.maven:maven-core:"
86              + DefaultLifecycleRegistry.class.getPackage().getImplementationVersion()
87              + ":default-lifecycle-bindings";
88  
89      public static final InputLocation DEFAULT_LIFECYCLE_INPUT_LOCATION =
90              new InputLocation(new InputSource(DEFAULT_LIFECYCLE_MODELID, null));
91  
92      private final List<LifecycleProvider> providers;
93  
94      public DefaultLifecycleRegistry() {
95          this(Collections.emptyList());
96      }
97  
98      @Inject
99      public DefaultLifecycleRegistry(List<LifecycleProvider> providers) {
100         List<LifecycleProvider> p = new ArrayList<>(providers);
101         p.add(() -> List.of(new CleanLifecycle(), new DefaultLifecycle(), new SiteLifecycle()));
102         this.providers = p;
103         // validate lifecycle
104         for (Lifecycle lifecycle : this) {
105             Set<String> set = new HashSet<>();
106             lifecycle.allPhases().forEach(phase -> {
107                 if (!set.add(phase.name())) {
108                     throw new IllegalArgumentException(
109                             "Found duplicated phase '" + phase.name() + "' in '" + lifecycle.id() + "' lifecycle");
110                 }
111             });
112         }
113     }
114 
115     @Override
116     public Iterator<Lifecycle> iterator() {
117         return stream().toList().iterator();
118     }
119 
120     @Override
121     public Stream<Lifecycle> stream() {
122         return providers.stream().map(ExtensibleEnumProvider::provides).flatMap(Collection::stream);
123     }
124 
125     @Override
126     public Optional<Lifecycle> lookup(String id) {
127         return stream().filter(lf -> Objects.equals(id, lf.id())).findAny();
128     }
129 
130     public List<String> computePhases(Lifecycle lifecycle) {
131         Graph graph = new Graph();
132         lifecycle.phases().forEach(phase -> addPhase(graph, null, null, phase));
133         List<String> allPhases = graph.visitAll();
134         Collections.reverse(allPhases);
135         List<String> computed =
136                 allPhases.stream().filter(s -> !s.startsWith("$")).collect(Collectors.toList());
137         List<String> given = lifecycle.orderedPhases().orElse(null);
138         if (given != null) {
139             if (given.size() != computed.size()) {
140                 Set<String> s1 =
141                         given.stream().filter(s -> !computed.contains(s)).collect(Collectors.toSet());
142                 Set<String> s2 =
143                         computed.stream().filter(s -> !given.contains(s)).collect(Collectors.toSet());
144                 throw new IllegalStateException(
145                         "List of phases differ in size: expected " + computed.size() + ", but received " + given.size()
146                                 + (s1.isEmpty() ? "" : ", missing " + s1)
147                                 + (s2.isEmpty() ? "" : ", unexpected " + s2));
148             }
149             return given;
150         }
151         return computed;
152     }
153 
154     private static void addPhase(
155             Graph graph, Graph.Vertex before, Graph.Vertex after, org.apache.maven.api.Lifecycle.Phase phase) {
156         Graph.Vertex ep0 = graph.addVertex("$" + phase.name());
157         Graph.Vertex ep1 = graph.addVertex("$$" + phase.name());
158         Graph.Vertex ep2 = graph.addVertex(phase.name());
159         Graph.Vertex ep3 = graph.addVertex("$$$" + phase.name());
160         graph.addEdge(ep0, ep1);
161         graph.addEdge(ep1, ep2);
162         graph.addEdge(ep2, ep3);
163         if (before != null) {
164             graph.addEdge(before, ep0);
165         }
166         if (after != null) {
167             graph.addEdge(ep3, after);
168         }
169         phase.links().forEach(link -> {
170             if (link.pointer().type() == Lifecycle.Pointer.Type.PROJECT) {
171                 if (link.kind() == Lifecycle.Link.Kind.AFTER) {
172                     graph.addEdge(graph.addVertex(link.pointer().phase()), ep0);
173                 } else {
174                     graph.addEdge(ep3, graph.addVertex("$" + link.pointer().phase()));
175                 }
176             }
177         });
178         phase.phases().forEach(child -> addPhase(graph, ep1, ep2, child));
179     }
180 
181     @Named
182     @Singleton
183     public static class LifecycleWrapperProvider implements LifecycleProvider {
184         private final PlexusContainer container;
185 
186         @Inject
187         public LifecycleWrapperProvider(PlexusContainer container) {
188             this.container = container;
189         }
190 
191         @Override
192         public Collection<Lifecycle> provides() {
193             try {
194                 Map<String, org.apache.maven.lifecycle.Lifecycle> all =
195                         container.lookupMap(org.apache.maven.lifecycle.Lifecycle.class);
196                 return all.keySet().stream()
197                         .filter(id -> !Lifecycle.CLEAN.equals(id)
198                                 && !Lifecycle.DEFAULT.equals(id)
199                                 && !Lifecycle.SITE.equals(id))
200                         .map(id -> wrap(all.get(id)))
201                         .collect(Collectors.toList());
202             } catch (ComponentLookupException e) {
203                 throw new LookupException(e);
204             }
205         }
206 
207         private Lifecycle wrap(org.apache.maven.lifecycle.Lifecycle lifecycle) {
208             return new Lifecycle() {
209                 @Override
210                 public String id() {
211                     return lifecycle.getId();
212                 }
213 
214                 @Override
215                 public Collection<Phase> phases() {
216                     List<String> names = lifecycle.getPhases();
217                     List<Phase> phases = new ArrayList<>();
218                     for (int i = 0; i < names.size(); i++) {
219                         String name = names.get(i);
220                         String prev = i > 0 ? names.get(i - 1) : null;
221                         phases.add(new Phase() {
222                             @Override
223                             public String name() {
224                                 return name;
225                             }
226 
227                             @Override
228                             public List<Phase> phases() {
229                                 return List.of();
230                             }
231 
232                             @Override
233                             public Stream<Phase> allPhases() {
234                                 return Stream.concat(
235                                         Stream.of(this), phases().stream().flatMap(Lifecycle.Phase::allPhases));
236                             }
237 
238                             @Override
239                             public List<Plugin> plugins() {
240                                 Map<String, LifecyclePhase> lfPhases = lifecycle.getDefaultLifecyclePhases();
241                                 LifecyclePhase phase = lfPhases != null ? lfPhases.get(name) : null;
242                                 if (phase != null) {
243                                     Map<String, Plugin> plugins = new LinkedHashMap<>();
244                                     DefaultPackagingRegistry.parseLifecyclePhaseDefinitions(plugins, name, phase);
245                                     return plugins.values().stream().toList();
246                                 }
247                                 return List.of();
248                             }
249 
250                             @Override
251                             public Collection<Link> links() {
252                                 if (prev == null) {
253                                     return List.of();
254                                 } else {
255                                     return List.of(new Link() {
256                                         @Override
257                                         public Kind kind() {
258                                             return Kind.AFTER;
259                                         }
260 
261                                         @Override
262                                         public Pointer pointer() {
263                                             return new Pointer() {
264                                                 @Override
265                                                 public String phase() {
266                                                     return prev;
267                                                 }
268 
269                                                 @Override
270                                                 public Type type() {
271                                                     return Type.PROJECT;
272                                                 }
273                                             };
274                                         }
275                                     });
276                                 }
277                             }
278                         });
279                     }
280                     return phases;
281                 }
282 
283                 @Override
284                 public Collection<Alias> aliases() {
285                     return Collections.emptyList();
286                 }
287             };
288         }
289     }
290 
291     static class WrappedLifecycle extends org.apache.maven.lifecycle.Lifecycle {
292         WrappedLifecycle(LifecycleRegistry registry, Lifecycle lifecycle) {
293             super(registry, lifecycle);
294         }
295     }
296 
297     abstract static class BaseLifecycleProvider implements Provider<org.apache.maven.lifecycle.Lifecycle> {
298         @Inject
299         private PlexusContainer lookup;
300 
301         private final String name;
302 
303         BaseLifecycleProvider(String name) {
304             this.name = name;
305         }
306 
307         @Override
308         public org.apache.maven.lifecycle.Lifecycle get() {
309             try {
310                 LifecycleRegistry registry = lookup.lookup(LifecycleRegistry.class);
311                 return new WrappedLifecycle(registry, registry.require(name));
312             } catch (ComponentLookupException e) {
313                 throw new LookupException(e);
314             }
315         }
316     }
317 
318     @Singleton
319     @Named(Lifecycle.CLEAN)
320     @SuppressWarnings("unused")
321     static class CleanLifecycleProvider extends BaseLifecycleProvider {
322         CleanLifecycleProvider() {
323             super(Lifecycle.CLEAN);
324         }
325     }
326 
327     @Singleton
328     @Named(Lifecycle.DEFAULT)
329     @SuppressWarnings("unused")
330     static class DefaultLifecycleProvider extends BaseLifecycleProvider {
331         DefaultLifecycleProvider() {
332             super(Lifecycle.DEFAULT);
333         }
334     }
335 
336     @Singleton
337     @Named(Lifecycle.SITE)
338     @SuppressWarnings("unused")
339     static class SiteLifecycleProvider extends BaseLifecycleProvider {
340         SiteLifecycleProvider() {
341             super(Lifecycle.SITE);
342         }
343     }
344 
345     static class CleanLifecycle implements Lifecycle {
346 
347         private static final String MAVEN_CLEAN_PLUGIN_VERSION = "3.2.0";
348 
349         @Override
350         public String id() {
351             return Lifecycle.CLEAN;
352         }
353 
354         @Override
355         public Collection<Phase> phases() {
356             return List.of(phase(
357                     Phase.CLEAN,
358                     plugin(
359                             MAVEN_PLUGINS + "maven-clean-plugin:" + MAVEN_CLEAN_PLUGIN_VERSION + ":clean",
360                             Phase.CLEAN)));
361         }
362 
363         @Override
364         public Collection<Alias> aliases() {
365             return List.of(alias("pre-clean", BEFORE + Phase.CLEAN), alias("post-clean", AFTER + Phase.CLEAN));
366         }
367     }
368 
369     static class DefaultLifecycle implements Lifecycle {
370         @Override
371         public String id() {
372             return Lifecycle.DEFAULT;
373         }
374 
375         @Override
376         public Collection<Phase> phases() {
377             return List.of(phase(
378                     "all",
379                     phase(INITIALIZE, phase(VALIDATE)),
380                     phase(
381                             BUILD,
382                             after(VALIDATE),
383                             phase(SOURCES),
384                             phase(RESOURCES),
385                             phase(COMPILE, after(SOURCES), dependencies(COMPILE, READY)),
386                             phase(READY, after(COMPILE), after(RESOURCES)),
387                             phase(PACKAGE, after(READY), dependencies("runtime", PACKAGE))),
388                     phase(
389                             VERIFY,
390                             after(VALIDATE),
391                             phase(
392                                     UNIT_TEST,
393                                     phase(TEST_SOURCES),
394                                     phase(TEST_RESOURCES),
395                                     phase(
396                                             TEST_COMPILE,
397                                             after(TEST_SOURCES),
398                                             after(READY),
399                                             dependencies("test-only", READY)),
400                                     phase(
401                                             TEST,
402                                             after(TEST_COMPILE),
403                                             after(TEST_RESOURCES),
404                                             dependencies("test", READY))),
405                             phase(INTEGRATION_TEST)),
406                     phase(INSTALL, after(PACKAGE)),
407                     phase(DEPLOY, after(PACKAGE))));
408         }
409 
410         @Override
411         public Collection<Alias> aliases() {
412             return List.of(
413                     alias("generate-sources", SOURCES),
414                     alias("process-sources", AFTER + SOURCES),
415                     alias("generate-resources", RESOURCES),
416                     alias("process-resources", AFTER + RESOURCES),
417                     alias("process-classes", AFTER + COMPILE),
418                     alias("generate-test-sources", TEST_SOURCES),
419                     alias("process-test-sources", AFTER + TEST_SOURCES),
420                     alias("generate-test-resources", TEST_RESOURCES),
421                     alias("process-test-resources", AFTER + TEST_RESOURCES),
422                     alias("process-test-classes", AFTER + TEST_COMPILE),
423                     alias("prepare-package", BEFORE + PACKAGE),
424                     alias("pre-integration-test", BEFORE + INTEGRATION_TEST),
425                     alias("post-integration-test", AFTER + INTEGRATION_TEST));
426         }
427 
428         @Override
429         public Optional<List<String>> orderedPhases() {
430             return Optional.of(Arrays.asList(
431                     VALIDATE,
432                     INITIALIZE,
433                     // "generate-sources",
434                     SOURCES,
435                     // "process-sources",
436                     // "generate-resources",
437                     RESOURCES,
438                     // "process-resources",
439                     COMPILE,
440                     // "process-classes",
441                     READY,
442                     // "generate-test-sources",
443                     TEST_SOURCES,
444                     // "process-test-sources",
445                     // "generate-test-resources",
446                     TEST_RESOURCES,
447                     // "process-test-resources",
448                     TEST_COMPILE,
449                     // "process-test-classes",
450                     TEST,
451                     UNIT_TEST,
452                     // "prepare-package",
453                     PACKAGE,
454                     BUILD,
455                     // "pre-integration-test",
456                     INTEGRATION_TEST,
457                     // "post-integration-test",
458                     VERIFY,
459                     INSTALL,
460                     DEPLOY,
461                     "all"));
462         }
463     }
464 
465     static class SiteLifecycle implements Lifecycle {
466 
467         private static final String MAVEN_SITE_PLUGIN_VERSION = "3.12.1";
468         private static final String MAVEN_SITE_PLUGIN =
469                 MAVEN_PLUGINS + "maven-site-plugin:" + MAVEN_SITE_PLUGIN_VERSION + ":";
470         private static final String PHASE_SITE = "site";
471         private static final String PHASE_SITE_DEPLOY = "site-deploy";
472 
473         @Override
474         public String id() {
475             return Lifecycle.SITE;
476         }
477 
478         @Override
479         public Collection<Phase> phases() {
480             return List.of(
481                     phase(PHASE_SITE, plugin(MAVEN_SITE_PLUGIN + "site", PHASE_SITE)),
482                     phase(
483                             PHASE_SITE_DEPLOY,
484                             after(PHASE_SITE),
485                             plugin(MAVEN_SITE_PLUGIN + "deploy", PHASE_SITE_DEPLOY)));
486         }
487 
488         @Override
489         public Collection<Alias> aliases() {
490             return List.of(alias("pre-site", BEFORE + PHASE_SITE), alias("post-site", AFTER + PHASE_SITE));
491         }
492     }
493 }