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