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 java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.apache.maven.execution.MavenSession;
31  import org.apache.maven.lifecycle.DefaultLifecycles;
32  import org.apache.maven.lifecycle.Lifecycle;
33  import org.apache.maven.lifecycle.LifecycleMappingDelegate;
34  import org.apache.maven.lifecycle.LifecycleNotFoundException;
35  import org.apache.maven.lifecycle.LifecyclePhaseNotFoundException;
36  import org.apache.maven.lifecycle.MavenExecutionPlan;
37  import org.apache.maven.lifecycle.MojoExecutionConfigurator;
38  import org.apache.maven.lifecycle.internal.builder.BuilderCommon;
39  import org.apache.maven.plugin.BuildPluginManager;
40  import org.apache.maven.plugin.InvalidPluginDescriptorException;
41  import org.apache.maven.plugin.MojoExecution;
42  import org.apache.maven.plugin.MojoNotFoundException;
43  import org.apache.maven.plugin.PluginDescriptorParsingException;
44  import org.apache.maven.plugin.PluginNotFoundException;
45  import org.apache.maven.plugin.PluginResolutionException;
46  import org.apache.maven.plugin.descriptor.MojoDescriptor;
47  import org.apache.maven.plugin.descriptor.Parameter;
48  import org.apache.maven.plugin.descriptor.PluginDescriptor;
49  import org.apache.maven.plugin.lifecycle.Execution;
50  import org.apache.maven.plugin.lifecycle.Phase;
51  import org.apache.maven.plugin.prefix.NoPluginFoundForPrefixException;
52  import org.apache.maven.plugin.version.PluginVersionResolutionException;
53  import org.apache.maven.plugin.version.PluginVersionResolver;
54  import org.apache.maven.project.MavenProject;
55  import org.codehaus.plexus.component.annotations.Component;
56  import org.codehaus.plexus.component.annotations.Requirement;
57  import org.codehaus.plexus.util.StringUtils;
58  import org.codehaus.plexus.util.xml.Xpp3Dom;
59  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
60  
61  /**
62   * <strong>NOTE:</strong> This class is not part of any public api and can be changed or deleted without prior notice.
63   *
64   * @since 3.0
65   * @author Benjamin Bentmann
66   * @author Kristian Rosenvold (Extract class)
67   */
68  @Component(role = LifecycleExecutionPlanCalculator.class)
69  public class DefaultLifecycleExecutionPlanCalculator implements LifecycleExecutionPlanCalculator {
70      @Requirement
71      private PluginVersionResolver pluginVersionResolver;
72  
73      @Requirement
74      private BuildPluginManager pluginManager;
75  
76      @Requirement
77      private DefaultLifecycles defaultLifeCycles;
78  
79      @Requirement
80      private MojoDescriptorCreator mojoDescriptorCreator;
81  
82      @Requirement
83      private LifecyclePluginResolver lifecyclePluginResolver;
84  
85      @Requirement(hint = DefaultLifecycleMappingDelegate.HINT)
86      private LifecycleMappingDelegate standardDelegate;
87  
88      @Requirement
89      private Map<String, LifecycleMappingDelegate> delegates;
90  
91      @Requirement
92      private Map<String, MojoExecutionConfigurator> mojoExecutionConfigurators;
93  
94      @SuppressWarnings({"UnusedDeclaration"})
95      public DefaultLifecycleExecutionPlanCalculator() {}
96  
97      // Only used for testing
98      public DefaultLifecycleExecutionPlanCalculator(
99              BuildPluginManager pluginManager,
100             DefaultLifecycles defaultLifeCycles,
101             MojoDescriptorCreator mojoDescriptorCreator,
102             LifecyclePluginResolver lifecyclePluginResolver) {
103         this.pluginManager = pluginManager;
104         this.defaultLifeCycles = defaultLifeCycles;
105         this.mojoDescriptorCreator = mojoDescriptorCreator;
106         this.lifecyclePluginResolver = lifecyclePluginResolver;
107         this.mojoExecutionConfigurators =
108                 Collections.singletonMap("default", (MojoExecutionConfigurator) new DefaultMojoExecutionConfigurator());
109     }
110 
111     @Override
112     public MavenExecutionPlan calculateExecutionPlan(
113             MavenSession session, MavenProject project, List<Object> tasks, boolean setup)
114             throws PluginNotFoundException, PluginResolutionException, LifecyclePhaseNotFoundException,
115                     PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException,
116                     NoPluginFoundForPrefixException, LifecycleNotFoundException, PluginVersionResolutionException {
117         lifecyclePluginResolver.resolveMissingPluginVersions(project, session);
118 
119         final List<MojoExecution> executions = calculateMojoExecutions(session, project, tasks);
120 
121         if (setup) {
122             setupMojoExecutions(session, project, executions);
123         }
124 
125         final List<ExecutionPlanItem> planItem = ExecutionPlanItem.createExecutionPlanItems(project, executions);
126 
127         return new MavenExecutionPlan(planItem, defaultLifeCycles);
128     }
129 
130     @Override
131     public MavenExecutionPlan calculateExecutionPlan(MavenSession session, MavenProject project, List<Object> tasks)
132             throws PluginNotFoundException, PluginResolutionException, LifecyclePhaseNotFoundException,
133                     PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException,
134                     NoPluginFoundForPrefixException, LifecycleNotFoundException, PluginVersionResolutionException {
135         return calculateExecutionPlan(session, project, tasks, true);
136     }
137 
138     private void setupMojoExecutions(MavenSession session, MavenProject project, List<MojoExecution> mojoExecutions)
139             throws PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
140                     MojoNotFoundException, InvalidPluginDescriptorException, NoPluginFoundForPrefixException,
141                     LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException {
142         for (MojoExecution mojoExecution : mojoExecutions) {
143             setupMojoExecution(session, project, mojoExecution);
144         }
145     }
146 
147     @Override
148     public void setupMojoExecution(MavenSession session, MavenProject project, MojoExecution mojoExecution)
149             throws PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
150                     MojoNotFoundException, InvalidPluginDescriptorException, NoPluginFoundForPrefixException,
151                     LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException {
152         MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
153 
154         if (mojoDescriptor == null) {
155             mojoDescriptor = pluginManager.getMojoDescriptor(
156                     mojoExecution.getPlugin(),
157                     mojoExecution.getGoal(),
158                     project.getRemotePluginRepositories(),
159                     session.getRepositorySession());
160 
161             mojoExecution.setMojoDescriptor(mojoDescriptor);
162         }
163 
164         mojoExecutionConfigurator(mojoExecution)
165                 .configure(project, mojoExecution, MojoExecution.Source.CLI.equals(mojoExecution.getSource()));
166 
167         finalizeMojoConfiguration(mojoExecution);
168 
169         calculateForkedExecutions(mojoExecution, session, project, new HashSet<MojoDescriptor>());
170     }
171 
172     public List<MojoExecution> calculateMojoExecutions(MavenSession session, MavenProject project, List<Object> tasks)
173             throws PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
174                     MojoNotFoundException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
175                     PluginVersionResolutionException, LifecyclePhaseNotFoundException {
176         final List<MojoExecution> mojoExecutions = new ArrayList<>();
177 
178         for (Object task : tasks) {
179             if (task instanceof GoalTask) {
180                 String pluginGoal = ((GoalTask) task).pluginGoal;
181 
182                 String executionId = "default-cli";
183                 int executionIdx = pluginGoal.indexOf('@');
184                 if (executionIdx > 0) {
185                     executionId = pluginGoal.substring(executionIdx + 1);
186                 }
187 
188                 MojoDescriptor mojoDescriptor = mojoDescriptorCreator.getMojoDescriptor(pluginGoal, session, project);
189 
190                 MojoExecution mojoExecution = new MojoExecution(mojoDescriptor, executionId, MojoExecution.Source.CLI);
191 
192                 mojoExecutions.add(mojoExecution);
193             } else if (task instanceof LifecycleTask) {
194                 String lifecyclePhase = ((LifecycleTask) task).getLifecyclePhase();
195 
196                 Map<String, List<MojoExecution>> phaseToMojoMapping =
197                         calculateLifecycleMappings(session, project, lifecyclePhase);
198 
199                 for (List<MojoExecution> mojoExecutionsFromLifecycle : phaseToMojoMapping.values()) {
200                     mojoExecutions.addAll(mojoExecutionsFromLifecycle);
201                 }
202             } else {
203                 throw new IllegalStateException("unexpected task " + task);
204             }
205         }
206         return mojoExecutions;
207     }
208 
209     private Map<String, List<MojoExecution>> calculateLifecycleMappings(
210             MavenSession session, MavenProject project, String lifecyclePhase)
211             throws LifecyclePhaseNotFoundException, PluginNotFoundException, PluginResolutionException,
212                     PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException {
213         /*
214          * Determine the lifecycle that corresponds to the given phase.
215          */
216 
217         Lifecycle lifecycle = defaultLifeCycles.get(lifecyclePhase);
218 
219         if (lifecycle == null) {
220             throw new LifecyclePhaseNotFoundException(
221                     "Unknown lifecycle phase \"" + lifecyclePhase
222                             + "\". You must specify a valid lifecycle phase"
223                             + " or a goal in the format <plugin-prefix>:<goal> or"
224                             + " <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: "
225                             + defaultLifeCycles.getLifecyclePhaseList() + ".",
226                     lifecyclePhase);
227         }
228 
229         LifecycleMappingDelegate delegate;
230         if (Arrays.binarySearch(DefaultLifecycles.STANDARD_LIFECYCLES, lifecycle.getId()) >= 0) {
231             delegate = standardDelegate;
232         } else {
233             delegate = delegates.get(lifecycle.getId());
234             if (delegate == null) {
235                 delegate = standardDelegate;
236             }
237         }
238 
239         return delegate.calculateLifecycleMappings(session, project, lifecycle, lifecyclePhase);
240     }
241 
242     /**
243      * Post-processes the effective configuration for the specified mojo execution. This step discards all parameters
244      * from the configuration that are not applicable to the mojo and injects the default values for any missing
245      * parameters.
246      *
247      * @param mojoExecution The mojo execution whose configuration should be finalized, must not be {@code null}.
248      */
249     private void finalizeMojoConfiguration(MojoExecution mojoExecution) {
250         MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
251 
252         Xpp3Dom executionConfiguration = mojoExecution.getConfiguration();
253         if (executionConfiguration == null) {
254             executionConfiguration = new Xpp3Dom("configuration");
255         }
256 
257         Xpp3Dom defaultConfiguration = getMojoConfiguration(mojoDescriptor);
258 
259         Xpp3Dom finalConfiguration = new Xpp3Dom("configuration");
260 
261         if (mojoDescriptor.getParameters() != null) {
262             for (Parameter parameter : mojoDescriptor.getParameters()) {
263                 Xpp3Dom parameterConfiguration = executionConfiguration.getChild(parameter.getName());
264 
265                 if (parameterConfiguration == null) {
266                     parameterConfiguration = executionConfiguration.getChild(parameter.getAlias());
267                 }
268 
269                 Xpp3Dom parameterDefaults = defaultConfiguration.getChild(parameter.getName());
270 
271                 parameterConfiguration = Xpp3Dom.mergeXpp3Dom(parameterConfiguration, parameterDefaults, Boolean.TRUE);
272 
273                 if (parameterConfiguration != null) {
274                     parameterConfiguration = new Xpp3Dom(parameterConfiguration, parameter.getName());
275 
276                     if (StringUtils.isEmpty(parameterConfiguration.getAttribute("implementation"))
277                             && StringUtils.isNotEmpty(parameter.getImplementation())) {
278                         parameterConfiguration.setAttribute("implementation", parameter.getImplementation());
279                     }
280 
281                     finalConfiguration.addChild(parameterConfiguration);
282                 }
283             }
284         }
285 
286         mojoExecution.setConfiguration(finalConfiguration);
287     }
288 
289     private Xpp3Dom getMojoConfiguration(MojoDescriptor mojoDescriptor) {
290         return MojoDescriptorCreator.convert(mojoDescriptor);
291     }
292 
293     @Override
294     public void calculateForkedExecutions(MojoExecution mojoExecution, MavenSession session)
295             throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException,
296                     PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
297                     LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException {
298         calculateForkedExecutions(mojoExecution, session, session.getCurrentProject(), new HashSet<MojoDescriptor>());
299     }
300 
301     private void calculateForkedExecutions(
302             MojoExecution mojoExecution,
303             MavenSession session,
304             MavenProject project,
305             Collection<MojoDescriptor> alreadyForkedExecutions)
306             throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException,
307                     PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
308                     LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException {
309         MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
310 
311         if (!mojoDescriptor.isForking()) {
312             return;
313         }
314 
315         if (!alreadyForkedExecutions.add(mojoDescriptor)) {
316             return;
317         }
318 
319         List<MavenProject> forkedProjects =
320                 LifecycleDependencyResolver.getProjects(project, session, mojoDescriptor.isAggregator());
321 
322         for (MavenProject forkedProject : forkedProjects) {
323             if (forkedProject != project) {
324                 lifecyclePluginResolver.resolveMissingPluginVersions(forkedProject, session);
325             }
326 
327             List<MojoExecution> forkedExecutions;
328 
329             if (StringUtils.isNotEmpty(mojoDescriptor.getExecutePhase())) {
330                 forkedExecutions =
331                         calculateForkedLifecycle(mojoExecution, session, forkedProject, alreadyForkedExecutions);
332             } else {
333                 forkedExecutions = calculateForkedGoal(mojoExecution, session, forkedProject, alreadyForkedExecutions);
334             }
335 
336             mojoExecution.setForkedExecutions(BuilderCommon.getKey(forkedProject), forkedExecutions);
337         }
338 
339         alreadyForkedExecutions.remove(mojoDescriptor);
340     }
341 
342     private List<MojoExecution> calculateForkedLifecycle(
343             MojoExecution mojoExecution,
344             MavenSession session,
345             MavenProject project,
346             Collection<MojoDescriptor> alreadyForkedExecutions)
347             throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException,
348                     PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
349                     LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException {
350         MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
351 
352         String forkedPhase = mojoDescriptor.getExecutePhase();
353 
354         Map<String, List<MojoExecution>> lifecycleMappings = calculateLifecycleMappings(session, project, forkedPhase);
355 
356         for (List<MojoExecution> forkedExecutions : lifecycleMappings.values()) {
357             for (MojoExecution forkedExecution : forkedExecutions) {
358                 if (forkedExecution.getMojoDescriptor() == null) {
359                     MojoDescriptor forkedMojoDescriptor = pluginManager.getMojoDescriptor(
360                             forkedExecution.getPlugin(),
361                             forkedExecution.getGoal(),
362                             project.getRemotePluginRepositories(),
363                             session.getRepositorySession());
364 
365                     forkedExecution.setMojoDescriptor(forkedMojoDescriptor);
366                 }
367 
368                 mojoExecutionConfigurator(forkedExecution).configure(project, forkedExecution, false);
369             }
370         }
371 
372         injectLifecycleOverlay(lifecycleMappings, mojoExecution, session, project);
373 
374         List<MojoExecution> mojoExecutions = new ArrayList<>();
375 
376         for (List<MojoExecution> forkedExecutions : lifecycleMappings.values()) {
377             for (MojoExecution forkedExecution : forkedExecutions) {
378                 if (!alreadyForkedExecutions.contains(forkedExecution.getMojoDescriptor())) {
379                     finalizeMojoConfiguration(forkedExecution);
380 
381                     calculateForkedExecutions(forkedExecution, session, project, alreadyForkedExecutions);
382 
383                     mojoExecutions.add(forkedExecution);
384                 }
385             }
386         }
387 
388         return mojoExecutions;
389     }
390 
391     private void injectLifecycleOverlay(
392             Map<String, List<MojoExecution>> lifecycleMappings,
393             MojoExecution mojoExecution,
394             MavenSession session,
395             MavenProject project)
396             throws PluginDescriptorParsingException, LifecycleNotFoundException, MojoNotFoundException,
397                     PluginNotFoundException, PluginResolutionException, NoPluginFoundForPrefixException,
398                     InvalidPluginDescriptorException, PluginVersionResolutionException {
399         MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
400 
401         PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
402 
403         String forkedLifecycle = mojoDescriptor.getExecuteLifecycle();
404 
405         if (StringUtils.isEmpty(forkedLifecycle)) {
406             return;
407         }
408 
409         org.apache.maven.plugin.lifecycle.Lifecycle lifecycleOverlay;
410 
411         try {
412             lifecycleOverlay = pluginDescriptor.getLifecycleMapping(forkedLifecycle);
413         } catch (IOException | XmlPullParserException e) {
414             throw new PluginDescriptorParsingException(pluginDescriptor.getPlugin(), pluginDescriptor.getSource(), e);
415         }
416 
417         if (lifecycleOverlay == null) {
418             throw new LifecycleNotFoundException(forkedLifecycle);
419         }
420 
421         for (Phase phase : lifecycleOverlay.getPhases()) {
422             List<MojoExecution> forkedExecutions = lifecycleMappings.get(phase.getId());
423 
424             if (forkedExecutions != null) {
425                 for (Execution execution : phase.getExecutions()) {
426                     for (String goal : execution.getGoals()) {
427                         MojoDescriptor forkedMojoDescriptor;
428 
429                         if (goal.indexOf(':') < 0) {
430                             forkedMojoDescriptor = pluginDescriptor.getMojo(goal);
431                             if (forkedMojoDescriptor == null) {
432                                 throw new MojoNotFoundException(goal, pluginDescriptor);
433                             }
434                         } else {
435                             forkedMojoDescriptor = mojoDescriptorCreator.getMojoDescriptor(goal, session, project);
436                         }
437 
438                         MojoExecution forkedExecution =
439                                 new MojoExecution(forkedMojoDescriptor, mojoExecution.getExecutionId());
440 
441                         Xpp3Dom forkedConfiguration = (Xpp3Dom) execution.getConfiguration();
442 
443                         forkedExecution.setConfiguration(forkedConfiguration);
444 
445                         mojoExecutionConfigurator(forkedExecution).configure(project, forkedExecution, true);
446 
447                         forkedExecutions.add(forkedExecution);
448                     }
449                 }
450 
451                 Xpp3Dom phaseConfiguration = (Xpp3Dom) phase.getConfiguration();
452 
453                 if (phaseConfiguration != null) {
454                     for (MojoExecution forkedExecution : forkedExecutions) {
455                         Xpp3Dom forkedConfiguration = forkedExecution.getConfiguration();
456 
457                         forkedConfiguration = Xpp3Dom.mergeXpp3Dom(phaseConfiguration, forkedConfiguration);
458 
459                         forkedExecution.setConfiguration(forkedConfiguration);
460                     }
461                 }
462             }
463         }
464     }
465 
466     // org.apache.maven.plugins:maven-remote-resources-plugin:1.0:process
467     // TODO take repo mans into account as one may be aggregating prefixes of many
468     // TODO collect at the root of the repository, read the one at the root, and fetch remote if something is missing
469     // or the user forces the issue
470 
471     private List<MojoExecution> calculateForkedGoal(
472             MojoExecution mojoExecution,
473             MavenSession session,
474             MavenProject project,
475             Collection<MojoDescriptor> alreadyForkedExecutions)
476             throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException,
477                     PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
478                     LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException {
479         MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
480 
481         PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
482 
483         String forkedGoal = mojoDescriptor.getExecuteGoal();
484 
485         MojoDescriptor forkedMojoDescriptor = pluginDescriptor.getMojo(forkedGoal);
486         if (forkedMojoDescriptor == null) {
487             throw new MojoNotFoundException(forkedGoal, pluginDescriptor);
488         }
489 
490         if (alreadyForkedExecutions.contains(forkedMojoDescriptor)) {
491             return Collections.emptyList();
492         }
493 
494         MojoExecution forkedExecution = new MojoExecution(forkedMojoDescriptor, forkedGoal);
495 
496         mojoExecutionConfigurator(forkedExecution).configure(project, forkedExecution, true);
497 
498         finalizeMojoConfiguration(forkedExecution);
499 
500         calculateForkedExecutions(forkedExecution, session, project, alreadyForkedExecutions);
501 
502         return Collections.singletonList(forkedExecution);
503     }
504 
505     private MojoExecutionConfigurator mojoExecutionConfigurator(MojoExecution mojoExecution) {
506         String configuratorId = mojoExecution.getMojoDescriptor().getComponentConfigurator();
507         if (configuratorId == null) {
508             configuratorId = "default";
509         }
510 
511         MojoExecutionConfigurator mojoExecutionConfigurator = mojoExecutionConfigurators.get(configuratorId);
512 
513         if (mojoExecutionConfigurator == null) {
514             //
515             // The plugin has a custom component configurator but does not have a custom mojo execution configurator
516             // so fall back to the default mojo execution configurator.
517             //
518             mojoExecutionConfigurator = mojoExecutionConfigurators.get("default");
519         }
520         return mojoExecutionConfigurator;
521     }
522 }