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