package org.apache.maven.lifecycle.internal;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.execution.BuildSuccess;
import org.apache.maven.execution.ExecutionEvent;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.lifecycle.MavenExecutionPlan;
import org.apache.maven.lifecycle.Schedule;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.Logger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
/**
* Builds the full lifecycle in weave-mode (phase by phase as opposed to project-by-project)
* <p/>
* NOTE: Weave mode is still experimental. It may be either promoted to first class citizen
* at some later point in time, and it may also be removed entirely. Weave mode has much more aggressive
* concurrency behaviour than regular threaded mode, and as such is still under test wrt cross platform stability.
* <p/>
* To remove weave mode from m3, the following should be removed:
* ExecutionPlanItem.schedule w/setters and getters
* DefaultLifeCycles.getScheduling() and all its use
* ReactorArtifactRepository has a reference to isWeave too.
* This class and its usage
*
* @since 3.0
* @author Kristian Rosenvold
* Builds one or more lifecycles for a full module
* <p/>
* NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
*/
@Component( role = LifecycleWeaveBuilder.class )
public class LifecycleWeaveBuilder
{
@Requirement
private MojoExecutor mojoExecutor;
@Requirement
private BuilderCommon builderCommon;
@Requirement
private Logger logger;
@Requirement
private ExecutionEventCatapult eventCatapult;
private Map<MavenProject, MavenExecutionPlan> executionPlans = new HashMap<MavenProject, MavenExecutionPlan>();
public LifecycleWeaveBuilder()
{
}
public LifecycleWeaveBuilder( MojoExecutor mojoExecutor, BuilderCommon builderCommon, Logger logger,
ExecutionEventCatapult eventCatapult )
{
this.mojoExecutor = mojoExecutor;
this.builderCommon = builderCommon;
this.logger = logger;
this.eventCatapult = eventCatapult;
}
public void build( ProjectBuildList projectBuilds, ReactorContext buildContext, List<TaskSegment> taskSegments,
MavenSession session, ExecutorService executor, ReactorBuildStatus reactorBuildStatus )
throws ExecutionException, InterruptedException
{
ConcurrentBuildLogger concurrentBuildLogger = new ConcurrentBuildLogger();
CompletionService<ProjectSegment> service = new ExecutorCompletionService<ProjectSegment>( executor );
try
{
for ( MavenProject mavenProject : session.getProjects() )
{
Artifact mainArtifact = mavenProject.getArtifact();
if ( mainArtifact != null && !( mainArtifact instanceof ThreadLockedArtifact ) )
{
ThreadLockedArtifact threadLockedArtifact = new ThreadLockedArtifact( mainArtifact );
mavenProject.setArtifact( threadLockedArtifact );
}
}
final List<Future<ProjectSegment>> futures = new ArrayList<Future<ProjectSegment>>();
final Map<ProjectSegment, Future<MavenExecutionPlan>> plans =
new HashMap<ProjectSegment, Future<MavenExecutionPlan>>();
for ( TaskSegment taskSegment : taskSegments )
{
ProjectBuildList segmentChunks = projectBuilds.getByTaskSegment( taskSegment );
Set<Artifact> projectArtifacts = new HashSet<Artifact>();
for ( ProjectSegment segmentChunk : segmentChunks )
{
Artifact artifact = segmentChunk.getProject().getArtifact();
if ( artifact != null )
{
projectArtifacts.add( artifact );
}
}
for ( ProjectSegment projectBuild : segmentChunks )
{
plans.put( projectBuild, executor.submit( createEPFuture( projectBuild, projectArtifacts ) ) );
}
for ( ProjectSegment projectSegment : plans.keySet() )
{
executionPlans.put( projectSegment.getProject(), plans.get( projectSegment ).get() );
}
for ( ProjectSegment projectBuild : segmentChunks )
{
try
{
final MavenExecutionPlan executionPlan = plans.get( projectBuild ).get();
DependencyContext dependencyContext =
mojoExecutor.newDependencyContext( session, executionPlan.getMojoExecutions() );
final Callable<ProjectSegment> projectBuilder =
createCallableForBuildingOneFullModule( buildContext, session, reactorBuildStatus,
executionPlan, projectBuild, dependencyContext,
concurrentBuildLogger );
futures.add( service.submit( projectBuilder ) );
}
catch ( Exception e )
{
throw new ExecutionException( e );
}
}
for ( Future<ProjectSegment> buildFuture : futures )
{
buildFuture.get(); // At this point, this build *is* finished.
// Do not leak threads past here or evil gremlins will get you!
}
futures.clear();
}
}
finally
{
projectBuilds.closeAll();
}
logger.info( concurrentBuildLogger.toString() );
}
private Callable<MavenExecutionPlan> createEPFuture( final ProjectSegment projectSegment,
final Set<Artifact> projectArtifacts )
{
return new Callable<MavenExecutionPlan>()
{
public MavenExecutionPlan call()
throws Exception
{
return builderCommon.resolveBuildPlan( projectSegment.getSession(), projectSegment.getProject(),
projectSegment.getTaskSegment(), projectArtifacts );
}
};
}
private Callable<ProjectSegment> createCallableForBuildingOneFullModule( final ReactorContext reactorContext,
final MavenSession rootSession,
final ReactorBuildStatus reactorBuildStatus,
final MavenExecutionPlan executionPlan,
final ProjectSegment projectBuild,
final DependencyContext dependencyContext,
final ConcurrentBuildLogger concurrentBuildLogger )
{
return new Callable<ProjectSegment>()
{
public ProjectSegment call()
throws Exception
{
Iterator<ExecutionPlanItem> planItems = executionPlan.iterator();
ExecutionPlanItem current = planItems.hasNext() ? planItems.next() : null;
ThreadLockedArtifact threadLockedArtifact = (ThreadLockedArtifact)projectBuild.getProject().getArtifact();
if ( threadLockedArtifact != null )
{
threadLockedArtifact.attachToThread();
}
long buildStartTime = System.currentTimeMillis();
//muxer.associateThreadWithProjectSegment( projectBuild );
if ( reactorBuildStatus.isHaltedOrBlacklisted( projectBuild.getProject() ) )
{
eventCatapult.fire( ExecutionEvent.Type.ProjectSkipped, projectBuild.getSession(), null );
return null;
}
eventCatapult.fire( ExecutionEvent.Type.ProjectStarted, projectBuild.getSession(), null );
Collection<ArtifactLink> dependencyLinks = getUpstreamReactorDependencies( projectBuild );
try
{
PhaseRecorder phaseRecorder = new PhaseRecorder( projectBuild.getProject() );
long totalMojoTime = 0;
long mojoStart;
while ( current != null && !reactorBuildStatus.isHaltedOrBlacklisted( projectBuild.getProject() ) )
{
BuildLogItem builtLogItem =
concurrentBuildLogger.createBuildLogItem( projectBuild.getProject(), current );
final Schedule schedule = current.getSchedule();
mojoStart = System.currentTimeMillis();
buildExecutionPlanItem( current, phaseRecorder, schedule, reactorContext, projectBuild,
dependencyContext );
totalMojoTime += ( System.currentTimeMillis() - mojoStart );
current.setComplete();
builtLogItem.setComplete();
ExecutionPlanItem nextPlanItem = planItems.hasNext() ? planItems.next() : null;
if ( nextPlanItem != null && phaseRecorder.isDifferentPhase( nextPlanItem.getMojoExecution() ) )
{
final Schedule scheduleOfNext = nextPlanItem.getSchedule();
if ( scheduleOfNext == null || !scheduleOfNext.isParallel() )
{
waitForAppropriateUpstreamExecutionsToFinish( builtLogItem, nextPlanItem, projectBuild,
scheduleOfNext );
}
for ( ArtifactLink dependencyLink : dependencyLinks )
{
dependencyLink.resolveFromUpstream();
}
}
current = nextPlanItem;
}
final BuildSuccess summary =
new BuildSuccess( projectBuild.getProject(), totalMojoTime ); // - waitingTime
reactorContext.getResult().addBuildSummary( summary );
eventCatapult.fire( ExecutionEvent.Type.ProjectSucceeded, projectBuild.getSession(), null );
}
catch ( Exception e )
{
builderCommon.handleBuildError( reactorContext, rootSession, projectBuild.getProject(), e,
buildStartTime );
}
finally
{
if ( current != null )
{
executionPlan.forceAllComplete();
}
// muxer.setThisModuleComplete( projectBuild );
}
return null;
}
};
}
private void waitForAppropriateUpstreamExecutionsToFinish( BuildLogItem builtLogItem,
ExecutionPlanItem nextPlanItem,
ProjectSegment projectBuild, Schedule scheduleOfNext )
throws InterruptedException
{
for ( MavenProject upstreamProject : projectBuild.getImmediateUpstreamProjects() )
{
final MavenExecutionPlan upstreamPlan = executionPlans.get( upstreamProject );
final String nextPhase = scheduleOfNext != null && scheduleOfNext.hasUpstreamPhaseDefined()
? scheduleOfNext.getUpstreamPhase()
: nextPlanItem.getLifecyclePhase();
final ExecutionPlanItem upstream = upstreamPlan.findLastInPhase( nextPhase );
if ( upstream != null )
{
long startWait = System.currentTimeMillis();
upstream.waitUntilDone();
builtLogItem.addWait( upstreamProject, upstream, startWait );
}
else if ( !upstreamPlan.containsPhase( nextPhase ) )
{
// Still a bit of a kludge; if we cannot connect in a sensible way to
// the upstream build plan we just revert to waiting for it all to
// complete. Real problem is per-mojo phase->lifecycle mapping
builtLogItem.addDependency( upstreamProject, "No phase tracking possible " );
upstreamPlan.waitUntilAllDone();
}
else
{
builtLogItem.addDependency( upstreamProject, "No schedule" );
}
}
}
private Collection<ArtifactLink> getUpstreamReactorDependencies( ProjectSegment projectBuild )
{
Collection<ArtifactLink> result = new ArrayList<ArtifactLink>();
for ( MavenProject upstreamProject : projectBuild.getTransitiveUpstreamProjects() )
{
Artifact upStreamArtifact = upstreamProject.getArtifact();
if ( upStreamArtifact != null )
{
Artifact dependencyArtifact = findDependency( projectBuild.getProject(), upStreamArtifact );
if ( dependencyArtifact != null )
{
result.add( new ArtifactLink( dependencyArtifact, upStreamArtifact ) );
}
}
Artifact upStreamTestScopedArtifact = findTestScopedArtifact( upstreamProject );
if ( upStreamTestScopedArtifact != null )
{
Artifact dependencyArtifact = findDependency( projectBuild.getProject(), upStreamArtifact );
if ( dependencyArtifact != null )
{
result.add( new ArtifactLink( dependencyArtifact, upStreamTestScopedArtifact ) );
}
}
}
return result;
}
private Artifact findTestScopedArtifact( MavenProject upstreamProject )
{
if ( upstreamProject == null )
{
return null;
}
List<Artifact> artifactList = upstreamProject.getAttachedArtifacts();
for ( Artifact artifact : artifactList )
{
if ( Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
{
return artifact;
}
}
return null;
}
private static boolean isThreadLockedAndEmpty(Artifact artifact){
return artifact instanceof ThreadLockedArtifact && !((ThreadLockedArtifact) artifact).hasReal();
}
private static Artifact findDependency( MavenProject project, Artifact upStreamArtifact )
{
if ( upStreamArtifact == null || isThreadLockedAndEmpty(upStreamArtifact))
{
return null;
}
String key = ArtifactUtils.key( upStreamArtifact.getGroupId(), upStreamArtifact.getArtifactId(),
upStreamArtifact.getVersion() );
final Set<Artifact> deps = project.getDependencyArtifacts();
for ( Artifact dep : deps )
{
String depKey = ArtifactUtils.key( dep.getGroupId(), dep.getArtifactId(), dep.getVersion() );
if ( key.equals( depKey ) )
{
return dep;
}
}
return null;
}
private void buildExecutionPlanItem( ExecutionPlanItem current, PhaseRecorder phaseRecorder, Schedule schedule,
ReactorContext reactorContext, ProjectSegment projectBuild,
DependencyContext dependencyContext )
throws LifecycleExecutionException
{
if ( schedule != null && schedule.isMojoSynchronized() )
{
synchronized ( current.getPlugin() )
{
buildExecutionPlanItem( reactorContext, current, projectBuild, dependencyContext, phaseRecorder );
}
}
else
{
buildExecutionPlanItem( reactorContext, current, projectBuild, dependencyContext, phaseRecorder );
}
}
private void buildExecutionPlanItem( ReactorContext reactorContext, ExecutionPlanItem node,
ProjectSegment projectBuild, DependencyContext dependencyContext,
PhaseRecorder phaseRecorder )
throws LifecycleExecutionException
{
MavenProject currentProject = projectBuild.getProject();
long buildStartTime = System.currentTimeMillis();
CurrentPhaseForThread.setPhase( node.getLifecyclePhase() );
MavenSession sessionForThisModule = projectBuild.getSession();
try
{
if ( reactorContext.getReactorBuildStatus().isHaltedOrBlacklisted( currentProject ) )
{
return;
}
BuilderCommon.attachToThread( currentProject );
mojoExecutor.execute( sessionForThisModule, node.getMojoExecution(), reactorContext.getProjectIndex(),
dependencyContext, phaseRecorder );
final BuildSuccess summary =
new BuildSuccess( currentProject, System.currentTimeMillis() - buildStartTime );
reactorContext.getResult().addBuildSummary( summary );
}
finally
{
Thread.currentThread().setContextClassLoader( reactorContext.getOriginalContextClassLoader() );
}
}
public static boolean isWeaveMode( MavenExecutionRequest request )
{
return "true".equals( request.getUserProperties().getProperty( "maven3.weaveMode" ) );
}
public static void setWeaveMode( Properties properties )
{
properties.setProperty( "maven3.weaveMode", "true" );
}
static class ArtifactLink
{
private final Artifact artifactInThis;
private final Artifact upstream;
ArtifactLink( Artifact artifactInThis, Artifact upstream )
{
this.artifactInThis = artifactInThis;
this.upstream = upstream;
}
public void resolveFromUpstream()
{
artifactInThis.setFile( upstream.getFile() );
artifactInThis.setRepository( upstream.getRepository() );
artifactInThis.setResolved( true ); // Or maybe upstream.isResolved()....
}
}
}