View Javadoc

1   package org.apache.maven.lifecycle.internal;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.artifact.Artifact;
23  import org.apache.maven.artifact.ArtifactUtils;
24  import org.apache.maven.execution.BuildSuccess;
25  import org.apache.maven.execution.ExecutionEvent;
26  import org.apache.maven.execution.MavenExecutionRequest;
27  import org.apache.maven.execution.MavenSession;
28  import org.apache.maven.lifecycle.LifecycleExecutionException;
29  import org.apache.maven.lifecycle.MavenExecutionPlan;
30  import org.apache.maven.lifecycle.Schedule;
31  import org.apache.maven.project.MavenProject;
32  import org.codehaus.plexus.component.annotations.Component;
33  import org.codehaus.plexus.component.annotations.Requirement;
34  import org.codehaus.plexus.logging.Logger;
35  
36  import java.util.ArrayList;
37  import java.util.Collection;
38  import java.util.HashMap;
39  import java.util.HashSet;
40  import java.util.Iterator;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.Properties;
44  import java.util.Set;
45  import java.util.concurrent.Callable;
46  import java.util.concurrent.CompletionService;
47  import java.util.concurrent.ExecutionException;
48  import java.util.concurrent.ExecutorCompletionService;
49  import java.util.concurrent.ExecutorService;
50  import java.util.concurrent.Future;
51  
52  /**
53   * Builds the full lifecycle in weave-mode (phase by phase as opposed to project-by-project)
54   * <p/>
55   * NOTE: Weave mode is still experimental. It may be either promoted to first class citizen
56   * at some later point in time, and it may also be removed entirely. Weave mode has much more aggressive
57   * concurrency behaviour than regular threaded mode, and as such is still under test wrt cross platform stability.
58   * <p/>
59   * To remove weave mode from m3, the following should be removed:
60   * ExecutionPlanItem.schedule w/setters and getters
61   * DefaultLifeCycles.getScheduling() and all its use
62   * ReactorArtifactRepository has a reference to isWeave too.
63   * This class and its usage
64   * 
65   * @since 3.0
66   * @author Kristian Rosenvold
67   *         Builds one or more lifecycles for a full module
68   *         <p/>
69   *         NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
70   */
71  @Component( role = LifecycleWeaveBuilder.class )
72  public class LifecycleWeaveBuilder
73  {
74  
75      @Requirement
76      private MojoExecutor mojoExecutor;
77  
78      @Requirement
79      private BuilderCommon builderCommon;
80  
81      @Requirement
82      private Logger logger;
83  
84      @Requirement
85      private ExecutionEventCatapult eventCatapult;
86  
87      private Map<MavenProject, MavenExecutionPlan> executionPlans = new HashMap<MavenProject, MavenExecutionPlan>();
88  
89  
90      @SuppressWarnings( { "UnusedDeclaration" } )
91      public LifecycleWeaveBuilder()
92      {
93      }
94  
95      public LifecycleWeaveBuilder( MojoExecutor mojoExecutor, BuilderCommon builderCommon, Logger logger,
96                                    ExecutionEventCatapult eventCatapult )
97      {
98          this.mojoExecutor = mojoExecutor;
99          this.builderCommon = builderCommon;
100         this.logger = logger;
101         this.eventCatapult = eventCatapult;
102     }
103 
104     public void build( ProjectBuildList projectBuilds, ReactorContext buildContext, List<TaskSegment> taskSegments,
105                        MavenSession session, ExecutorService executor, ReactorBuildStatus reactorBuildStatus )
106         throws ExecutionException, InterruptedException
107     {
108         ConcurrentBuildLogger concurrentBuildLogger = new ConcurrentBuildLogger();
109         CompletionService<ProjectSegment> service = new ExecutorCompletionService<ProjectSegment>( executor );
110 
111         try
112         {
113             for ( MavenProject mavenProject : session.getProjects() )
114             {
115                 Artifact mainArtifact = mavenProject.getArtifact();
116                 if ( mainArtifact != null && !( mainArtifact instanceof ThreadLockedArtifact ) )
117                 {
118                     ThreadLockedArtifact threadLockedArtifact = new ThreadLockedArtifact( mainArtifact );
119                     mavenProject.setArtifact( threadLockedArtifact );
120                 }
121             }
122 
123             final List<Future<ProjectSegment>> futures = new ArrayList<Future<ProjectSegment>>();
124             final Map<ProjectSegment, Future<MavenExecutionPlan>> plans =
125                 new HashMap<ProjectSegment, Future<MavenExecutionPlan>>();
126 
127             for ( TaskSegment taskSegment : taskSegments )
128             {
129                 ProjectBuildList segmentChunks = projectBuilds.getByTaskSegment( taskSegment );
130                 Set<Artifact> projectArtifacts = new HashSet<Artifact>();
131                 for ( ProjectSegment segmentChunk : segmentChunks )
132                 {
133                     Artifact artifact = segmentChunk.getProject().getArtifact();
134                     if ( artifact != null )
135                     {
136                         projectArtifacts.add( artifact );
137                     }
138                 }
139                 for ( ProjectSegment projectBuild : segmentChunks )
140                 {
141                     plans.put( projectBuild, executor.submit( createEPFuture( projectBuild, projectArtifacts ) ) );
142                 }
143 
144                 for ( ProjectSegment projectSegment : plans.keySet() )
145                 {
146                     executionPlans.put( projectSegment.getProject(), plans.get( projectSegment ).get() );
147 
148                 }
149                 for ( ProjectSegment projectBuild : segmentChunks )
150                 {
151                     try
152                     {
153                         final MavenExecutionPlan executionPlan = plans.get( projectBuild ).get();
154 
155                         DependencyContext dependencyContext =
156                             mojoExecutor.newDependencyContext( session, executionPlan.getMojoExecutions() );
157 
158                         final Callable<ProjectSegment> projectBuilder =
159                             createCallableForBuildingOneFullModule( buildContext, session, reactorBuildStatus,
160                                                                     executionPlan, projectBuild, dependencyContext,
161                                                                     concurrentBuildLogger );
162 
163                         futures.add( service.submit( projectBuilder ) );
164                     }
165                     catch ( Exception e )
166                     {
167                         throw new ExecutionException( e );
168                     }
169                 }
170 
171                 for ( Future<ProjectSegment> buildFuture : futures )
172                 {
173                     buildFuture.get();  // At this point, this build *is* finished.
174                     // Do not leak threads past here or evil gremlins will get you!
175                 }
176                 futures.clear();
177             }
178         }
179         finally
180         {
181             projectBuilds.closeAll();
182         }
183         logger.info( concurrentBuildLogger.toString() );
184     }
185 
186     private Callable<MavenExecutionPlan> createEPFuture( final ProjectSegment projectSegment,
187                                                          final Set<Artifact> projectArtifacts )
188     {
189         return new Callable<MavenExecutionPlan>()
190         {
191             public MavenExecutionPlan call()
192                 throws Exception
193             {
194                 return builderCommon.resolveBuildPlan( projectSegment.getSession(), projectSegment.getProject(),
195                                                        projectSegment.getTaskSegment(), projectArtifacts );
196             }
197         };
198     }
199 
200 
201     private Callable<ProjectSegment> createCallableForBuildingOneFullModule( final ReactorContext reactorContext,
202                                                                              final MavenSession rootSession,
203                                                                              final ReactorBuildStatus reactorBuildStatus,
204                                                                              final MavenExecutionPlan executionPlan,
205                                                                              final ProjectSegment projectBuild,
206                                                                              final DependencyContext dependencyContext,
207                                                                              final ConcurrentBuildLogger concurrentBuildLogger )
208     {
209         return new Callable<ProjectSegment>()
210         {
211             public ProjectSegment call()
212                 throws Exception
213             {
214                 Iterator<ExecutionPlanItem> planItems = executionPlan.iterator();
215                 ExecutionPlanItem current = planItems.hasNext() ? planItems.next() : null;
216                 ThreadLockedArtifact threadLockedArtifact = (ThreadLockedArtifact)projectBuild.getProject().getArtifact();
217                 if ( threadLockedArtifact != null )
218                 {
219                     threadLockedArtifact.attachToThread();
220                 }
221                 long buildStartTime = System.currentTimeMillis();
222 
223                 //muxer.associateThreadWithProjectSegment( projectBuild );
224 
225                 if ( reactorBuildStatus.isHaltedOrBlacklisted( projectBuild.getProject() ) )
226                 {
227                     eventCatapult.fire( ExecutionEvent.Type.ProjectSkipped, projectBuild.getSession(), null );
228                     return null;
229                 }
230 
231                 eventCatapult.fire( ExecutionEvent.Type.ProjectStarted, projectBuild.getSession(), null );
232 
233                 Collection<ArtifactLink> dependencyLinks = getUpstreamReactorDependencies( projectBuild );
234 
235                 try
236                 {
237                     PhaseRecorder phaseRecorder = new PhaseRecorder( projectBuild.getProject() );
238                     long totalMojoTime = 0;
239                     long mojoStart;
240                     while ( current != null && !reactorBuildStatus.isHaltedOrBlacklisted( projectBuild.getProject() ) )
241                     {
242 
243                         BuildLogItem builtLogItem =
244                             concurrentBuildLogger.createBuildLogItem( projectBuild.getProject(), current );
245                         final Schedule schedule = current.getSchedule();
246 
247                         mojoStart = System.currentTimeMillis();
248                         buildExecutionPlanItem( current, phaseRecorder, schedule, reactorContext, projectBuild,
249                                                 dependencyContext );
250                         totalMojoTime += ( System.currentTimeMillis() - mojoStart );
251 
252                         current.setComplete();
253                         builtLogItem.setComplete();
254 
255                         ExecutionPlanItem nextPlanItem = planItems.hasNext() ? planItems.next() : null;
256                         if ( nextPlanItem != null && phaseRecorder.isDifferentPhase( nextPlanItem.getMojoExecution() ) )
257                         {
258 
259                             final Schedule scheduleOfNext = nextPlanItem.getSchedule();
260                             if ( scheduleOfNext == null || !scheduleOfNext.isParallel() )
261                             {
262                                 waitForAppropriateUpstreamExecutionsToFinish( builtLogItem, nextPlanItem, projectBuild,
263                                                                               scheduleOfNext );
264                             }
265 
266                             for ( ArtifactLink dependencyLink : dependencyLinks )
267                             {
268                                 dependencyLink.resolveFromUpstream();
269                             }
270                         }
271                         current = nextPlanItem;
272                     }
273 
274                     final BuildSuccess summary =
275                         new BuildSuccess( projectBuild.getProject(), totalMojoTime ); // - waitingTime
276                     reactorContext.getResult().addBuildSummary( summary );
277                     eventCatapult.fire( ExecutionEvent.Type.ProjectSucceeded, projectBuild.getSession(), null );
278                 }
279                 catch ( Exception e )
280                 {
281                     builderCommon.handleBuildError( reactorContext, rootSession, projectBuild.getProject(), e,
282                                                     buildStartTime );
283                 }
284                 finally
285                 {
286                     if ( current != null )
287                     {
288                         executionPlan.forceAllComplete();
289                     }
290                     // muxer.setThisModuleComplete( projectBuild );
291                 }
292                 return null;
293             }
294 
295         };
296     }
297 
298     private void waitForAppropriateUpstreamExecutionsToFinish( BuildLogItem builtLogItem,
299                                                                ExecutionPlanItem nextPlanItem,
300                                                                ProjectSegment projectBuild, Schedule scheduleOfNext )
301         throws InterruptedException
302     {
303         for ( MavenProject upstreamProject : projectBuild.getImmediateUpstreamProjects() )
304         {
305             final MavenExecutionPlan upstreamPlan = executionPlans.get( upstreamProject );
306             final String nextPhase = scheduleOfNext != null && scheduleOfNext.hasUpstreamPhaseDefined()
307                 ? scheduleOfNext.getUpstreamPhase()
308                 : nextPlanItem.getLifecyclePhase();
309             final ExecutionPlanItem upstream = upstreamPlan.findLastInPhase( nextPhase );
310 
311             if ( upstream != null )
312             {
313                 long startWait = System.currentTimeMillis();
314                 upstream.waitUntilDone();
315                 builtLogItem.addWait( upstreamProject, upstream, startWait );
316             }
317             else if ( !upstreamPlan.containsPhase( nextPhase ) )
318             {
319                 // Still a bit of a kludge; if we cannot connect in a sensible way to
320                 // the upstream build plan we just revert to waiting for it all to
321                 // complete. Real problem is per-mojo phase->lifecycle mapping
322                 builtLogItem.addDependency( upstreamProject, "No phase tracking possible " );
323                 upstreamPlan.waitUntilAllDone();
324             }
325             else
326             {
327                 builtLogItem.addDependency( upstreamProject, "No schedule" );
328             }
329         }
330     }
331 
332     private Collection<ArtifactLink> getUpstreamReactorDependencies( ProjectSegment projectBuild )
333     {
334         Collection<ArtifactLink> result = new ArrayList<ArtifactLink>();
335         for ( MavenProject upstreamProject : projectBuild.getTransitiveUpstreamProjects() )
336         {
337             Artifact upStreamArtifact = upstreamProject.getArtifact();
338             if ( upStreamArtifact != null )
339             {
340                 Artifact dependencyArtifact = findDependency( projectBuild.getProject(), upStreamArtifact );
341                 if ( dependencyArtifact != null )
342                 {
343                     result.add( new ArtifactLink( dependencyArtifact, upStreamArtifact ) );
344                 }
345             }
346 
347             Artifact upStreamTestScopedArtifact = findTestScopedArtifact( upstreamProject );
348             if ( upStreamTestScopedArtifact != null )
349             {
350                 Artifact dependencyArtifact = findDependency( projectBuild.getProject(), upStreamArtifact );
351                 if ( dependencyArtifact != null )
352                 {
353                     result.add( new ArtifactLink( dependencyArtifact, upStreamTestScopedArtifact ) );
354                 }
355             }
356         }
357         return result;
358     }
359 
360 
361     private Artifact findTestScopedArtifact( MavenProject upstreamProject )
362     {
363         if ( upstreamProject == null )
364         {
365             return null;
366         }
367 
368         List<Artifact> artifactList = upstreamProject.getAttachedArtifacts();
369         for ( Artifact artifact : artifactList )
370         {
371             if ( Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
372             {
373                 return artifact;
374             }
375         }
376         return null;
377     }
378 
379     private static boolean isThreadLockedAndEmpty(Artifact artifact){
380         return artifact instanceof  ThreadLockedArtifact && !((ThreadLockedArtifact) artifact).hasReal();
381     }
382 
383     private static Artifact findDependency( MavenProject project, Artifact upStreamArtifact )
384     {
385         if ( upStreamArtifact == null || isThreadLockedAndEmpty(upStreamArtifact))
386         {
387             return null;
388         }
389 
390         String key = ArtifactUtils.key( upStreamArtifact.getGroupId(), upStreamArtifact.getArtifactId(),
391                                         upStreamArtifact.getVersion() );
392         final Set<Artifact> deps = project.getDependencyArtifacts();
393         for ( Artifact dep : deps )
394         {
395             String depKey = ArtifactUtils.key( dep.getGroupId(), dep.getArtifactId(), dep.getVersion() );
396             if ( key.equals( depKey ) )
397             {
398                 return dep;
399             }
400         }
401         return null;
402 
403     }
404 
405     private void buildExecutionPlanItem( ExecutionPlanItem current, PhaseRecorder phaseRecorder, Schedule schedule,
406                                          ReactorContext reactorContext, ProjectSegment projectBuild,
407                                          DependencyContext dependencyContext )
408         throws LifecycleExecutionException
409     {
410         if ( schedule != null && schedule.isMojoSynchronized() )
411         {
412             synchronized ( current.getPlugin() )
413             {
414                 buildExecutionPlanItem( reactorContext, current, projectBuild, dependencyContext, phaseRecorder );
415             }
416         }
417         else
418         {
419             buildExecutionPlanItem( reactorContext, current, projectBuild, dependencyContext, phaseRecorder );
420         }
421     }
422 
423 
424     private void buildExecutionPlanItem( ReactorContext reactorContext, ExecutionPlanItem node,
425                                          ProjectSegment projectBuild, DependencyContext dependencyContext,
426                                          PhaseRecorder phaseRecorder )
427         throws LifecycleExecutionException
428     {
429 
430         MavenProject currentProject = projectBuild.getProject();
431 
432         long buildStartTime = System.currentTimeMillis();
433 
434         CurrentPhaseForThread.setPhase(  node.getLifecyclePhase() );
435 
436         MavenSession sessionForThisModule = projectBuild.getSession();
437         try
438         {
439 
440             if ( reactorContext.getReactorBuildStatus().isHaltedOrBlacklisted( currentProject ) )
441             {
442                 return;
443             }
444 
445             BuilderCommon.attachToThread( currentProject );
446 
447             mojoExecutor.execute( sessionForThisModule, node.getMojoExecution(), reactorContext.getProjectIndex(),
448                                   dependencyContext, phaseRecorder );
449 
450             final BuildSuccess summary =
451                 new BuildSuccess( currentProject, System.currentTimeMillis() - buildStartTime );
452             reactorContext.getResult().addBuildSummary( summary );
453         }
454         finally
455         {
456             Thread.currentThread().setContextClassLoader( reactorContext.getOriginalContextClassLoader() );
457         }
458     }
459 
460     public static boolean isWeaveMode( MavenExecutionRequest request )
461     {
462         return "true".equals( request.getUserProperties().getProperty( "maven3.weaveMode" ) );
463     }
464 
465     public static void setWeaveMode( Properties properties )
466     {
467         properties.setProperty( "maven3.weaveMode", "true" );
468     }
469 
470     static class ArtifactLink
471     {
472         private final Artifact artifactInThis;
473 
474         private final Artifact upstream;
475 
476         ArtifactLink( Artifact artifactInThis, Artifact upstream )
477         {
478             this.artifactInThis = artifactInThis;
479             this.upstream = upstream;
480         }
481 
482         public void resolveFromUpstream()
483         {
484             artifactInThis.setFile( upstream.getFile() );
485             artifactInThis.setRepository( upstream.getRepository() );
486             artifactInThis.setResolved( true ); // Or maybe upstream.isResolved()....
487 
488         }
489     }
490 
491 }