001 package org.apache.maven.lifecycle.internal;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements. See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership. The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License. You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied. See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022 import org.apache.maven.artifact.Artifact;
023 import org.apache.maven.artifact.ArtifactUtils;
024 import org.apache.maven.execution.BuildSuccess;
025 import org.apache.maven.execution.ExecutionEvent;
026 import org.apache.maven.execution.MavenExecutionRequest;
027 import org.apache.maven.execution.MavenSession;
028 import org.apache.maven.lifecycle.LifecycleExecutionException;
029 import org.apache.maven.lifecycle.MavenExecutionPlan;
030 import org.apache.maven.lifecycle.Schedule;
031 import org.apache.maven.project.MavenProject;
032 import org.codehaus.plexus.component.annotations.Component;
033 import org.codehaus.plexus.component.annotations.Requirement;
034 import org.codehaus.plexus.logging.Logger;
035
036 import java.util.ArrayList;
037 import java.util.Collection;
038 import java.util.HashMap;
039 import java.util.HashSet;
040 import java.util.Iterator;
041 import java.util.List;
042 import java.util.Map;
043 import java.util.Properties;
044 import java.util.Set;
045 import java.util.concurrent.Callable;
046 import java.util.concurrent.CompletionService;
047 import java.util.concurrent.ExecutionException;
048 import java.util.concurrent.ExecutorCompletionService;
049 import java.util.concurrent.ExecutorService;
050 import java.util.concurrent.Future;
051
052 /**
053 * Builds the full lifecycle in weave-mode (phase by phase as opposed to project-by-project)
054 * <p/>
055 * NOTE: Weave mode is still experimental. It may be either promoted to first class citizen
056 * at some later point in time, and it may also be removed entirely. Weave mode has much more aggressive
057 * concurrency behaviour than regular threaded mode, and as such is still under test wrt cross platform stability.
058 * <p/>
059 * To remove weave mode from m3, the following should be removed:
060 * ExecutionPlanItem.schedule w/setters and getters
061 * DefaultLifeCycles.getScheduling() and all its use
062 * ReactorArtifactRepository has a reference to isWeave too.
063 * This class and its usage
064 *
065 * @since 3.0
066 * @author Kristian Rosenvold
067 * Builds one or more lifecycles for a full module
068 * <p/>
069 * NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
070 */
071 @Component( role = LifecycleWeaveBuilder.class )
072 public class LifecycleWeaveBuilder
073 {
074
075 @Requirement
076 private MojoExecutor mojoExecutor;
077
078 @Requirement
079 private BuilderCommon builderCommon;
080
081 @Requirement
082 private Logger logger;
083
084 @Requirement
085 private ExecutionEventCatapult eventCatapult;
086
087 private Map<MavenProject, MavenExecutionPlan> executionPlans = new HashMap<MavenProject, MavenExecutionPlan>();
088
089
090 @SuppressWarnings( { "UnusedDeclaration" } )
091 public LifecycleWeaveBuilder()
092 {
093 }
094
095 public LifecycleWeaveBuilder( MojoExecutor mojoExecutor, BuilderCommon builderCommon, Logger logger,
096 ExecutionEventCatapult eventCatapult )
097 {
098 this.mojoExecutor = mojoExecutor;
099 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 }