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