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 @Named
73 @Singleton
74 public class MojoExecutor {
75
76 private static final Logger LOGGER = LoggerFactory.getLogger(MojoExecutor.class);
77
78 private final BuildPluginManager pluginManager;
79 private final MavenPluginManager mavenPluginManager;
80 private final LifecycleDependencyResolver lifeCycleDependencyResolver;
81 private final ExecutionEventCatapult eventCatapult;
82
83 private final OwnerReentrantReadWriteLock aggregatorLock = new OwnerReentrantReadWriteLock();
84
85 private final Provider<MojosExecutionStrategy> mojosExecutionStrategy;
86
87 private final MessageBuilderFactory messageBuilderFactory;
88
89 private final Map<Thread, MojoDescriptor> mojos = new ConcurrentHashMap<>();
90
91 @Inject
92 public MojoExecutor(
93 BuildPluginManager pluginManager,
94 MavenPluginManager mavenPluginManager,
95 LifecycleDependencyResolver lifeCycleDependencyResolver,
96 ExecutionEventCatapult eventCatapult,
97 Provider<MojosExecutionStrategy> mojosExecutionStrategy,
98 MessageBuilderFactory messageBuilderFactory) {
99 this.pluginManager = pluginManager;
100 this.mavenPluginManager = mavenPluginManager;
101 this.lifeCycleDependencyResolver = lifeCycleDependencyResolver;
102 this.eventCatapult = eventCatapult;
103 this.mojosExecutionStrategy = mojosExecutionStrategy;
104 this.messageBuilderFactory = messageBuilderFactory;
105 }
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 (classpath != null && !classpath.isEmpty()) {
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.get().execute(mojoExecutions, session, new MojoExecutionRunner() {
161 @Override
162 public void run(MojoExecution mojoExecution) throws LifecycleExecutionException {
163 MojoExecutor.this.execute(session, mojoExecution, projectIndex, dependencyContext, phaseRecorder);
164 }
165 });
166 }
167
168 private void execute(
169 MavenSession session,
170 MojoExecution mojoExecution,
171 ProjectIndex projectIndex,
172 DependencyContext dependencyContext,
173 PhaseRecorder phaseRecorder)
174 throws LifecycleExecutionException {
175 execute(session, mojoExecution, projectIndex, dependencyContext);
176 phaseRecorder.observeExecution(mojoExecution);
177 }
178
179 private void execute(
180 MavenSession session,
181 MojoExecution mojoExecution,
182 ProjectIndex projectIndex,
183 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, projectIndex, dependencyContext);
215 }
216
217
218
219
220
221
222
223
224
225 private class ProjectLock implements AutoCloseable {
226 final Lock acquiredAggregatorLock;
227 final OwnerReentrantLock acquiredProjectLock;
228
229 ProjectLock(MavenSession session, MojoDescriptor mojoDescriptor) {
230 mojos.put(Thread.currentThread(), mojoDescriptor);
231 if (session.getRequest().getDegreeOfConcurrency() > 1) {
232 boolean aggregator = mojoDescriptor.isAggregator();
233 acquiredAggregatorLock = aggregator ? aggregatorLock.writeLock() : aggregatorLock.readLock();
234 acquiredProjectLock = getProjectLock(session);
235 if (!acquiredAggregatorLock.tryLock()) {
236 Thread owner = aggregatorLock.getOwner();
237 MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null;
238 String str = ownerMojo != null ? " The " + ownerMojo.getId() : "An";
239 String msg = str + " aggregator mojo is already being executed "
240 + "in this parallel build, those kind of mojos require exclusive access to "
241 + "reactor to prevent race conditions. This mojo execution will be blocked "
242 + "until the aggregator mojo is done.";
243 warn(msg);
244 acquiredAggregatorLock.lock();
245 }
246 if (!acquiredProjectLock.tryLock()) {
247 Thread owner = acquiredProjectLock.getOwner();
248 MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null;
249 String str = ownerMojo != null ? " The " + ownerMojo.getId() : "A";
250 String msg = str + " mojo is already being executed "
251 + "on the project " + session.getCurrentProject().getGroupId()
252 + ":" + session.getCurrentProject().getArtifactId() + ". "
253 + "This mojo execution will be blocked "
254 + "until the mojo is done.";
255 warn(msg);
256 acquiredProjectLock.lock();
257 }
258 } else {
259 acquiredAggregatorLock = null;
260 acquiredProjectLock = null;
261 }
262 }
263
264 @Override
265 public void close() {
266
267 if (acquiredProjectLock != null) {
268 acquiredProjectLock.unlock();
269 }
270 if (acquiredAggregatorLock != null) {
271 acquiredAggregatorLock.unlock();
272 }
273 mojos.remove(Thread.currentThread());
274 }
275
276 @SuppressWarnings({"unchecked", "rawtypes"})
277 private OwnerReentrantLock getProjectLock(MavenSession session) {
278 SessionData data = session.getRepositorySession().getData();
279 Map<MavenProject, OwnerReentrantLock> locks =
280 (Map) data.computeIfAbsent(ProjectLock.class, ConcurrentHashMap::new);
281 return locks.computeIfAbsent(session.getCurrentProject(), p -> new OwnerReentrantLock());
282 }
283 }
284
285 static class OwnerReentrantLock extends ReentrantLock {
286 @Override
287 public Thread getOwner() {
288 return super.getOwner();
289 }
290 }
291
292 static class OwnerReentrantReadWriteLock extends ReentrantReadWriteLock {
293 @Override
294 public Thread getOwner() {
295 return super.getOwner();
296 }
297 }
298
299 private static void warn(String msg) {
300 for (String s : MultilineMessageHelper.format(msg)) {
301 LOGGER.warn(s);
302 }
303 }
304
305 private void doExecute(
306 MavenSession session,
307 MojoExecution mojoExecution,
308 ProjectIndex projectIndex,
309 DependencyContext dependencyContext)
310 throws LifecycleExecutionException {
311 MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
312
313 List<MavenProject> forkedProjects = executeForkedExecutions(mojoExecution, session, projectIndex);
314
315 ensureDependenciesAreResolved(mojoDescriptor, session, dependencyContext);
316
317 try (ProjectLock lock = new ProjectLock(session, mojoDescriptor)) {
318 doExecute2(session, mojoExecution);
319 } finally {
320 for (MavenProject forkedProject : forkedProjects) {
321 forkedProject.setExecutionProject(null);
322 }
323 }
324 }
325
326 private void doExecute2(MavenSession session, MojoExecution mojoExecution) throws LifecycleExecutionException {
327 eventCatapult.fire(ExecutionEvent.Type.MojoStarted, session, mojoExecution);
328 try {
329 try {
330 pluginManager.executeMojo(session, mojoExecution);
331 } catch (MojoFailureException
332 | PluginManagerException
333 | PluginConfigurationException
334 | MojoExecutionException e) {
335 throw new LifecycleExecutionException(
336 messageBuilderFactory, mojoExecution, session.getCurrentProject(), e);
337 }
338
339 eventCatapult.fire(ExecutionEvent.Type.MojoSucceeded, session, mojoExecution);
340 } catch (LifecycleExecutionException e) {
341 eventCatapult.fire(ExecutionEvent.Type.MojoFailed, session, mojoExecution, e);
342
343 throw e;
344 }
345 }
346
347 public void ensureDependenciesAreResolved(
348 MojoDescriptor mojoDescriptor, MavenSession session, DependencyContext dependencyContext)
349 throws LifecycleExecutionException {
350
351 MavenProject project = dependencyContext.getProject();
352 boolean aggregating = mojoDescriptor.isAggregator();
353
354 if (dependencyContext.isResolutionRequiredForCurrentProject()) {
355 Collection<String> scopesToCollect = dependencyContext.getScopesToCollectForCurrentProject();
356 Collection<String> scopesToResolve = dependencyContext.getScopesToResolveForCurrentProject();
357
358 lifeCycleDependencyResolver.resolveProjectDependencies(
359 project, scopesToCollect, scopesToResolve, session, aggregating, Collections.emptySet());
360
361 dependencyContext.synchronizeWithProjectState();
362 }
363
364 if (aggregating) {
365 Collection<String> scopesToCollect = toScopes(mojoDescriptor.getDependencyCollectionRequired());
366 Collection<String> scopesToResolve = toScopes(mojoDescriptor.getDependencyResolutionRequired());
367
368 if (dependencyContext.isResolutionRequiredForAggregatedProjects(scopesToCollect, scopesToResolve)) {
369 for (MavenProject aggregatedProject : session.getProjects()) {
370 if (aggregatedProject != project) {
371 lifeCycleDependencyResolver.resolveProjectDependencies(
372 aggregatedProject,
373 scopesToCollect,
374 scopesToResolve,
375 session,
376 aggregating,
377 Collections.emptySet());
378 }
379 }
380 }
381 }
382
383 ArtifactFilter artifactFilter = getArtifactFilter(mojoDescriptor);
384 List<MavenProject> projectsToResolve = LifecycleDependencyResolver.getProjects(
385 session.getCurrentProject(), session, mojoDescriptor.isAggregator());
386 for (MavenProject projectToResolve : projectsToResolve) {
387 projectToResolve.setArtifactFilter(artifactFilter);
388 }
389 }
390
391 private ArtifactFilter getArtifactFilter(MojoDescriptor mojoDescriptor) {
392 String scopeToResolve = mojoDescriptor.getDependencyResolutionRequired();
393 String scopeToCollect = mojoDescriptor.getDependencyCollectionRequired();
394
395 List<String> scopes = new ArrayList<>(2);
396 if (scopeToCollect != null && !scopeToCollect.isEmpty()) {
397 scopes.add(scopeToCollect);
398 }
399 if (scopeToResolve != null && !scopeToResolve.isEmpty()) {
400 scopes.add(scopeToResolve);
401 }
402
403 if (scopes.isEmpty()) {
404 return null;
405 } else {
406 return new CumulativeScopeArtifactFilter(scopes);
407 }
408 }
409
410 public List<MavenProject> executeForkedExecutions(
411 MojoExecution mojoExecution, MavenSession session, ProjectIndex projectIndex)
412 throws LifecycleExecutionException {
413 List<MavenProject> forkedProjects = Collections.emptyList();
414
415 Map<String, List<MojoExecution>> forkedExecutions = mojoExecution.getForkedExecutions();
416
417 if (!forkedExecutions.isEmpty()) {
418 eventCatapult.fire(ExecutionEvent.Type.ForkStarted, session, mojoExecution);
419
420 MavenProject project = session.getCurrentProject();
421
422 forkedProjects = new ArrayList<>(forkedExecutions.size());
423
424 try {
425 for (Map.Entry<String, List<MojoExecution>> fork : forkedExecutions.entrySet()) {
426 String projectId = fork.getKey();
427
428 int index = projectIndex.getIndices().get(projectId);
429
430 MavenProject forkedProject = projectIndex.getProjects().get(projectId);
431
432 forkedProjects.add(forkedProject);
433
434 MavenProject executedProject = forkedProject.clone();
435
436 forkedProject.setExecutionProject(executedProject);
437
438 List<MojoExecution> mojoExecutions = fork.getValue();
439
440 if (mojoExecutions.isEmpty()) {
441 continue;
442 }
443
444 try {
445 session.setCurrentProject(executedProject);
446 session.getProjects().set(index, executedProject);
447 projectIndex.getProjects().put(projectId, executedProject);
448
449 eventCatapult.fire(ExecutionEvent.Type.ForkedProjectStarted, session, mojoExecution);
450
451 execute(session, mojoExecutions, projectIndex);
452
453 eventCatapult.fire(ExecutionEvent.Type.ForkedProjectSucceeded, session, mojoExecution);
454 } catch (LifecycleExecutionException e) {
455 eventCatapult.fire(ExecutionEvent.Type.ForkedProjectFailed, session, mojoExecution, e);
456
457 throw e;
458 } finally {
459 projectIndex.getProjects().put(projectId, forkedProject);
460 session.getProjects().set(index, forkedProject);
461 session.setCurrentProject(project);
462 }
463 }
464
465 eventCatapult.fire(ExecutionEvent.Type.ForkSucceeded, session, mojoExecution);
466 } catch (LifecycleExecutionException e) {
467 eventCatapult.fire(ExecutionEvent.Type.ForkFailed, session, mojoExecution, e);
468
469 throw e;
470 }
471 }
472
473 return forkedProjects;
474 }
475 }