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