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      public LifecycleWeaveBuilder()
91      {
92      }
93  
94      public LifecycleWeaveBuilder( MojoExecutor mojoExecutor, BuilderCommon builderCommon, Logger logger,
95                                    ExecutionEventCatapult eventCatapult )
96      {
97          this.mojoExecutor = mojoExecutor;
98          this.builderCommon = builderCommon;
99          this.logger = logger;
100         this.eventCatapult = eventCatapult;
101     }
102 
103     public void build( ProjectBuildList projectBuilds, ReactorContext buildContext, List<TaskSegment> taskSegments,
104                        MavenSession session, ExecutorService executor, ReactorBuildStatus reactorBuildStatus )
105         throws ExecutionException, InterruptedException
106     {
107         ConcurrentBuildLogger concurrentBuildLogger = new ConcurrentBuildLogger();
108         CompletionService<ProjectSegment> service = new ExecutorCompletionService<ProjectSegment>( executor );
109 
110         try
111         {
112             for ( MavenProject mavenProject : session.getProjects() )
113             {
114                 Artifact mainArtifact = mavenProject.getArtifact();
115                 if ( mainArtifact != null && !( mainArtifact instanceof ThreadLockedArtifact ) )
116                 {
117                     ThreadLockedArtifact threadLockedArtifact = new ThreadLockedArtifact( mainArtifact );
118                     mavenProject.setArtifact( threadLockedArtifact );
119                 }
120             }
121 
122             final List<Future<ProjectSegment>> futures = new ArrayList<Future<ProjectSegment>>();
123             final Map<ProjectSegment, Future<MavenExecutionPlan>> plans =
124                 new HashMap<ProjectSegment, Future<MavenExecutionPlan>>();
125 
126             for ( TaskSegment taskSegment : taskSegments )
127             {
128                 ProjectBuildList segmentChunks = projectBuilds.getByTaskSegment( taskSegment );
129                 Set<Artifact> projectArtifacts = new HashSet<Artifact>();
130                 for ( ProjectSegment segmentChunk : segmentChunks )
131                 {
132                     Artifact artifact = segmentChunk.getProject().getArtifact();
133                     if ( artifact != null )
134                     {
135                         projectArtifacts.add( artifact );
136                     }
137                 }
138                 for ( ProjectSegment projectBuild : segmentChunks )
139                 {
140                     plans.put( projectBuild, executor.submit( createEPFuture( projectBuild, projectArtifacts ) ) );
141                 }
142 
143                 for ( ProjectSegment projectSegment : plans.keySet() )
144                 {
145                     executionPlans.put( projectSegment.getProject(), plans.get( projectSegment ).get() );
146 
147                 }
148                 for ( ProjectSegment projectBuild : segmentChunks )
149                 {
150                     try
151                     {
152                         final MavenExecutionPlan executionPlan = plans.get( projectBuild ).get();
153 
154                         DependencyContext dependencyContext =
155                             mojoExecutor.newDependencyContext( session, executionPlan.getMojoExecutions() );
156 
157                         final Callable<ProjectSegment> projectBuilder =
158                             createCallableForBuildingOneFullModule( buildContext, session, reactorBuildStatus,
159                                                                     executionPlan, projectBuild, dependencyContext,
160                                                                     concurrentBuildLogger );
161 
162                         futures.add( service.submit( projectBuilder ) );
163                     }
164                     catch ( Exception e )
165                     {
166                         throw new ExecutionException( e );
167                     }
168                 }
169 
170                 for ( Future<ProjectSegment> buildFuture : futures )
171                 {
172                     buildFuture.get();  // At this point, this build *is* finished.
173                     // Do not leak threads past here or evil gremlins will get you!
174                 }
175                 futures.clear();
176             }
177         }
178         finally
179         {
180             projectBuilds.closeAll();
181         }
182         logger.info( concurrentBuildLogger.toString() );
183     }
184 
185     private Callable<MavenExecutionPlan> createEPFuture( final ProjectSegment projectSegment,
186                                                          final Set<Artifact> projectArtifacts )
187     {
188         return new Callable<MavenExecutionPlan>()
189         {
190             public MavenExecutionPlan call()
191                 throws Exception
192             {
193                 return builderCommon.resolveBuildPlan( projectSegment.getSession(), projectSegment.getProject(),
194                                                        projectSegment.getTaskSegment(), projectArtifacts );
195             }
196         };
197     }
198 
199 
200     private Callable<ProjectSegment> createCallableForBuildingOneFullModule( final ReactorContext reactorContext,
201                                                                              final MavenSession rootSession,
202                                                                              final ReactorBuildStatus reactorBuildStatus,
203                                                                              final MavenExecutionPlan executionPlan,
204                                                                              final ProjectSegment projectBuild,
205                                                                              final DependencyContext dependencyContext,
206                                                                              final ConcurrentBuildLogger concurrentBuildLogger )
207     {
208         return new Callable<ProjectSegment>()
209         {
210             public ProjectSegment call()
211                 throws Exception
212             {
213                 Iterator<ExecutionPlanItem> planItems = executionPlan.iterator();
214                 ExecutionPlanItem current = planItems.hasNext() ? planItems.next() : null;
215                 ThreadLockedArtifact threadLockedArtifact =
216                     (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.getSession(),
282                                                     projectBuild.getProject(), e, 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     {
381         return artifact instanceof ThreadLockedArtifact && !( (ThreadLockedArtifact) artifact ).hasReal();
382     }
383 
384     private static Artifact findDependency( MavenProject project, Artifact upStreamArtifact )
385     {
386         if ( upStreamArtifact == null || isThreadLockedAndEmpty( upStreamArtifact ) )
387         {
388             return null;
389         }
390 
391         String key = ArtifactUtils.key( upStreamArtifact.getGroupId(), upStreamArtifact.getArtifactId(),
392                                         upStreamArtifact.getVersion() );
393         final Set<Artifact> deps = project.getDependencyArtifacts();
394         for ( Artifact dep : deps )
395         {
396             String depKey = ArtifactUtils.key( dep.getGroupId(), dep.getArtifactId(), dep.getVersion() );
397             if ( key.equals( depKey ) )
398             {
399                 return dep;
400             }
401         }
402         return null;
403 
404     }
405 
406     private void buildExecutionPlanItem( ExecutionPlanItem current, PhaseRecorder phaseRecorder, Schedule schedule,
407                                          ReactorContext reactorContext, ProjectSegment projectBuild,
408                                          DependencyContext dependencyContext )
409         throws LifecycleExecutionException
410     {
411         if ( schedule != null && schedule.isMojoSynchronized() )
412         {
413             synchronized ( current.getPlugin() )
414             {
415                 buildExecutionPlanItem( reactorContext, current, projectBuild, dependencyContext, phaseRecorder );
416             }
417         }
418         else
419         {
420             buildExecutionPlanItem( reactorContext, current, projectBuild, dependencyContext, phaseRecorder );
421         }
422     }
423 
424 
425     private void buildExecutionPlanItem( ReactorContext reactorContext, ExecutionPlanItem node,
426                                          ProjectSegment projectBuild, DependencyContext dependencyContext,
427                                          PhaseRecorder phaseRecorder )
428         throws LifecycleExecutionException
429     {
430 
431         MavenProject currentProject = projectBuild.getProject();
432 
433         long buildStartTime = System.currentTimeMillis();
434 
435         CurrentPhaseForThread.setPhase(  node.getLifecyclePhase() );
436 
437         MavenSession sessionForThisModule = projectBuild.getSession();
438         try
439         {
440 
441             if ( reactorContext.getReactorBuildStatus().isHaltedOrBlacklisted( currentProject ) )
442             {
443                 return;
444             }
445 
446             BuilderCommon.attachToThread( currentProject );
447 
448             mojoExecutor.execute( sessionForThisModule, node.getMojoExecution(), reactorContext.getProjectIndex(),
449                                   dependencyContext, phaseRecorder );
450 
451             final BuildSuccess summary =
452                 new BuildSuccess( currentProject, System.currentTimeMillis() - buildStartTime );
453             reactorContext.getResult().addBuildSummary( summary );
454         }
455         finally
456         {
457             Thread.currentThread().setContextClassLoader( reactorContext.getOriginalContextClassLoader() );
458         }
459     }
460 
461     public static boolean isWeaveMode( MavenExecutionRequest request )
462     {
463         return "true".equals( request.getUserProperties().getProperty( "maven3.weaveMode" ) );
464     }
465 
466     public static void setWeaveMode( Properties properties )
467     {
468         properties.setProperty( "maven3.weaveMode", "true" );
469     }
470 
471     static class ArtifactLink
472     {
473         private final Artifact artifactInThis;
474 
475         private final Artifact upstream;
476 
477         ArtifactLink( Artifact artifactInThis, Artifact upstream )
478         {
479             this.artifactInThis = artifactInThis;
480             this.upstream = upstream;
481         }
482 
483         public void resolveFromUpstream()
484         {
485             artifactInThis.setFile( upstream.getFile() );
486             artifactInThis.setRepository( upstream.getRepository() );
487             artifactInThis.setResolved( true ); // Or maybe upstream.isResolved()....
488         }
489     }
490 }