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