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