blob: 444f959fb6a195ce01eae77575afe6e15b9c3a57 [file] [log] [blame]
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 java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
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.apache.maven.project.artifact.ProjectArtifactsCache;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.util.filter.AndDependencyFilter;
import org.eclipse.aether.util.filter.ScopeDependencyFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>
* Resolves dependencies for the artifacts in context of the lifecycle build
* </p>
* <strong>NOTE:</strong> This class is not part of any public api and can be changed or deleted without prior notice.
* @since 3.0
* @author Benjamin Bentmann
* @author Jason van Zyl
* @author Kristian Rosenvold (extracted class)
*/
@Named
public class LifecycleDependencyResolver
{
private final Logger logger = LoggerFactory.getLogger( getClass() );
private final ProjectDependenciesResolver dependenciesResolver;
private final ProjectArtifactFactory artifactFactory;
private final EventSpyDispatcher eventSpyDispatcher;
private final ProjectArtifactsCache projectArtifactsCache;
@Inject
public LifecycleDependencyResolver(
ProjectDependenciesResolver dependenciesResolver,
ProjectArtifactFactory artifactFactory,
EventSpyDispatcher eventSpyDispatcher,
ProjectArtifactsCache projectArtifactsCache )
{
this.dependenciesResolver = dependenciesResolver;
this.artifactFactory = artifactFactory;
this.eventSpyDispatcher = eventSpyDispatcher;
this.projectArtifactsCache = projectArtifactsCache;
}
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( artifactFactory.createArtifacts( project ) );
}
catch ( InvalidDependencyVersionException e )
{
throw new LifecycleExecutionException( e );
}
}
Set<Artifact> resolvedArtifacts;
ProjectArtifactsCache.Key cacheKey = projectArtifactsCache.createKey( project, scopesToCollect,
scopesToResolve, aggregating, session.getRepositorySession() );
ProjectArtifactsCache.CacheRecord recordArtifacts;
recordArtifacts = projectArtifactsCache.get( cacheKey );
if ( recordArtifacts != null )
{
resolvedArtifacts = recordArtifacts.getArtifacts();
}
else
{
try
{
resolvedArtifacts = getDependencies( project, scopesToCollect, scopesToResolve, session,
aggregating, projectArtifacts );
recordArtifacts = projectArtifactsCache.put( cacheKey, resolvedArtifacts );
}
catch ( LifecycleExecutionException e )
{
projectArtifactsCache.put( cacheKey, e );
projectArtifactsCache.register( project, cacheKey, recordArtifacts );
throw e;
}
}
projectArtifactsCache.register( project, cacheKey, recordArtifacts );
Map<Artifact, File> reactorProjects = new HashMap<>( session.getProjects().size() );
for ( MavenProject reactorProject : session.getProjects() )
{
reactorProjects.put( reactorProject.getArtifact(), reactorProject.getArtifact().getFile() );
}
Map<String, Artifact> map = new HashMap<>();
for ( Artifact artifact : resolvedArtifacts )
{
/**
* MNG-6300: resolvedArtifacts can be cache result; this ensures reactor files are always up to date
* During lifecycle the Artifact.getFile() can change from target/classes to the actual jar.
* This clearly shows that target/classes should not be abused as artifactFile just for the classpath
*/
File reactorProjectFile = reactorProjects.get( artifact );
if ( reactorProjectFile != null )
{
artifact.setFile( reactorProjectFile );
}
map.put( artifact.getDependencyConflictId(), artifact );
}
project.setResolvedArtifacts( resolvedArtifacts );
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<>();
}
scopesToCollect = new HashSet<>( 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<>();
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.eclipse.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<>( 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<>();
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<>();
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.eclipse.aether.artifact.Artifact a = dependency.getArtifact();
String key = ArtifactUtils.key( a.getGroupId(), a.getArtifactId(), a.getVersion() );
return !keys.contains( key );
}
return false;
}
}
}