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