1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.Arrays;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.TreeSet;
33 import java.util.concurrent.ConcurrentHashMap;
34 import java.util.concurrent.locks.Lock;
35 import java.util.concurrent.locks.ReentrantLock;
36 import java.util.concurrent.locks.ReentrantReadWriteLock;
37
38 import org.apache.maven.artifact.Artifact;
39 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
40 import org.apache.maven.artifact.resolver.filter.CumulativeScopeArtifactFilter;
41 import org.apache.maven.execution.ExecutionEvent;
42 import org.apache.maven.execution.MavenSession;
43 import org.apache.maven.internal.MultilineMessageHelper;
44 import org.apache.maven.lifecycle.LifecycleExecutionException;
45 import org.apache.maven.lifecycle.MissingProjectException;
46 import org.apache.maven.message.MessageBuilderFactory;
47 import org.apache.maven.plugin.BuildPluginManager;
48 import org.apache.maven.plugin.MavenPluginManager;
49 import org.apache.maven.plugin.MojoExecution;
50 import org.apache.maven.plugin.MojoExecutionException;
51 import org.apache.maven.plugin.MojoExecutionRunner;
52 import org.apache.maven.plugin.MojoFailureException;
53 import org.apache.maven.plugin.MojosExecutionStrategy;
54 import org.apache.maven.plugin.PluginConfigurationException;
55 import org.apache.maven.plugin.PluginIncompatibleException;
56 import org.apache.maven.plugin.PluginManagerException;
57 import org.apache.maven.plugin.descriptor.MojoDescriptor;
58 import org.apache.maven.project.MavenProject;
59 import org.codehaus.plexus.PlexusContainer;
60 import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
61 import org.codehaus.plexus.util.StringUtils;
62 import org.eclipse.aether.SessionData;
63 import org.slf4j.Logger;
64 import org.slf4j.LoggerFactory;
65
66
67
68
69
70
71
72
73
74
75
76
77 @Singleton
78 @Named
79 public class MojoExecutor {
80
81 private static final Logger LOGGER = LoggerFactory.getLogger(MojoExecutor.class);
82
83 @Inject
84 private BuildPluginManager pluginManager;
85
86 @Inject
87 private MavenPluginManager mavenPluginManager;
88
89 @Inject
90 private LifecycleDependencyResolver lifeCycleDependencyResolver;
91
92 @Inject
93 private ExecutionEventCatapult eventCatapult;
94
95 @Inject
96 private MessageBuilderFactory messageBuilderFactory;
97
98 private final OwnerReentrantReadWriteLock aggregatorLock = new OwnerReentrantReadWriteLock();
99
100 @Inject
101 private PlexusContainer container;
102
103 private final Map<Thread, MojoDescriptor> mojos = new ConcurrentHashMap<>();
104
105 public MojoExecutor() {}
106
107 public DependencyContext newDependencyContext(MavenSession session, List<MojoExecution> mojoExecutions) {
108 Set<String> scopesToCollect = new TreeSet<>();
109 Set<String> scopesToResolve = new TreeSet<>();
110
111 collectDependencyRequirements(scopesToResolve, scopesToCollect, mojoExecutions);
112
113 return new DependencyContext(session.getCurrentProject(), scopesToCollect, scopesToResolve);
114 }
115
116 private void collectDependencyRequirements(
117 Set<String> scopesToResolve, Set<String> scopesToCollect, Collection<MojoExecution> mojoExecutions) {
118 for (MojoExecution mojoExecution : mojoExecutions) {
119 MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
120
121 scopesToResolve.addAll(toScopes(mojoDescriptor.getDependencyResolutionRequired()));
122
123 scopesToCollect.addAll(toScopes(mojoDescriptor.getDependencyCollectionRequired()));
124 }
125 }
126
127 private Collection<String> toScopes(String classpath) {
128 Collection<String> scopes = Collections.emptyList();
129
130 if (StringUtils.isNotEmpty(classpath)) {
131 if (Artifact.SCOPE_COMPILE.equals(classpath)) {
132 scopes = Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_PROVIDED);
133 } else if (Artifact.SCOPE_RUNTIME.equals(classpath)) {
134 scopes = Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_RUNTIME);
135 } else if (Artifact.SCOPE_COMPILE_PLUS_RUNTIME.equals(classpath)) {
136 scopes = Arrays.asList(
137 Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_PROVIDED, Artifact.SCOPE_RUNTIME);
138 } else if (Artifact.SCOPE_RUNTIME_PLUS_SYSTEM.equals(classpath)) {
139 scopes = Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_RUNTIME);
140 } else if (Artifact.SCOPE_TEST.equals(classpath)) {
141 scopes = Arrays.asList(
142 Artifact.SCOPE_COMPILE,
143 Artifact.SCOPE_SYSTEM,
144 Artifact.SCOPE_PROVIDED,
145 Artifact.SCOPE_RUNTIME,
146 Artifact.SCOPE_TEST);
147 }
148 }
149 return Collections.unmodifiableCollection(scopes);
150 }
151
152 public void execute(
153 final MavenSession session, final List<MojoExecution> mojoExecutions, final ProjectIndex projectIndex)
154 throws LifecycleExecutionException {
155
156 final DependencyContext dependencyContext = newDependencyContext(session, mojoExecutions);
157
158 final PhaseRecorder phaseRecorder = new PhaseRecorder(session.getCurrentProject());
159
160 MojosExecutionStrategy strategy;
161 try {
162 strategy = container.lookup(MojosExecutionStrategy.class);
163 } catch (ComponentLookupException e) {
164 throw new IllegalStateException("Unable to lookup MojosExecutionStrategy", e);
165 }
166 strategy.execute(mojoExecutions, session, new MojoExecutionRunner() {
167 @Override
168 public void run(MojoExecution mojoExecution) throws LifecycleExecutionException {
169 MojoExecutor.this.execute(session, mojoExecution, projectIndex, dependencyContext, phaseRecorder);
170 }
171 });
172 }
173
174 private void execute(
175 MavenSession session,
176 MojoExecution mojoExecution,
177 ProjectIndex projectIndex,
178 DependencyContext dependencyContext,
179 PhaseRecorder phaseRecorder)
180 throws LifecycleExecutionException {
181 execute(session, mojoExecution, projectIndex, dependencyContext);
182 phaseRecorder.observeExecution(mojoExecution);
183 }
184
185 private void execute(
186 MavenSession session,
187 MojoExecution mojoExecution,
188 ProjectIndex projectIndex,
189 DependencyContext dependencyContext)
190 throws LifecycleExecutionException {
191 MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
192
193 try {
194 mavenPluginManager.checkPrerequisites(mojoDescriptor.getPluginDescriptor());
195 } catch (PluginIncompatibleException e) {
196 throw new LifecycleExecutionException(messageBuilderFactory, mojoExecution, session.getCurrentProject(), e);
197 }
198
199 if (mojoDescriptor.isProjectRequired() && !session.getRequest().isProjectPresent()) {
200 Throwable cause = new MissingProjectException(
201 "Goal requires a project to execute" + " but there is no POM in this directory ("
202 + session.getExecutionRootDirectory() + ")."
203 + " Please verify you invoked Maven from the correct directory.");
204 throw new LifecycleExecutionException(messageBuilderFactory, mojoExecution, null, cause);
205 }
206
207 if (mojoDescriptor.isOnlineRequired() && session.isOffline()) {
208 if (MojoExecution.Source.CLI.equals(mojoExecution.getSource())) {
209 Throwable cause = new IllegalStateException(
210 "Goal requires online mode for execution" + " but Maven is currently offline.");
211 throw new LifecycleExecutionException(
212 messageBuilderFactory, mojoExecution, session.getCurrentProject(), cause);
213 } else {
214 eventCatapult.fire(ExecutionEvent.Type.MojoSkipped, session, mojoExecution);
215
216 return;
217 }
218 }
219
220 doExecute(session, mojoExecution, projectIndex, dependencyContext);
221 }
222
223
224
225
226
227
228
229
230
231 private class ProjectLock implements AutoCloseable {
232 final Lock acquiredAggregatorLock;
233 final OwnerReentrantLock acquiredProjectLock;
234
235 ProjectLock(MavenSession session, MojoDescriptor mojoDescriptor) {
236 mojos.put(Thread.currentThread(), mojoDescriptor);
237 if (session.getRequest().getDegreeOfConcurrency() > 1) {
238 boolean aggregator = mojoDescriptor.isAggregator();
239 acquiredAggregatorLock = aggregator ? aggregatorLock.writeLock() : aggregatorLock.readLock();
240 acquiredProjectLock = getProjectLock(session);
241 if (!acquiredAggregatorLock.tryLock()) {
242 Thread owner = aggregatorLock.getOwner();
243 MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null;
244 String str = ownerMojo != null ? " The " + ownerMojo.getId() : "An";
245 String msg = str + " aggregator mojo is already being executed "
246 + "in this parallel build, those kind of mojos require exclusive access to "
247 + "reactor to prevent race conditions. This mojo execution will be blocked "
248 + "until the aggregator mojo is done.";
249 warn(msg);
250 acquiredAggregatorLock.lock();
251 }
252 if (!acquiredProjectLock.tryLock()) {
253 Thread owner = acquiredProjectLock.getOwner();
254 MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null;
255 String str = ownerMojo != null ? " The " + ownerMojo.getId() : "A";
256 String msg = str + " mojo is already being executed "
257 + "on the project " + session.getCurrentProject().getGroupId()
258 + ":" + session.getCurrentProject().getArtifactId() + ". "
259 + "This mojo execution will be blocked "
260 + "until the mojo is done.";
261 warn(msg);
262 acquiredProjectLock.lock();
263 }
264 } else {
265 acquiredAggregatorLock = null;
266 acquiredProjectLock = null;
267 }
268 }
269
270 @Override
271 public void close() {
272
273 if (acquiredProjectLock != null) {
274 acquiredProjectLock.unlock();
275 }
276 if (acquiredAggregatorLock != null) {
277 acquiredAggregatorLock.unlock();
278 }
279 mojos.remove(Thread.currentThread());
280 }
281
282 @SuppressWarnings({"unchecked", "rawtypes"})
283 private OwnerReentrantLock getProjectLock(MavenSession session) {
284 SessionData data = session.getRepositorySession().getData();
285 Map<MavenProject, OwnerReentrantLock> locks =
286 (Map) data.computeIfAbsent(ProjectLock.class, ConcurrentHashMap::new);
287 return locks.computeIfAbsent(session.getCurrentProject(), p -> new OwnerReentrantLock());
288 }
289 }
290
291 static class OwnerReentrantLock extends ReentrantLock {
292 @Override
293 public Thread getOwner() {
294 return super.getOwner();
295 }
296 }
297
298 static class OwnerReentrantReadWriteLock extends ReentrantReadWriteLock {
299 @Override
300 public Thread getOwner() {
301 return super.getOwner();
302 }
303 }
304
305 private static void warn(String msg) {
306 for (String s : MultilineMessageHelper.format(msg)) {
307 LOGGER.warn(s);
308 }
309 }
310
311 private void doExecute(
312 MavenSession session,
313 MojoExecution mojoExecution,
314 ProjectIndex projectIndex,
315 DependencyContext dependencyContext)
316 throws LifecycleExecutionException {
317 MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
318
319 List<MavenProject> forkedProjects = executeForkedExecutions(mojoExecution, session, projectIndex);
320
321 try (ProjectLock lock = new ProjectLock(session, mojoDescriptor)) {
322 ensureDependenciesAreResolved(mojoDescriptor, session, dependencyContext);
323
324 doExecute2(session, mojoExecution);
325 } finally {
326 for (MavenProject forkedProject : forkedProjects) {
327 forkedProject.setExecutionProject(null);
328 }
329 }
330 }
331
332 private void doExecute2(MavenSession session, MojoExecution mojoExecution) throws LifecycleExecutionException {
333 eventCatapult.fire(ExecutionEvent.Type.MojoStarted, session, mojoExecution);
334 try {
335 try {
336 pluginManager.executeMojo(session, mojoExecution);
337 } catch (MojoFailureException
338 | PluginManagerException
339 | PluginConfigurationException
340 | MojoExecutionException e) {
341 throw new LifecycleExecutionException(
342 messageBuilderFactory, mojoExecution, session.getCurrentProject(), e);
343 }
344
345 eventCatapult.fire(ExecutionEvent.Type.MojoSucceeded, session, mojoExecution);
346 } catch (LifecycleExecutionException e) {
347 eventCatapult.fire(ExecutionEvent.Type.MojoFailed, session, mojoExecution, e);
348
349 throw e;
350 }
351 }
352
353 public void ensureDependenciesAreResolved(
354 MojoDescriptor mojoDescriptor, MavenSession session, DependencyContext dependencyContext)
355 throws LifecycleExecutionException {
356
357 MavenProject project = dependencyContext.getProject();
358 boolean aggregating = mojoDescriptor.isAggregator();
359
360 if (dependencyContext.isResolutionRequiredForCurrentProject()) {
361 Collection<String> scopesToCollect = dependencyContext.getScopesToCollectForCurrentProject();
362 Collection<String> scopesToResolve = dependencyContext.getScopesToResolveForCurrentProject();
363
364 lifeCycleDependencyResolver.resolveProjectDependencies(
365 project, scopesToCollect, scopesToResolve, session, aggregating, Collections.<Artifact>emptySet());
366
367 dependencyContext.synchronizeWithProjectState();
368 }
369
370 if (aggregating) {
371 Collection<String> scopesToCollect = toScopes(mojoDescriptor.getDependencyCollectionRequired());
372 Collection<String> scopesToResolve = toScopes(mojoDescriptor.getDependencyResolutionRequired());
373
374 if (dependencyContext.isResolutionRequiredForAggregatedProjects(scopesToCollect, scopesToResolve)) {
375 for (MavenProject aggregatedProject : session.getProjects()) {
376 if (aggregatedProject != project) {
377 lifeCycleDependencyResolver.resolveProjectDependencies(
378 aggregatedProject,
379 scopesToCollect,
380 scopesToResolve,
381 session,
382 aggregating,
383 Collections.<Artifact>emptySet());
384 }
385 }
386 }
387 }
388
389 ArtifactFilter artifactFilter = getArtifactFilter(mojoDescriptor);
390 List<MavenProject> projectsToResolve = LifecycleDependencyResolver.getProjects(
391 session.getCurrentProject(), session, mojoDescriptor.isAggregator());
392 for (MavenProject projectToResolve : projectsToResolve) {
393 projectToResolve.setArtifactFilter(artifactFilter);
394 }
395 }
396
397 private ArtifactFilter getArtifactFilter(MojoDescriptor mojoDescriptor) {
398 String scopeToResolve = mojoDescriptor.getDependencyResolutionRequired();
399 String scopeToCollect = mojoDescriptor.getDependencyCollectionRequired();
400
401 List<String> scopes = new ArrayList<>(2);
402 if (StringUtils.isNotEmpty(scopeToCollect)) {
403 scopes.add(scopeToCollect);
404 }
405 if (StringUtils.isNotEmpty(scopeToResolve)) {
406 scopes.add(scopeToResolve);
407 }
408
409 if (scopes.isEmpty()) {
410 return null;
411 } else {
412 return new CumulativeScopeArtifactFilter(scopes);
413 }
414 }
415
416 public List<MavenProject> executeForkedExecutions(
417 MojoExecution mojoExecution, MavenSession session, ProjectIndex projectIndex)
418 throws LifecycleExecutionException {
419 List<MavenProject> forkedProjects = Collections.emptyList();
420
421 Map<String, List<MojoExecution>> forkedExecutions = mojoExecution.getForkedExecutions();
422
423 if (!forkedExecutions.isEmpty()) {
424 eventCatapult.fire(ExecutionEvent.Type.ForkStarted, session, mojoExecution);
425
426 MavenProject project = session.getCurrentProject();
427
428 forkedProjects = new ArrayList<>(forkedExecutions.size());
429
430 try {
431 for (Map.Entry<String, List<MojoExecution>> fork : forkedExecutions.entrySet()) {
432 String projectId = fork.getKey();
433
434 int index = projectIndex.getIndices().get(projectId);
435
436 MavenProject forkedProject = projectIndex.getProjects().get(projectId);
437
438 forkedProjects.add(forkedProject);
439
440 MavenProject executedProject = forkedProject.clone();
441
442 forkedProject.setExecutionProject(executedProject);
443
444 List<MojoExecution> mojoExecutions = fork.getValue();
445
446 if (mojoExecutions.isEmpty()) {
447 continue;
448 }
449
450 try {
451 session.setCurrentProject(executedProject);
452 session.getProjects().set(index, executedProject);
453 projectIndex.getProjects().put(projectId, executedProject);
454
455 eventCatapult.fire(ExecutionEvent.Type.ForkedProjectStarted, session, mojoExecution);
456
457 execute(session, mojoExecutions, projectIndex);
458
459 eventCatapult.fire(ExecutionEvent.Type.ForkedProjectSucceeded, session, mojoExecution);
460 } catch (LifecycleExecutionException e) {
461 eventCatapult.fire(ExecutionEvent.Type.ForkedProjectFailed, session, mojoExecution, e);
462
463 throw e;
464 } finally {
465 projectIndex.getProjects().put(projectId, forkedProject);
466 session.getProjects().set(index, forkedProject);
467 session.setCurrentProject(project);
468 }
469 }
470
471 eventCatapult.fire(ExecutionEvent.Type.ForkSucceeded, session, mojoExecution);
472 } catch (LifecycleExecutionException e) {
473 eventCatapult.fire(ExecutionEvent.Type.ForkFailed, session, mojoExecution, e);
474
475 throw e;
476 }
477 }
478
479 return forkedProjects;
480 }
481 }