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 public LifecycleWeaveBuilder()
091 {
092 }
093
094 public LifecycleWeaveBuilder( MojoExecutor mojoExecutor, BuilderCommon builderCommon, Logger logger,
095 ExecutionEventCatapult eventCatapult )
096 {
097 this.mojoExecutor = mojoExecutor;
098 this.builderCommon = builderCommon;
099 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 }