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.RepositoryUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.eventspy.internal.EventSpyDispatcher;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.project.DefaultDependencyResolutionRequest;
import org.apache.maven.project.DependencyResolutionException;
import org.apache.maven.project.DependencyResolutionResult;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectDependenciesResolver;
import org.apache.maven.project.artifact.InvalidDependencyVersionException;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.Logger;
import org.sonatype.aether.graph.Dependency;
import org.sonatype.aether.graph.DependencyFilter;
import org.sonatype.aether.graph.DependencyNode;
import org.sonatype.aether.util.filter.AndDependencyFilter;
import org.sonatype.aether.util.filter.ScopeDependencyFilter;
import java.util.*;
/**
* Resolves dependencies for the artifacts in context of the lifecycle build
*
* @since 3.0
* @author Benjamin Bentmann
* @author Jason van Zyl
* @author Kristian Rosenvold (extracted class)
* <p/>
* NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
*/
@Component(role = LifecycleDependencyResolver.class)
public class LifecycleDependencyResolver
{
@Requirement
private ProjectDependenciesResolver dependenciesResolver;
@Requirement
private Logger logger;
@Requirement
private ArtifactFactory artifactFactory;
@Requirement
private EventSpyDispatcher eventSpyDispatcher;
public LifecycleDependencyResolver()
{
}
public LifecycleDependencyResolver( ProjectDependenciesResolver projectDependenciesResolver, Logger logger )
{
this.dependenciesResolver = projectDependenciesResolver;
this.logger = logger;
}
public static List<MavenProject> getProjects( MavenProject project, MavenSession session, boolean aggregator )
{
if ( aggregator )
{
return session.getProjects();
}
else
{
return Collections.singletonList( project );
}
}
public void resolveProjectDependencies( MavenProject project, Collection<String> scopesToCollect,
Collection<String> scopesToResolve, MavenSession session,
boolean aggregating, Set<Artifact> projectArtifacts )
throws LifecycleExecutionException
{
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
try
{
ClassLoader projectRealm = project.getClassRealm();
if ( projectRealm != null && projectRealm != tccl )
{
Thread.currentThread().setContextClassLoader( projectRealm );
}
if ( project.getDependencyArtifacts() == null )
{
try
{
project.setDependencyArtifacts( project.createArtifacts( artifactFactory, null, null ) );
}
catch ( InvalidDependencyVersionException e )
{
throw new LifecycleExecutionException( e );
}
}
Set<Artifact> artifacts =
getDependencies( project, scopesToCollect, scopesToResolve, session, aggregating, projectArtifacts );
project.setResolvedArtifacts( artifacts );
Map<String, Artifact> map = new HashMap<String, Artifact>();
for ( Artifact artifact : artifacts )
{
map.put( artifact.getDependencyConflictId(), artifact );
}
for ( Artifact artifact : project.getDependencyArtifacts() )
{
if ( artifact.getFile() == null )
{
Artifact resolved = map.get( artifact.getDependencyConflictId() );
if ( resolved != null )
{
artifact.setFile( resolved.getFile() );
artifact.setDependencyTrail( resolved.getDependencyTrail() );
artifact.setResolvedVersion( resolved.getVersion() );
artifact.setResolved( true );
}
}
}
}
finally
{
Thread.currentThread().setContextClassLoader( tccl );
}
}
private Set<Artifact> getDependencies( MavenProject project, Collection<String> scopesToCollect,
Collection<String> scopesToResolve, MavenSession session,
boolean aggregating, Set<Artifact> projectArtifacts )
throws LifecycleExecutionException
{
if ( scopesToCollect == null )
{
scopesToCollect = Collections.emptySet();
}
if ( scopesToResolve == null )
{
scopesToResolve = Collections.emptySet();
}
if ( scopesToCollect.isEmpty() && scopesToResolve.isEmpty() )
{
return new LinkedHashSet<Artifact>();
}
scopesToCollect = new HashSet<String>( scopesToCollect );
scopesToCollect.addAll( scopesToResolve );
DependencyFilter collectionFilter = new ScopeDependencyFilter( null, negate( scopesToCollect ) );
DependencyFilter resolutionFilter = new ScopeDependencyFilter( null, negate( scopesToResolve ) );
resolutionFilter = AndDependencyFilter.newInstance( collectionFilter, resolutionFilter );
resolutionFilter =
AndDependencyFilter.newInstance( resolutionFilter, new ReactorDependencyFilter( projectArtifacts ) );
DependencyResolutionResult result;
try
{
DefaultDependencyResolutionRequest request =
new DefaultDependencyResolutionRequest( project, session.getRepositorySession() );
request.setResolutionFilter( resolutionFilter );
eventSpyDispatcher.onEvent( request );
result = dependenciesResolver.resolve( request );
}
catch ( DependencyResolutionException e )
{
result = e.getResult();
/*
* MNG-2277, the check below compensates for our bad plugin support where we ended up with aggregator
* plugins that require dependency resolution although they usually run in phases of the build where project
* artifacts haven't been assembled yet. The prime example of this is "mvn release:prepare".
*/
if ( aggregating && areAllDependenciesInReactor( session.getProjects(), result.getUnresolvedDependencies() ) )
{
logger.warn( "The following dependencies could not be resolved at this point of the build"
+ " but seem to be part of the reactor:" );
for ( Dependency dependency : result.getUnresolvedDependencies() )
{
logger.warn( "o " + dependency );
}
logger.warn( "Try running the build up to the lifecycle phase \"package\"" );
}
else
{
throw new LifecycleExecutionException( null, project, e );
}
}
eventSpyDispatcher.onEvent( result );
Set<Artifact> artifacts = new LinkedHashSet<Artifact>();
if ( result.getDependencyGraph() != null && !result.getDependencyGraph().getChildren().isEmpty() )
{
RepositoryUtils.toArtifacts( artifacts, result.getDependencyGraph().getChildren(),
Collections.singletonList( project.getArtifact().getId() ), collectionFilter );
}
return artifacts;
}
private boolean areAllDependenciesInReactor( Collection<MavenProject> projects, Collection<Dependency> dependencies )
{
Set<String> projectKeys = getReactorProjectKeys( projects );
for ( Dependency dependency : dependencies )
{
org.sonatype.aether.artifact.Artifact a = dependency.getArtifact();
String key = ArtifactUtils.key( a.getGroupId(), a.getArtifactId(), a.getVersion() );
if ( !projectKeys.contains( key ) )
{
return false;
}
}
return true;
}
private Set<String> getReactorProjectKeys( Collection<MavenProject> projects )
{
Set<String> projectKeys = new HashSet<String>( projects.size() * 2 );
for ( MavenProject project : projects )
{
String key = ArtifactUtils.key( project.getGroupId(), project.getArtifactId(), project.getVersion() );
projectKeys.add( key );
}
return projectKeys;
}
private Collection<String> negate( Collection<String> scopes )
{
Collection<String> result = new HashSet<String>();
Collections.addAll( result, "system", "compile", "provided", "runtime", "test" );
for ( String scope : scopes )
{
if ( "compile".equals( scope ) )
{
result.remove( "compile" );
result.remove( "system" );
result.remove( "provided" );
}
else if ( "runtime".equals( scope ) )
{
result.remove( "compile" );
result.remove( "runtime" );
}
else if ( "compile+runtime".equals( scope ) )
{
result.remove( "compile" );
result.remove( "system" );
result.remove( "provided" );
result.remove( "runtime" );
}
else if ( "runtime+system".equals( scope ) )
{
result.remove( "compile" );
result.remove( "system" );
result.remove( "runtime" );
}
else if ( "test".equals( scope ) )
{
result.clear();
}
}
return result;
}
private static class ReactorDependencyFilter
implements DependencyFilter
{
private Set<String> keys = new HashSet<String>();
public ReactorDependencyFilter( Collection<Artifact> artifacts )
{
for ( Artifact artifact : artifacts )
{
String key = ArtifactUtils.key( artifact );
keys.add( key );
}
}
public boolean accept( DependencyNode node, List<DependencyNode> parents )
{
Dependency dependency = node.getDependency();
if ( dependency != null )
{
org.sonatype.aether.artifact.Artifact a = dependency.getArtifact();
String key = ArtifactUtils.key( a.getGroupId(), a.getArtifactId(), a.getVersion() );
return !keys.contains( key );
}
return false;
}
}
}