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