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      @Override
82      public void execute(MavenSession session) {
83          eventCatapult.fire(ExecutionEvent.Type.SessionStarted, session, null);
84  
85          try {
86              if (requiresProject(session) && projectIsNotPresent(session)) {
87                  throw new MissingProjectException("The goal you specified requires a project to execute"
88                          + " but there is no POM in this directory (" + session.getTopDirectory() + ")."
89                          + " Please verify you invoked Maven from the correct directory.");
90              }
91  
92              List<TaskSegment> taskSegments = calculateTaskSegments(session);
93              if (taskSegments.isEmpty()) {
94                  throw new NoGoalSpecifiedException("No goals have been specified for this build."
95                          + " You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or"
96                          + " <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>."
97                          + " Available lifecycle phases are: " + defaultLifeCycles.getLifecyclePhaseList() + ".");
98              }
99  
100             int degreeOfConcurrency = session.getRequest().getDegreeOfConcurrency();
101             if (degreeOfConcurrency > 1) {
102                 logger.info("");
103                 logger.info(String.format(
104                         "Using the %s implementation with a thread count of %d",
105                         executor.getClass().getSimpleName(), degreeOfConcurrency));
106             }
107 
108             ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
109             ReactorBuildStatus reactorBuildStatus = new ReactorBuildStatus(session.getProjectDependencyGraph());
110             ReactorContext reactorContext =
111                     new ReactorContext(session.getResult(), oldContextClassLoader, reactorBuildStatus);
112             executor.execute(session, reactorContext, taskSegments);
113 
114         } catch (Exception e) {
115             session.getResult().addException(e);
116         } finally {
117             eventCatapult.fire(ExecutionEvent.Type.SessionEnded, session, null);
118         }
119     }
120 
121     public List<TaskSegment> calculateTaskSegments(MavenSession session) throws Exception {
122 
123         MavenProject rootProject = session.getTopLevelProject();
124 
125         List<String> tasks = requireNonNull(session.getGoals()); // session never returns null, but empty list
126 
127         if (tasks.isEmpty()
128                 && (rootProject.getDefaultGoal() != null
129                         && !rootProject.getDefaultGoal().isEmpty())) {
130             tasks = Stream.of(rootProject.getDefaultGoal().split("\\s+"))
131                     .filter(g -> !g.isEmpty())
132                     .collect(Collectors.toList());
133         }
134 
135         return calculateTaskSegments(session, tasks);
136     }
137 
138     public List<TaskSegment> calculateTaskSegments(MavenSession session, List<String> tasks) throws Exception {
139         List<TaskSegment> taskSegments = new ArrayList<>(tasks.size());
140 
141         TaskSegment currentSegment = null;
142 
143         for (String task : tasks) {
144             if (isBeforeOrAfterPhase(task)) {
145                 String prevTask = task;
146                 task = PhaseId.of(task).phase();
147                 logger.warn("Illegal call to phase '{}'. The main phase '{}' will be used instead.", prevTask, task);
148             }
149             if (isGoalSpecification(task)) {
150                 // "pluginPrefix[:version]:goal" or "groupId:artifactId[:version]:goal"
151 
152                 lifecyclePluginResolver.resolveMissingPluginVersions(session.getTopLevelProject(), session);
153 
154                 MojoDescriptor mojoDescriptor =
155                         mojoDescriptorCreator.getMojoDescriptor(task, session, session.getTopLevelProject());
156 
157                 boolean aggregating = mojoDescriptor.isAggregator() || !mojoDescriptor.isProjectRequired();
158 
159                 if (currentSegment == null || currentSegment.isAggregating() != aggregating) {
160                     currentSegment = new TaskSegment(aggregating);
161                     taskSegments.add(currentSegment);
162                 }
163 
164                 currentSegment.getTasks().add(new GoalTask(task));
165             } else {
166                 // lifecycle phase
167 
168                 if (currentSegment == null || currentSegment.isAggregating()) {
169                     currentSegment = new TaskSegment(false);
170                     taskSegments.add(currentSegment);
171                 }
172 
173                 currentSegment.getTasks().add(new LifecycleTask(task));
174             }
175         }
176 
177         return taskSegments;
178     }
179 
180     private boolean projectIsNotPresent(MavenSession session) {
181         return !session.getRequest().isProjectPresent();
182     }
183 
184     private boolean requiresProject(MavenSession session) {
185         List<String> goals = session.getGoals();
186         if (goals != null) {
187             for (String goal : goals) {
188                 if (!isGoalSpecification(goal)) {
189                     return true;
190                 }
191             }
192         }
193         return false;
194     }
195 
196     private boolean isBeforeOrAfterPhase(String task) {
197         return task.startsWith(Lifecycle.BEFORE) || task.startsWith(Lifecycle.AFTER);
198     }
199 
200     private boolean isGoalSpecification(String task) {
201         return task.indexOf(':') >= 0;
202     }
203 }