| /* |
| * 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; |
| } |
| } |
| } |