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.artifact.Artifact;
40 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
41 import org.apache.maven.artifact.resolver.filter.CumulativeScopeArtifactFilter;
42 import org.apache.maven.execution.ExecutionEvent;
43 import org.apache.maven.execution.MavenSession;
44 import org.apache.maven.internal.MultilineMessageHelper;
45 import org.apache.maven.lifecycle.LifecycleExecutionException;
46 import org.apache.maven.lifecycle.MissingProjectException;
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.util.StringUtils;
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 Map<Thread, MojoDescriptor> mojos = new ConcurrentHashMap<>();
91
92 @Inject
93 public MojoExecutor(
94 BuildPluginManager pluginManager,
95 MavenPluginManager mavenPluginManager,
96 LifecycleDependencyResolver lifeCycleDependencyResolver,
97 ExecutionEventCatapult eventCatapult,
98 Provider<MojosExecutionStrategy> mojosExecutionStrategy) {
99 this.pluginManager = pluginManager;
100 this.mavenPluginManager = mavenPluginManager;
101 this.lifeCycleDependencyResolver = lifeCycleDependencyResolver;
102 this.eventCatapult = eventCatapult;
103 this.mojosExecutionStrategy = mojosExecutionStrategy;
104 }
105
106 public DependencyContext newDependencyContext(MavenSession session, List<MojoExecution> mojoExecutions) {
107 Set<String> scopesToCollect = new TreeSet<>();
108 Set<String> scopesToResolve = new TreeSet<>();
109
110 collectDependencyRequirements(scopesToResolve, scopesToCollect, mojoExecutions);
111
112 return new DependencyContext(session.getCurrentProject(), scopesToCollect, scopesToResolve);
113 }
114
115 private void collectDependencyRequirements(
116 Set<String> scopesToResolve, Set<String> scopesToCollect, Collection<MojoExecution> mojoExecutions) {
117 for (MojoExecution mojoExecution : mojoExecutions) {
118 MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
119
120 scopesToResolve.addAll(toScopes(mojoDescriptor.getDependencyResolutionRequired()));
121
122 scopesToCollect.addAll(toScopes(mojoDescriptor.getDependencyCollectionRequired()));
123 }
124 }
125
126 private Collection<String> toScopes(String classpath) {
127 Collection<String> scopes = Collections.emptyList();
128
129 if (StringUtils.isNotEmpty(classpath)) {
130 if (Artifact.SCOPE_COMPILE.equals(classpath)) {
131 scopes = Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_PROVIDED);
132 } else if (Artifact.SCOPE_RUNTIME.equals(classpath)) {
133 scopes = Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_RUNTIME);
134 } else if (Artifact.SCOPE_COMPILE_PLUS_RUNTIME.equals(classpath)) {
135 scopes = Arrays.asList(
136 Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_PROVIDED, Artifact.SCOPE_RUNTIME);
137 } else if (Artifact.SCOPE_RUNTIME_PLUS_SYSTEM.equals(classpath)) {
138 scopes = Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_RUNTIME);
139 } else if (Artifact.SCOPE_TEST.equals(classpath)) {
140 scopes = Arrays.asList(
141 Artifact.SCOPE_COMPILE,
142 Artifact.SCOPE_SYSTEM,
143 Artifact.SCOPE_PROVIDED,
144 Artifact.SCOPE_RUNTIME,
145 Artifact.SCOPE_TEST);
146 }
147 }
148 return Collections.unmodifiableCollection(scopes);
149 }
150
151 public void execute(
152 final MavenSession session, final List<MojoExecution> mojoExecutions, final ProjectIndex projectIndex)
153 throws LifecycleExecutionException {
154
155 final DependencyContext dependencyContext = newDependencyContext(session, mojoExecutions);
156
157 final PhaseRecorder phaseRecorder = new PhaseRecorder(session.getCurrentProject());
158
159 mojosExecutionStrategy.get().execute(mojoExecutions, session, new MojoExecutionRunner() {
160 @Override
161 public void run(MojoExecution mojoExecution) throws LifecycleExecutionException {
162 MojoExecutor.this.execute(session, mojoExecution, projectIndex, dependencyContext, phaseRecorder);
163 }
164 });
165 }
166
167 private void execute(
168 MavenSession session,
169 MojoExecution mojoExecution,
170 ProjectIndex projectIndex,
171 DependencyContext dependencyContext,
172 PhaseRecorder phaseRecorder)
173 throws LifecycleExecutionException {
174 execute(session, mojoExecution, projectIndex, dependencyContext);
175 phaseRecorder.observeExecution(mojoExecution);
176 }
177
178 private void execute(
179 MavenSession session,
180 MojoExecution mojoExecution,
181 ProjectIndex projectIndex,
182 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(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(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(mojoExecution, session.getCurrentProject(), cause);
205 } else {
206 eventCatapult.fire(ExecutionEvent.Type.MojoSkipped, session, mojoExecution);
207
208 return;
209 }
210 }
211
212 doExecute(session, mojoExecution, projectIndex, dependencyContext);
213 }
214
215
216
217
218
219
220
221
222
223 private class ProjectLock implements AutoCloseable {
224 final Lock acquiredAggregatorLock;
225 final OwnerReentrantLock acquiredProjectLock;
226
227 ProjectLock(MavenSession session, MojoDescriptor mojoDescriptor) {
228 mojos.put(Thread.currentThread(), mojoDescriptor);
229 if (session.getRequest().getDegreeOfConcurrency() > 1) {
230 boolean aggregator = mojoDescriptor.isAggregator();
231 acquiredAggregatorLock = aggregator ? aggregatorLock.writeLock() : aggregatorLock.readLock();
232 acquiredProjectLock = getProjectLock(session);
233 if (!acquiredAggregatorLock.tryLock()) {
234 Thread owner = aggregatorLock.getOwner();
235 MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null;
236 String str = ownerMojo != null ? " The " + ownerMojo.getId() : "An";
237 String msg = str + " aggregator mojo is already being executed "
238 + "in this parallel build, those kind of mojos require exclusive access to "
239 + "reactor to prevent race conditions. This mojo execution will be blocked "
240 + "until the aggregator mojo is done.";
241 warn(msg);
242 acquiredAggregatorLock.lock();
243 }
244 if (!acquiredProjectLock.tryLock()) {
245 Thread owner = acquiredProjectLock.getOwner();
246 MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null;
247 String str = ownerMojo != null ? " The " + ownerMojo.getId() : "A";
248 String msg = str + " mojo is already being executed "
249 + "on the project " + session.getCurrentProject().getGroupId()
250 + ":" + session.getCurrentProject().getArtifactId() + ". "
251 + "This mojo execution will be blocked "
252 + "until the mojo is done.";
253 warn(msg);
254 acquiredProjectLock.lock();
255 }
256 } else {
257 acquiredAggregatorLock = null;
258 acquiredProjectLock = null;
259 }
260 }
261
262 @Override
263 public void close() {
264
265 if (acquiredProjectLock != null) {
266 acquiredProjectLock.unlock();
267 }
268 if (acquiredAggregatorLock != null) {
269 acquiredAggregatorLock.unlock();
270 }
271 mojos.remove(Thread.currentThread());
272 }
273
274 @SuppressWarnings({"unchecked", "rawtypes"})
275 private OwnerReentrantLock getProjectLock(MavenSession session) {
276 SessionData data = session.getRepositorySession().getData();
277
278
279
280 Map<MavenProject, OwnerReentrantLock> locks = (Map) data.get(ProjectLock.class);
281
282 if (locks == null) {
283
284 data.set(ProjectLock.class, null, new ConcurrentHashMap<>());
285 locks = (Map) data.get(ProjectLock.class);
286 }
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 ensureDependenciesAreResolved(mojoDescriptor, session, dependencyContext);
322
323 try (ProjectLock lock = new ProjectLock(session, mojoDescriptor)) {
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(mojoExecution, session.getCurrentProject(), e);
342 }
343
344 eventCatapult.fire(ExecutionEvent.Type.MojoSucceeded, session, mojoExecution);
345 } catch (LifecycleExecutionException e) {
346 eventCatapult.fire(ExecutionEvent.Type.MojoFailed, session, mojoExecution, e);
347
348 throw e;
349 }
350 }
351
352 public void ensureDependenciesAreResolved(
353 MojoDescriptor mojoDescriptor, MavenSession session, DependencyContext dependencyContext)
354 throws LifecycleExecutionException {
355
356 MavenProject project = dependencyContext.getProject();
357 boolean aggregating = mojoDescriptor.isAggregator();
358
359 if (dependencyContext.isResolutionRequiredForCurrentProject()) {
360 Collection<String> scopesToCollect = dependencyContext.getScopesToCollectForCurrentProject();
361 Collection<String> scopesToResolve = dependencyContext.getScopesToResolveForCurrentProject();
362
363 lifeCycleDependencyResolver.resolveProjectDependencies(
364 project, scopesToCollect, scopesToResolve, session, aggregating, Collections.emptySet());
365
366 dependencyContext.synchronizeWithProjectState();
367 }
368
369 if (aggregating) {
370 Collection<String> scopesToCollect = toScopes(mojoDescriptor.getDependencyCollectionRequired());
371 Collection<String> scopesToResolve = toScopes(mojoDescriptor.getDependencyResolutionRequired());
372
373 if (dependencyContext.isResolutionRequiredForAggregatedProjects(scopesToCollect, scopesToResolve)) {
374 for (MavenProject aggregatedProject : session.getProjects()) {
375 if (aggregatedProject != project) {
376 lifeCycleDependencyResolver.resolveProjectDependencies(
377 aggregatedProject,
378 scopesToCollect,
379 scopesToResolve,
380 session,
381 aggregating,
382 Collections.emptySet());
383 }
384 }
385 }
386 }
387
388 ArtifactFilter artifactFilter = getArtifactFilter(mojoDescriptor);
389 List<MavenProject> projectsToResolve = LifecycleDependencyResolver.getProjects(
390 session.getCurrentProject(), session, mojoDescriptor.isAggregator());
391 for (MavenProject projectToResolve : projectsToResolve) {
392 projectToResolve.setArtifactFilter(artifactFilter);
393 }
394 }
395
396 private ArtifactFilter getArtifactFilter(MojoDescriptor mojoDescriptor) {
397 String scopeToResolve = mojoDescriptor.getDependencyResolutionRequired();
398 String scopeToCollect = mojoDescriptor.getDependencyCollectionRequired();
399
400 List<String> scopes = new ArrayList<>(2);
401 if (StringUtils.isNotEmpty(scopeToCollect)) {
402 scopes.add(scopeToCollect);
403 }
404 if (StringUtils.isNotEmpty(scopeToResolve)) {
405 scopes.add(scopeToResolve);
406 }
407
408 if (scopes.isEmpty()) {
409 return null;
410 } else {
411 return new CumulativeScopeArtifactFilter(scopes);
412 }
413 }
414
415 public List<MavenProject> executeForkedExecutions(
416 MojoExecution mojoExecution, MavenSession session, ProjectIndex projectIndex)
417 throws LifecycleExecutionException {
418 List<MavenProject> forkedProjects = Collections.emptyList();
419
420 Map<String, List<MojoExecution>> forkedExecutions = mojoExecution.getForkedExecutions();
421
422 if (!forkedExecutions.isEmpty()) {
423 eventCatapult.fire(ExecutionEvent.Type.ForkStarted, session, mojoExecution);
424
425 MavenProject project = session.getCurrentProject();
426
427 forkedProjects = new ArrayList<>(forkedExecutions.size());
428
429 try {
430 for (Map.Entry<String, List<MojoExecution>> fork : forkedExecutions.entrySet()) {
431 String projectId = fork.getKey();
432
433 int index = projectIndex.getIndices().get(projectId);
434
435 MavenProject forkedProject = projectIndex.getProjects().get(projectId);
436
437 forkedProjects.add(forkedProject);
438
439 MavenProject executedProject = forkedProject.clone();
440
441 forkedProject.setExecutionProject(executedProject);
442
443 List<MojoExecution> mojoExecutions = fork.getValue();
444
445 if (mojoExecutions.isEmpty()) {
446 continue;
447 }
448
449 try {
450 session.setCurrentProject(executedProject);
451 session.getProjects().set(index, executedProject);
452 projectIndex.getProjects().put(projectId, executedProject);
453
454 eventCatapult.fire(ExecutionEvent.Type.ForkedProjectStarted, session, mojoExecution);
455
456 execute(session, mojoExecutions, projectIndex);
457
458 eventCatapult.fire(ExecutionEvent.Type.ForkedProjectSucceeded, session, mojoExecution);
459 } catch (LifecycleExecutionException e) {
460 eventCatapult.fire(ExecutionEvent.Type.ForkedProjectFailed, session, mojoExecution, e);
461
462 throw e;
463 } finally {
464 projectIndex.getProjects().put(projectId, forkedProject);
465 session.getProjects().set(index, forkedProject);
466 session.setCurrentProject(project);
467 }
468 }
469
470 eventCatapult.fire(ExecutionEvent.Type.ForkSucceeded, session, mojoExecution);
471 } catch (LifecycleExecutionException e) {
472 eventCatapult.fire(ExecutionEvent.Type.ForkFailed, session, mojoExecution, e);
473
474 throw e;
475 }
476 }
477
478 return forkedProjects;
479 }
480 }