1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
59
60
61
62
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
95 Set<MavenProject> projectAndSubmodules = new HashSet<>(project.getCollectedProjects());
96 projectAndSubmodules.add(project);
97 return session.getProjects().stream()
98 .filter(projectAndSubmodules::contains)
99 .collect(Collectors.toList());
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
143
144
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
266
267
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 }