View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.lifecycle.internal;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  
24  import java.io.File;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.LinkedHashSet;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  import java.util.stream.Collectors;
34  
35  import org.apache.maven.RepositoryUtils;
36  import org.apache.maven.artifact.Artifact;
37  import org.apache.maven.artifact.ArtifactUtils;
38  import org.apache.maven.eventspy.internal.EventSpyDispatcher;
39  import org.apache.maven.execution.MavenSession;
40  import org.apache.maven.lifecycle.LifecycleExecutionException;
41  import org.apache.maven.project.DefaultDependencyResolutionRequest;
42  import org.apache.maven.project.DependencyResolutionException;
43  import org.apache.maven.project.DependencyResolutionResult;
44  import org.apache.maven.project.MavenProject;
45  import org.apache.maven.project.ProjectDependenciesResolver;
46  import org.apache.maven.project.artifact.InvalidDependencyVersionException;
47  import org.apache.maven.project.artifact.ProjectArtifactsCache;
48  import org.codehaus.plexus.logging.Logger;
49  import org.eclipse.aether.graph.Dependency;
50  import org.eclipse.aether.graph.DependencyFilter;
51  import org.eclipse.aether.graph.DependencyNode;
52  import org.eclipse.aether.util.filter.AndDependencyFilter;
53  import org.eclipse.aether.util.filter.ScopeDependencyFilter;
54  
55  /**
56   * <p>
57   * Resolves dependencies for the artifacts in context of the lifecycle build
58   * </p>
59   * <strong>NOTE:</strong> This class is not part of any public api and can be changed or deleted without prior notice.
60   * @since 3.0
61   * @author Benjamin Bentmann
62   * @author Jason van Zyl
63   * @author Kristian Rosenvold (extracted class)
64   */
65  @Named
66  public class LifecycleDependencyResolver {
67  
68      @Inject
69      private ProjectDependenciesResolver dependenciesResolver;
70  
71      @Inject
72      private Logger logger;
73  
74      @Inject
75      private ProjectArtifactFactory artifactFactory;
76  
77      @Inject
78      private EventSpyDispatcher eventSpyDispatcher;
79  
80      @Inject
81      private ProjectArtifactsCache projectArtifactsCache;
82  
83      public LifecycleDependencyResolver() {}
84  
85      public LifecycleDependencyResolver(ProjectDependenciesResolver projectDependenciesResolver, Logger logger) {
86          this.dependenciesResolver = projectDependenciesResolver;
87          this.logger = logger;
88      }
89  
90      public static List<MavenProject> getProjects(MavenProject project, MavenSession session, boolean aggregator) {
91          if (aggregator && project.getCollectedProjects() != null) {
92              // get the unsorted list of wanted projects
93              Set<MavenProject> projectAndSubmodules = new HashSet<>(project.getCollectedProjects());
94              projectAndSubmodules.add(project);
95              return session.getProjects().stream() // sorted all
96                      .filter(projectAndSubmodules::contains)
97                      .collect(Collectors.toList()); // sorted and filtered to what we need
98          } else {
99              return Collections.singletonList(project);
100         }
101     }
102 
103     public void resolveProjectDependencies(
104             MavenProject project,
105             Collection<String> scopesToCollect,
106             Collection<String> scopesToResolve,
107             MavenSession session,
108             boolean aggregating,
109             Set<Artifact> projectArtifacts)
110             throws LifecycleExecutionException {
111         ClassLoader tccl = Thread.currentThread().getContextClassLoader();
112         try {
113             ClassLoader projectRealm = project.getClassRealm();
114             if (projectRealm != null && projectRealm != tccl) {
115                 Thread.currentThread().setContextClassLoader(projectRealm);
116             }
117 
118             if (project.getDependencyArtifacts() == null) {
119                 try {
120                     project.setDependencyArtifacts(artifactFactory.createArtifacts(project));
121                 } catch (InvalidDependencyVersionException e) {
122                     throw new LifecycleExecutionException(e);
123                 }
124             }
125 
126             ProjectArtifactsCache.Key cacheKey = projectArtifactsCache.createKey(
127                     project, scopesToCollect, scopesToResolve, aggregating, session.getRepositorySession());
128             ProjectArtifactsCache.CacheRecord recordArtifacts;
129             recordArtifacts = projectArtifactsCache.get(cacheKey);
130 
131             if (recordArtifacts == null) {
132                 synchronized (cacheKey) {
133                     recordArtifacts = projectArtifactsCache.get(cacheKey);
134                     if (recordArtifacts == null) {
135                         try {
136                             Set<Artifact> resolvedArtifacts = getDependencies(
137                                     project, scopesToCollect, scopesToResolve, session, aggregating, projectArtifacts);
138                             recordArtifacts = projectArtifactsCache.put(cacheKey, resolvedArtifacts);
139                         } catch (LifecycleExecutionException e) {
140                             projectArtifactsCache.put(cacheKey, e);
141                             projectArtifactsCache.register(project, cacheKey, recordArtifacts);
142                             throw e;
143                         }
144                     }
145                 }
146             }
147             projectArtifactsCache.register(project, cacheKey, recordArtifacts);
148             Set<Artifact> resolvedArtifacts = recordArtifacts.getArtifacts();
149 
150             Map<Artifact, File> reactorProjects =
151                     new HashMap<>(session.getProjects().size());
152             for (MavenProject reactorProject : session.getProjects()) {
153                 reactorProjects.put(
154                         reactorProject.getArtifact(),
155                         reactorProject.getArtifact().getFile());
156             }
157 
158             Map<String, Artifact> map = new HashMap<>();
159             for (Artifact artifact : resolvedArtifacts) {
160                 /**
161                  * MNG-6300: resolvedArtifacts can be cache result; this ensures reactor files are always up to date
162                  * During lifecycle the Artifact.getFile() can change from target/classes to the actual jar.
163                  * This clearly shows that target/classes should not be abused as artifactFile just for the classpath
164                  */
165                 File reactorProjectFile = reactorProjects.get(artifact);
166                 if (reactorProjectFile != null) {
167                     artifact.setFile(reactorProjectFile);
168                 }
169 
170                 map.put(artifact.getDependencyConflictId(), artifact);
171             }
172 
173             project.setResolvedArtifacts(resolvedArtifacts);
174 
175             for (Artifact artifact : project.getDependencyArtifacts()) {
176                 if (artifact.getFile() == null) {
177                     Artifact resolved = map.get(artifact.getDependencyConflictId());
178                     if (resolved != null) {
179                         artifact.setFile(resolved.getFile());
180                         artifact.setDependencyTrail(resolved.getDependencyTrail());
181                         artifact.setResolvedVersion(resolved.getVersion());
182                         artifact.setResolved(true);
183                     }
184                 }
185             }
186         } finally {
187             Thread.currentThread().setContextClassLoader(tccl);
188         }
189     }
190 
191     private Set<Artifact> getDependencies(
192             MavenProject project,
193             Collection<String> scopesToCollect,
194             Collection<String> scopesToResolve,
195             MavenSession session,
196             boolean aggregating,
197             Set<Artifact> projectArtifacts)
198             throws LifecycleExecutionException {
199         if (scopesToCollect == null) {
200             scopesToCollect = Collections.emptySet();
201         }
202         if (scopesToResolve == null) {
203             scopesToResolve = Collections.emptySet();
204         }
205 
206         if (scopesToCollect.isEmpty() && scopesToResolve.isEmpty()) {
207             return new LinkedHashSet<>();
208         }
209 
210         scopesToCollect = new HashSet<>(scopesToCollect);
211         scopesToCollect.addAll(scopesToResolve);
212 
213         DependencyFilter collectionFilter = new ScopeDependencyFilter(null, negate(scopesToCollect));
214         DependencyFilter resolutionFilter = new ScopeDependencyFilter(null, negate(scopesToResolve));
215         resolutionFilter = AndDependencyFilter.newInstance(collectionFilter, resolutionFilter);
216         resolutionFilter =
217                 AndDependencyFilter.newInstance(resolutionFilter, new ReactorDependencyFilter(projectArtifacts));
218 
219         DependencyResolutionResult result;
220         try {
221             DefaultDependencyResolutionRequest request =
222                     new DefaultDependencyResolutionRequest(project, session.getRepositorySession());
223             request.setResolutionFilter(resolutionFilter);
224 
225             eventSpyDispatcher.onEvent(request);
226 
227             result = dependenciesResolver.resolve(request);
228         } catch (DependencyResolutionException e) {
229             result = e.getResult();
230 
231             /*
232              * MNG-2277, the check below compensates for our bad plugin support where we ended up with aggregator
233              * plugins that require dependency resolution although they usually run in phases of the build where project
234              * artifacts haven't been assembled yet. The prime example of this is "mvn release:prepare".
235              */
236             if (aggregating && areAllDependenciesInReactor(session.getProjects(), result.getUnresolvedDependencies())) {
237                 logger.warn("The following dependencies could not be resolved at this point of the build"
238                         + " but seem to be part of the reactor:");
239 
240                 for (Dependency dependency : result.getUnresolvedDependencies()) {
241                     logger.warn("o " + dependency);
242                 }
243 
244                 logger.warn("Try running the build up to the lifecycle phase \"package\"");
245             } else {
246                 throw new LifecycleExecutionException(null, project, e);
247             }
248         }
249 
250         eventSpyDispatcher.onEvent(result);
251 
252         Set<Artifact> artifacts = new LinkedHashSet<>();
253         if (result.getDependencyGraph() != null
254                 && !result.getDependencyGraph().getChildren().isEmpty()) {
255             RepositoryUtils.toArtifacts(
256                     artifacts,
257                     result.getDependencyGraph().getChildren(),
258                     Collections.singletonList(project.getArtifact().getId()),
259                     collectionFilter);
260         }
261         return artifacts;
262     }
263 
264     private boolean areAllDependenciesInReactor(
265             Collection<MavenProject> projects, Collection<Dependency> dependencies) {
266         Set<String> projectKeys = getReactorProjectKeys(projects);
267 
268         for (Dependency dependency : dependencies) {
269             org.eclipse.aether.artifact.Artifact a = dependency.getArtifact();
270             String key = ArtifactUtils.key(a.getGroupId(), a.getArtifactId(), a.getVersion());
271             if (!projectKeys.contains(key)) {
272                 return false;
273             }
274         }
275 
276         return true;
277     }
278 
279     private Set<String> getReactorProjectKeys(Collection<MavenProject> projects) {
280         Set<String> projectKeys = new HashSet<>(projects.size() * 2);
281         for (MavenProject project : projects) {
282             String key = ArtifactUtils.key(project.getGroupId(), project.getArtifactId(), project.getVersion());
283             projectKeys.add(key);
284         }
285         return projectKeys;
286     }
287 
288     private Collection<String> negate(Collection<String> scopes) {
289         Collection<String> result = new HashSet<>();
290         Collections.addAll(result, "system", "compile", "provided", "runtime", "test");
291 
292         for (String scope : scopes) {
293             if ("compile".equals(scope)) {
294                 result.remove("compile");
295                 result.remove("system");
296                 result.remove("provided");
297             } else if ("runtime".equals(scope)) {
298                 result.remove("compile");
299                 result.remove("runtime");
300             } else if ("compile+runtime".equals(scope)) {
301                 result.remove("compile");
302                 result.remove("system");
303                 result.remove("provided");
304                 result.remove("runtime");
305             } else if ("runtime+system".equals(scope)) {
306                 result.remove("compile");
307                 result.remove("system");
308                 result.remove("runtime");
309             } else if ("test".equals(scope)) {
310                 result.clear();
311             }
312         }
313 
314         return result;
315     }
316 
317     private static class ReactorDependencyFilter implements DependencyFilter {
318 
319         private Set<String> keys = new HashSet<>();
320 
321         ReactorDependencyFilter(Collection<Artifact> artifacts) {
322             for (Artifact artifact : artifacts) {
323                 String key = ArtifactUtils.key(artifact);
324                 keys.add(key);
325             }
326         }
327 
328         public boolean accept(DependencyNode node, List<DependencyNode> parents) {
329             Dependency dependency = node.getDependency();
330             if (dependency != null) {
331                 org.eclipse.aether.artifact.Artifact a = dependency.getArtifact();
332                 String key = ArtifactUtils.key(a.getGroupId(), a.getArtifactId(), a.getVersion());
333                 return !keys.contains(key);
334             }
335             return false;
336         }
337     }
338 }