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