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 java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28 import java.util.TreeSet;
29 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.concurrent.ConcurrentMap;
31 import java.util.concurrent.locks.Lock;
32 import java.util.concurrent.locks.ReentrantLock;
33 import java.util.concurrent.locks.ReentrantReadWriteLock;
34
35 import org.apache.maven.artifact.Artifact;
36 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
37 import org.apache.maven.artifact.resolver.filter.CumulativeScopeArtifactFilter;
38 import org.apache.maven.execution.ExecutionEvent;
39 import org.apache.maven.execution.MavenSession;
40 import org.apache.maven.internal.MultilineMessageHelper;
41 import org.apache.maven.lifecycle.LifecycleExecutionException;
42 import org.apache.maven.lifecycle.MissingProjectException;
43 import org.apache.maven.plugin.BuildPluginManager;
44 import org.apache.maven.plugin.MavenPluginManager;
45 import org.apache.maven.plugin.MojoExecution;
46 import org.apache.maven.plugin.MojoExecutionException;
47 import org.apache.maven.plugin.MojoExecutionRunner;
48 import org.apache.maven.plugin.MojoFailureException;
49 import org.apache.maven.plugin.MojosExecutionStrategy;
50 import org.apache.maven.plugin.PluginConfigurationException;
51 import org.apache.maven.plugin.PluginIncompatibleException;
52 import org.apache.maven.plugin.PluginManagerException;
53 import org.apache.maven.plugin.descriptor.MojoDescriptor;
54 import org.apache.maven.project.MavenProject;
55 import org.codehaus.plexus.PlexusContainer;
56 import org.codehaus.plexus.component.annotations.Component;
57 import org.codehaus.plexus.component.annotations.Requirement;
58 import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
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 @Component(role = MojoExecutor.class)
76 public class MojoExecutor {
77
78 private static final Logger LOGGER = LoggerFactory.getLogger(MojoExecutor.class);
79
80 @Requirement
81 private BuildPluginManager pluginManager;
82
83 @Requirement
84 private MavenPluginManager mavenPluginManager;
85
86 @Requirement
87 private LifecycleDependencyResolver lifeCycleDependencyResolver;
88
89 @Requirement
90 private ExecutionEventCatapult eventCatapult;
91
92 private final OwnerReentrantReadWriteLock aggregatorLock = new OwnerReentrantReadWriteLock();
93
94 @Requirement
95 private PlexusContainer container;
96
97 private final Map<Thread, MojoDescriptor> mojos = new ConcurrentHashMap<>();
98
99 public MojoExecutor() {}
100
101 public DependencyContext newDependencyContext(MavenSession session, List<MojoExecution> mojoExecutions) {
102 Set<String> scopesToCollect = new TreeSet<>();
103 Set<String> scopesToResolve = new TreeSet<>();
104
105 collectDependencyRequirements(scopesToResolve, scopesToCollect, mojoExecutions);
106
107 return new DependencyContext(session.getCurrentProject(), scopesToCollect, scopesToResolve);
108 }
109
110 private void collectDependencyRequirements(
111 Set<String> scopesToResolve, Set<String> scopesToCollect, Collection<MojoExecution> mojoExecutions) {
112 for (MojoExecution mojoExecution : mojoExecutions) {
113 MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
114
115 scopesToResolve.addAll(toScopes(mojoDescriptor.getDependencyResolutionRequired()));
116
117 scopesToCollect.addAll(toScopes(mojoDescriptor.getDependencyCollectionRequired()));
118 }
119 }
120
121 private Collection<String> toScopes(String classpath) {
122 Collection<String> scopes = Collections.emptyList();
123
124 if (StringUtils.isNotEmpty(classpath)) {
125 if (Artifact.SCOPE_COMPILE.equals(classpath)) {
126 scopes = Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_PROVIDED);
127 } else if (Artifact.SCOPE_RUNTIME.equals(classpath)) {
128 scopes = Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_RUNTIME);
129 } else if (Artifact.SCOPE_COMPILE_PLUS_RUNTIME.equals(classpath)) {
130 scopes = Arrays.asList(
131 Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_PROVIDED, Artifact.SCOPE_RUNTIME);
132 } else if (Artifact.SCOPE_RUNTIME_PLUS_SYSTEM.equals(classpath)) {
133 scopes = Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_RUNTIME);
134 } else if (Artifact.SCOPE_TEST.equals(classpath)) {
135 scopes = Arrays.asList(
136 Artifact.SCOPE_COMPILE,
137 Artifact.SCOPE_SYSTEM,
138 Artifact.SCOPE_PROVIDED,
139 Artifact.SCOPE_RUNTIME,
140 Artifact.SCOPE_TEST);
141 }
142 }
143 return Collections.unmodifiableCollection(scopes);
144 }
145
146 public void execute(
147 final MavenSession session, final List<MojoExecution> mojoExecutions, final ProjectIndex projectIndex)
148 throws LifecycleExecutionException {
149
150 final DependencyContext dependencyContext = newDependencyContext(session, mojoExecutions);
151
152 final PhaseRecorder phaseRecorder = new PhaseRecorder(session.getCurrentProject());
153
154 MojosExecutionStrategy strategy;
155 try {
156 strategy = container.lookup(MojosExecutionStrategy.class);
157 } catch (ComponentLookupException e) {
158 throw new IllegalStateException("Unable to lookup MojosExecutionStrategy", e);
159 }
160 strategy.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.checkRequiredMavenVersion(mojoDescriptor.getPluginDescriptor());
189 } catch (PluginIncompatibleException e) {
190 throw new LifecycleExecutionException(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(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(mojoExecution, session.getCurrentProject(), cause);
206 } else {
207 eventCatapult.fire(ExecutionEvent.Type.MojoSkipped, session, mojoExecution);
208
209 return;
210 }
211 }
212
213 doExecute(session, mojoExecution, projectIndex, 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 @SuppressWarnings({"unchecked", "rawtypes"})
276 private OwnerReentrantLock getProjectLock(MavenSession session) {
277 SessionData data = session.getRepositorySession().getData();
278 ConcurrentMap<MavenProject, OwnerReentrantLock> locks = (ConcurrentMap) data.get(ProjectLock.class);
279
280 if (locks == null) {
281
282 data.set(ProjectLock.class, null, new ConcurrentHashMap<>());
283 locks = (ConcurrentMap) data.get(ProjectLock.class);
284 }
285 OwnerReentrantLock acquiredProjectLock = locks.get(session.getCurrentProject());
286 if (acquiredProjectLock == null) {
287 acquiredProjectLock = new OwnerReentrantLock();
288 OwnerReentrantLock prev = locks.putIfAbsent(session.getCurrentProject(), acquiredProjectLock);
289 if (prev != null) {
290 acquiredProjectLock = prev;
291 }
292 }
293 return acquiredProjectLock;
294 }
295 }
296
297 static class OwnerReentrantLock extends ReentrantLock {
298 @Override
299 public Thread getOwner() {
300 return super.getOwner();
301 }
302 }
303
304 static class OwnerReentrantReadWriteLock extends ReentrantReadWriteLock {
305 @Override
306 public Thread getOwner() {
307 return super.getOwner();
308 }
309 }
310
311 private static void warn(String msg) {
312 for (String s : MultilineMessageHelper.format(msg)) {
313 LOGGER.warn(s);
314 }
315 }
316
317 private void doExecute(
318 MavenSession session,
319 MojoExecution mojoExecution,
320 ProjectIndex projectIndex,
321 DependencyContext dependencyContext)
322 throws LifecycleExecutionException {
323 MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
324
325 List<MavenProject> forkedProjects = executeForkedExecutions(mojoExecution, session, projectIndex);
326
327 ensureDependenciesAreResolved(mojoDescriptor, session, dependencyContext);
328
329 try (ProjectLock lock = new ProjectLock(session, mojoDescriptor)) {
330 doExecute2(session, mojoExecution);
331 } finally {
332 for (MavenProject forkedProject : forkedProjects) {
333 forkedProject.setExecutionProject(null);
334 }
335 }
336 }
337
338 private void doExecute2(MavenSession session, MojoExecution mojoExecution) throws LifecycleExecutionException {
339 eventCatapult.fire(ExecutionEvent.Type.MojoStarted, session, mojoExecution);
340 try {
341 try {
342 pluginManager.executeMojo(session, mojoExecution);
343 } catch (MojoFailureException
344 | PluginManagerException
345 | PluginConfigurationException
346 | MojoExecutionException e) {
347 throw new LifecycleExecutionException(mojoExecution, session.getCurrentProject(), e);
348 }
349
350 eventCatapult.fire(ExecutionEvent.Type.MojoSucceeded, session, mojoExecution);
351 } catch (LifecycleExecutionException e) {
352 eventCatapult.fire(ExecutionEvent.Type.MojoFailed, session, mojoExecution, e);
353
354 throw e;
355 }
356 }
357
358 public void ensureDependenciesAreResolved(
359 MojoDescriptor mojoDescriptor, MavenSession session, DependencyContext dependencyContext)
360 throws LifecycleExecutionException {
361
362 MavenProject project = dependencyContext.getProject();
363 boolean aggregating = mojoDescriptor.isAggregator();
364
365 if (dependencyContext.isResolutionRequiredForCurrentProject()) {
366 Collection<String> scopesToCollect = dependencyContext.getScopesToCollectForCurrentProject();
367 Collection<String> scopesToResolve = dependencyContext.getScopesToResolveForCurrentProject();
368
369 lifeCycleDependencyResolver.resolveProjectDependencies(
370 project, scopesToCollect, scopesToResolve, session, aggregating, Collections.<Artifact>emptySet());
371
372 dependencyContext.synchronizeWithProjectState();
373 }
374
375 if (aggregating) {
376 Collection<String> scopesToCollect = toScopes(mojoDescriptor.getDependencyCollectionRequired());
377 Collection<String> scopesToResolve = toScopes(mojoDescriptor.getDependencyResolutionRequired());
378
379 if (dependencyContext.isResolutionRequiredForAggregatedProjects(scopesToCollect, scopesToResolve)) {
380 for (MavenProject aggregatedProject : session.getProjects()) {
381 if (aggregatedProject != project) {
382 lifeCycleDependencyResolver.resolveProjectDependencies(
383 aggregatedProject,
384 scopesToCollect,
385 scopesToResolve,
386 session,
387 aggregating,
388 Collections.<Artifact>emptySet());
389 }
390 }
391 }
392 }
393
394 ArtifactFilter artifactFilter = getArtifactFilter(mojoDescriptor);
395 List<MavenProject> projectsToResolve = LifecycleDependencyResolver.getProjects(
396 session.getCurrentProject(), session, mojoDescriptor.isAggregator());
397 for (MavenProject projectToResolve : projectsToResolve) {
398 projectToResolve.setArtifactFilter(artifactFilter);
399 }
400 }
401
402 private ArtifactFilter getArtifactFilter(MojoDescriptor mojoDescriptor) {
403 String scopeToResolve = mojoDescriptor.getDependencyResolutionRequired();
404 String scopeToCollect = mojoDescriptor.getDependencyCollectionRequired();
405
406 List<String> scopes = new ArrayList<>(2);
407 if (StringUtils.isNotEmpty(scopeToCollect)) {
408 scopes.add(scopeToCollect);
409 }
410 if (StringUtils.isNotEmpty(scopeToResolve)) {
411 scopes.add(scopeToResolve);
412 }
413
414 if (scopes.isEmpty()) {
415 return null;
416 } else {
417 return new CumulativeScopeArtifactFilter(scopes);
418 }
419 }
420
421 public List<MavenProject> executeForkedExecutions(
422 MojoExecution mojoExecution, MavenSession session, ProjectIndex projectIndex)
423 throws LifecycleExecutionException {
424 List<MavenProject> forkedProjects = Collections.emptyList();
425
426 Map<String, List<MojoExecution>> forkedExecutions = mojoExecution.getForkedExecutions();
427
428 if (!forkedExecutions.isEmpty()) {
429 eventCatapult.fire(ExecutionEvent.Type.ForkStarted, session, mojoExecution);
430
431 MavenProject project = session.getCurrentProject();
432
433 forkedProjects = new ArrayList<>(forkedExecutions.size());
434
435 try {
436 for (Map.Entry<String, List<MojoExecution>> fork : forkedExecutions.entrySet()) {
437 String projectId = fork.getKey();
438
439 int index = projectIndex.getIndices().get(projectId);
440
441 MavenProject forkedProject = projectIndex.getProjects().get(projectId);
442
443 forkedProjects.add(forkedProject);
444
445 MavenProject executedProject = forkedProject.clone();
446
447 forkedProject.setExecutionProject(executedProject);
448
449 List<MojoExecution> mojoExecutions = fork.getValue();
450
451 if (mojoExecutions.isEmpty()) {
452 continue;
453 }
454
455 try {
456 session.setCurrentProject(executedProject);
457 session.getProjects().set(index, executedProject);
458 projectIndex.getProjects().put(projectId, executedProject);
459
460 eventCatapult.fire(ExecutionEvent.Type.ForkedProjectStarted, session, mojoExecution);
461
462 execute(session, mojoExecutions, projectIndex);
463
464 eventCatapult.fire(ExecutionEvent.Type.ForkedProjectSucceeded, session, mojoExecution);
465 } catch (LifecycleExecutionException e) {
466 eventCatapult.fire(ExecutionEvent.Type.ForkedProjectFailed, session, mojoExecution, e);
467
468 throw e;
469 } finally {
470 projectIndex.getProjects().put(projectId, forkedProject);
471 session.getProjects().set(index, forkedProject);
472 session.setCurrentProject(project);
473 }
474 }
475
476 eventCatapult.fire(ExecutionEvent.Type.ForkSucceeded, session, mojoExecution);
477 } catch (LifecycleExecutionException e) {
478 eventCatapult.fire(ExecutionEvent.Type.ForkFailed, session, mojoExecution, e);
479
480 throw e;
481 }
482 }
483
484 return forkedProjects;
485 }
486 }