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.lifecycle.internal;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.util.ArrayList;
26  import java.util.Comparator;
27  import java.util.LinkedHashMap;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.TreeMap;
31  import java.util.stream.Collectors;
32  
33  import org.apache.maven.execution.MavenSession;
34  import org.apache.maven.lifecycle.Lifecycle;
35  import org.apache.maven.lifecycle.LifecycleMappingDelegate;
36  import org.apache.maven.model.Plugin;
37  import org.apache.maven.model.PluginExecution;
38  import org.apache.maven.plugin.BuildPluginManager;
39  import org.apache.maven.plugin.InvalidPluginDescriptorException;
40  import org.apache.maven.plugin.MojoExecution;
41  import org.apache.maven.plugin.MojoNotFoundException;
42  import org.apache.maven.plugin.PluginDescriptorParsingException;
43  import org.apache.maven.plugin.PluginNotFoundException;
44  import org.apache.maven.plugin.PluginResolutionException;
45  import org.apache.maven.plugin.descriptor.MojoDescriptor;
46  import org.apache.maven.project.MavenProject;
47  
48  /**
49   * Lifecycle mapping delegate component interface. Calculates project build execution plan given {@link Lifecycle} and
50   * lifecycle phase. Standard lifecycles use plugin execution {@code <phase>} or mojo default lifecycle phase to
51   * calculate the execution plan, but custom lifecycles can use alternative mapping strategies.
52   */
53  @Named(DefaultLifecycleMappingDelegate.HINT)
54  @Singleton
55  public class DefaultLifecycleMappingDelegate implements LifecycleMappingDelegate {
56      public static final String HINT = "default";
57  
58      private final BuildPluginManager pluginManager;
59  
60      @Inject
61      public DefaultLifecycleMappingDelegate(BuildPluginManager pluginManager) {
62          this.pluginManager = pluginManager;
63      }
64  
65      @Override
66      public Map<String, List<MojoExecution>> calculateLifecycleMappings(
67              MavenSession session, MavenProject project, Lifecycle lifecycle, String lifecyclePhase)
68              throws PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
69                      MojoNotFoundException, InvalidPluginDescriptorException {
70          /*
71           * Initialize mapping from lifecycle phase to bound mojos. The key set of this map denotes the phases the caller
72           * is interested in, i.e. all phases up to and including the specified phase.
73           */
74  
75          Map<String, Map<PhaseId, List<MojoExecution>>> mappings =
76                  new TreeMap<>(new PhaseComparator(lifecycle.getPhases()));
77  
78          Map<String, String> aliases = lifecycle.getDelegate().aliases().stream()
79                  .collect(Collectors.toMap(a -> a.v3Phase(), a -> a.v4Phase()));
80  
81          if (aliases.containsKey(lifecyclePhase)) {
82              lifecyclePhase = PhaseId.of(aliases.get(lifecyclePhase)).phase();
83          }
84  
85          boolean passed = false;
86          for (String phase : lifecycle.getPhases()) {
87              boolean include = true;
88              if (phase.equals(lifecyclePhase)) {
89                  passed = true;
90              } else if (passed) {
91                  if (phase.startsWith(org.apache.maven.api.Lifecycle.AFTER)) {
92                      String realPhase = phase.substring(org.apache.maven.api.Lifecycle.AFTER.length());
93                      include = mappings.containsKey(org.apache.maven.api.Lifecycle.BEFORE + realPhase);
94                  } else {
95                      include = false;
96                  }
97              }
98              if (include) {
99                  Map<PhaseId, List<MojoExecution>> phaseBindings = new TreeMap<>(
100                         Comparator.comparing(PhaseId::toString, new PhaseComparator(lifecycle.getPhases())));
101                 mappings.put(phase, phaseBindings);
102             }
103         }
104 
105         /*
106          * Grab plugin executions that are bound to the selected lifecycle phases from project. The effective model of
107          * the project already contains the plugin executions induced by the project's packaging type. Remember, all
108          * phases of interest and only those are in the lifecycle mapping, if a phase has no value in the map, we are
109          * not interested in any of the executions bound to it.
110          */
111 
112         for (Plugin plugin : project.getBuild().getPlugins()) {
113             for (PluginExecution execution : plugin.getExecutions()) {
114                 // if the phase is specified then I don't have to go fetch the plugin yet and pull it down
115                 // to examine the phase it is associated to.
116                 String phase = execution.getPhase();
117                 if (aliases.containsKey(phase)) {
118                     phase = aliases.get(phase);
119                 }
120                 if (phase != null) {
121                     Map<PhaseId, List<MojoExecution>> phaseBindings = getPhaseBindings(mappings, phase);
122                     if (phaseBindings != null) {
123                         for (String goal : execution.getGoals()) {
124                             MojoExecution mojoExecution = new MojoExecution(plugin, goal, execution.getId());
125                             mojoExecution.setLifecyclePhase(phase);
126                             PhaseId phaseId = PhaseId.of(phase);
127                             if (phaseId.priority() == 0) {
128                                 phaseId = PhaseId.of(phase + "[" + execution.getPriority() + "]");
129                             }
130                             addMojoExecution(phaseBindings, mojoExecution, phaseId);
131                         }
132                     }
133                 }
134                 // if not then I need to grab the mojo descriptor and look at the phase that is specified
135                 else {
136                     for (String goal : execution.getGoals()) {
137                         MojoDescriptor mojoDescriptor = pluginManager.getMojoDescriptor(
138                                 plugin, goal, project.getRemotePluginRepositories(), session.getRepositorySession());
139 
140                         phase = mojoDescriptor.getPhase();
141                         if (aliases.containsKey(phase)) {
142                             phase = aliases.get(phase);
143                         }
144                         Map<PhaseId, List<MojoExecution>> phaseBindings = getPhaseBindings(mappings, phase);
145                         if (phaseBindings != null) {
146                             MojoExecution mojoExecution = new MojoExecution(mojoDescriptor, execution.getId());
147                             mojoExecution.setLifecyclePhase(phase);
148                             PhaseId phaseId = PhaseId.of(phase + "[" + execution.getPriority() + "]");
149                             addMojoExecution(phaseBindings, mojoExecution, phaseId);
150                         }
151                     }
152                 }
153             }
154         }
155 
156         Map<String, List<MojoExecution>> lifecycleMappings = new LinkedHashMap<>();
157 
158         for (Map.Entry<String, Map<PhaseId, List<MojoExecution>>> entry : mappings.entrySet()) {
159             List<MojoExecution> mojoExecutions = new ArrayList<>();
160 
161             for (List<MojoExecution> executions : entry.getValue().values()) {
162                 mojoExecutions.addAll(executions);
163             }
164 
165             lifecycleMappings.put(entry.getKey(), mojoExecutions);
166         }
167 
168         return lifecycleMappings;
169     }
170 
171     private Map<PhaseId, List<MojoExecution>> getPhaseBindings(
172             Map<String, Map<PhaseId, List<MojoExecution>>> mappings, String phase) {
173         if (phase != null) {
174             PhaseId id = PhaseId.of(phase);
175             return mappings.get(id.executionPoint().prefix() + id.phase());
176         }
177         return null;
178     }
179 
180     private void addMojoExecution(
181             Map<PhaseId, List<MojoExecution>> phaseBindings, MojoExecution mojoExecution, PhaseId phaseId) {
182         List<MojoExecution> mojoExecutions = phaseBindings.computeIfAbsent(phaseId, k -> new ArrayList<>());
183 
184         mojoExecutions.add(mojoExecution);
185     }
186 }