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