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.concurrent;
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.List;
27  import java.util.stream.Collectors;
28  import java.util.stream.Stream;
29  
30  import org.apache.maven.api.Lifecycle;
31  import org.apache.maven.execution.ExecutionEvent;
32  import org.apache.maven.execution.MavenSession;
33  import org.apache.maven.lifecycle.DefaultLifecycles;
34  import org.apache.maven.lifecycle.MissingProjectException;
35  import org.apache.maven.lifecycle.NoGoalSpecifiedException;
36  import org.apache.maven.lifecycle.internal.ExecutionEventCatapult;
37  import org.apache.maven.lifecycle.internal.GoalTask;
38  import org.apache.maven.lifecycle.internal.LifecyclePluginResolver;
39  import org.apache.maven.lifecycle.internal.LifecycleStarter;
40  import org.apache.maven.lifecycle.internal.LifecycleTask;
41  import org.apache.maven.lifecycle.internal.MojoDescriptorCreator;
42  import org.apache.maven.lifecycle.internal.PhaseId;
43  import org.apache.maven.lifecycle.internal.ReactorBuildStatus;
44  import org.apache.maven.lifecycle.internal.ReactorContext;
45  import org.apache.maven.lifecycle.internal.TaskSegment;
46  import org.apache.maven.plugin.descriptor.MojoDescriptor;
47  import org.apache.maven.project.MavenProject;
48  import org.slf4j.Logger;
49  import org.slf4j.LoggerFactory;
50  
51  import static java.util.Objects.requireNonNull;
52  
53  /**
54   * Starts the build life cycle
55   */
56  @Named("concurrent")
57  @Singleton
58  public class ConcurrentLifecycleStarter implements LifecycleStarter {
59      private final Logger logger = LoggerFactory.getLogger(getClass());
60  
61      private final ExecutionEventCatapult eventCatapult;
62      private final DefaultLifecycles defaultLifeCycles;
63      private final BuildPlanExecutor executor;
64      private final LifecyclePluginResolver lifecyclePluginResolver;
65      private final MojoDescriptorCreator mojoDescriptorCreator;
66  
67      @Inject
68      public ConcurrentLifecycleStarter(
69              ExecutionEventCatapult eventCatapult,
70              DefaultLifecycles defaultLifeCycles,
71              BuildPlanExecutor executor,
72              LifecyclePluginResolver lifecyclePluginResolver,
73              MojoDescriptorCreator mojoDescriptorCreator) {
74          this.eventCatapult = eventCatapult;
75          this.defaultLifeCycles = defaultLifeCycles;
76          this.executor = executor;
77          this.lifecyclePluginResolver = lifecyclePluginResolver;
78          this.mojoDescriptorCreator = mojoDescriptorCreator;
79      }
80  
81      public void execute(MavenSession session) {
82          eventCatapult.fire(ExecutionEvent.Type.SessionStarted, session, null);
83  
84          try {
85              if (requiresProject(session) && projectIsNotPresent(session)) {
86                  throw new MissingProjectException("The goal you specified requires a project to execute"
87                          + " but there is no POM in this directory (" + session.getTopDirectory() + ")."
88                          + " Please verify you invoked Maven from the correct directory.");
89              }
90  
91              List<TaskSegment> taskSegments = calculateTaskSegments(session);
92              if (taskSegments.isEmpty()) {
93                  throw new NoGoalSpecifiedException("No goals have been specified for this build."
94                          + " You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or"
95                          + " <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>."
96                          + " Available lifecycle phases are: " + defaultLifeCycles.getLifecyclePhaseList() + ".");
97              }
98  
99              int degreeOfConcurrency = session.getRequest().getDegreeOfConcurrency();
100             if (degreeOfConcurrency > 1) {
101                 logger.info("");
102                 logger.info(String.format(
103                         "Using the %s implementation with a thread count of %d",
104                         executor.getClass().getSimpleName(), degreeOfConcurrency));
105             }
106 
107             ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
108             ReactorBuildStatus reactorBuildStatus = new ReactorBuildStatus(session.getProjectDependencyGraph());
109             ReactorContext reactorContext =
110                     new ReactorContext(session.getResult(), oldContextClassLoader, reactorBuildStatus);
111             executor.execute(session, reactorContext, taskSegments);
112 
113         } catch (Exception e) {
114             session.getResult().addException(e);
115         } finally {
116             eventCatapult.fire(ExecutionEvent.Type.SessionEnded, session, null);
117         }
118     }
119 
120     public List<TaskSegment> calculateTaskSegments(MavenSession session) throws Exception {
121 
122         MavenProject rootProject = session.getTopLevelProject();
123 
124         List<String> tasks = requireNonNull(session.getGoals()); // session never returns null, but empty list
125 
126         if (tasks.isEmpty()
127                 && (rootProject.getDefaultGoal() != null
128                         && !rootProject.getDefaultGoal().isEmpty())) {
129             tasks = Stream.of(rootProject.getDefaultGoal().split("\\s+"))
130                     .filter(g -> !g.isEmpty())
131                     .collect(Collectors.toList());
132         }
133 
134         return calculateTaskSegments(session, tasks);
135     }
136 
137     public List<TaskSegment> calculateTaskSegments(MavenSession session, List<String> tasks) throws Exception {
138         List<TaskSegment> taskSegments = new ArrayList<>(tasks.size());
139 
140         TaskSegment currentSegment = null;
141 
142         for (String task : tasks) {
143             if (isBeforeOrAfterPhase(task)) {
144                 String prevTask = task;
145                 task = PhaseId.of(task).phase();
146                 logger.warn("Illegal call to phase '{}'. The main phase '{}' will be used instead.", prevTask, task);
147             }
148             if (isGoalSpecification(task)) {
149                 // "pluginPrefix[:version]:goal" or "groupId:artifactId[:version]:goal"
150 
151                 lifecyclePluginResolver.resolveMissingPluginVersions(session.getTopLevelProject(), session);
152 
153                 MojoDescriptor mojoDescriptor =
154                         mojoDescriptorCreator.getMojoDescriptor(task, session, session.getTopLevelProject());
155 
156                 boolean aggregating = mojoDescriptor.isAggregator() || !mojoDescriptor.isProjectRequired();
157 
158                 if (currentSegment == null || currentSegment.isAggregating() != aggregating) {
159                     currentSegment = new TaskSegment(aggregating);
160                     taskSegments.add(currentSegment);
161                 }
162 
163                 currentSegment.getTasks().add(new GoalTask(task));
164             } else {
165                 // lifecycle phase
166 
167                 if (currentSegment == null || currentSegment.isAggregating()) {
168                     currentSegment = new TaskSegment(false);
169                     taskSegments.add(currentSegment);
170                 }
171 
172                 currentSegment.getTasks().add(new LifecycleTask(task));
173             }
174         }
175 
176         return taskSegments;
177     }
178 
179     private boolean projectIsNotPresent(MavenSession session) {
180         return !session.getRequest().isProjectPresent();
181     }
182 
183     private boolean requiresProject(MavenSession session) {
184         List<String> goals = session.getGoals();
185         if (goals != null) {
186             for (String goal : goals) {
187                 if (!isGoalSpecification(goal)) {
188                     return true;
189                 }
190             }
191         }
192         return false;
193     }
194 
195     private boolean isBeforeOrAfterPhase(String task) {
196         return task.startsWith(Lifecycle.BEFORE) || task.startsWith(Lifecycle.AFTER);
197     }
198 
199     private boolean isGoalSpecification(String task) {
200         return task.indexOf(':') >= 0;
201     }
202 }