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