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