blob: 6b1df8b7753671428005a2759217cec7bb8823f5 [file] [log] [blame]
/*
* 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.
*/
package org.apache.maven.lifecycle.internal;
import javax.inject.Inject;
import javax.inject.Named;
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 java.util.stream.Collectors;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.api.services.MessageBuilderFactory;
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
*/
@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;
private final MessageBuilderFactory messageBuilderFactory;
@Inject
public LifecycleDependencyResolver(
ProjectDependenciesResolver dependenciesResolver,
ProjectArtifactFactory artifactFactory,
EventSpyDispatcher eventSpyDispatcher,
ProjectArtifactsCache projectArtifactsCache,
MessageBuilderFactory messageBuilderFactory) {
this.dependenciesResolver = dependenciesResolver;
this.artifactFactory = artifactFactory;
this.eventSpyDispatcher = eventSpyDispatcher;
this.projectArtifactsCache = projectArtifactsCache;
this.messageBuilderFactory = messageBuilderFactory;
}
public static List<MavenProject> getProjects(MavenProject project, MavenSession session, boolean aggregator) {
if (aggregator && project.getCollectedProjects() != null) {
// get the unsorted list of wanted projects
Set<MavenProject> projectAndSubmodules = new HashSet<>(project.getCollectedProjects());
projectAndSubmodules.add(project);
return session.getProjects().stream() // sorted all
.filter(projectAndSubmodules::contains)
.collect(Collectors.toList()); // sorted and filtered to what we need
} 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 = resolveProjectArtifacts(
project, scopesToCollect, scopesToResolve, session, aggregating, projectArtifacts);
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);
}
}
public DependencyResolutionResult getProjectDependencyResolutionResult(
MavenProject project,
Collection<String> scopesToCollect,
Collection<String> scopesToResolve,
MavenSession session,
boolean aggregating,
Set<Artifact> projectArtifacts)
throws LifecycleExecutionException {
Set<Artifact> resolvedArtifacts = resolveProjectArtifacts(
project, scopesToCollect, scopesToResolve, session, aggregating, projectArtifacts);
if (resolvedArtifacts instanceof ProjectArtifactsCache.ArtifactsSetWithResult) {
return ((ProjectArtifactsCache.ArtifactsSetWithResult) resolvedArtifacts).getResult();
} else {
throw new IllegalStateException();
}
}
public Set<Artifact> resolveProjectArtifacts(
MavenProject project,
Collection<String> scopesToCollect,
Collection<String> scopesToResolve,
MavenSession session,
boolean aggregating,
Set<Artifact> projectArtifacts)
throws LifecycleExecutionException {
ProjectArtifactsCache.Key cacheKey = projectArtifactsCache.createKey(
project, scopesToCollect, scopesToResolve, aggregating, session.getRepositorySession());
ProjectArtifactsCache.CacheRecord recordArtifacts;
recordArtifacts = projectArtifactsCache.get(cacheKey);
if (recordArtifacts == null) {
synchronized (cacheKey) {
recordArtifacts = projectArtifactsCache.get(cacheKey);
if (recordArtifacts == null) {
try {
Set<Artifact> 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);
return recordArtifacts.getArtifacts();
}
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 SetWithResolutionResult(null, 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(messageBuilderFactory, 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 new SetWithResolutionResult(result, 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 final 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;
}
}
}