1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.graph;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.io.File;
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Set;
34
35 import org.apache.maven.MavenExecutionException;
36 import org.apache.maven.ProjectCycleException;
37 import org.apache.maven.artifact.ArtifactUtils;
38 import org.apache.maven.execution.BuildResumptionDataRepository;
39 import org.apache.maven.execution.MavenExecutionRequest;
40 import org.apache.maven.execution.MavenSession;
41 import org.apache.maven.execution.ProjectActivation;
42 import org.apache.maven.execution.ProjectDependencyGraph;
43 import org.apache.maven.model.Plugin;
44 import org.apache.maven.model.building.DefaultModelProblem;
45 import org.apache.maven.model.building.Result;
46 import org.apache.maven.project.CycleDetectedException;
47 import org.apache.maven.project.DuplicateProjectException;
48 import org.apache.maven.project.MavenProject;
49 import org.apache.maven.project.ProjectBuildingException;
50 import org.apache.maven.project.collector.MultiModuleCollectionStrategy;
51 import org.apache.maven.project.collector.PomlessCollectionStrategy;
52 import org.apache.maven.project.collector.RequestPomCollectionStrategy;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 import static java.util.Comparator.comparing;
57
58
59
60
61 @Named(GraphBuilder.HINT)
62 @Singleton
63 public class DefaultGraphBuilder implements GraphBuilder {
64 private static final Logger LOGGER = LoggerFactory.getLogger(DefaultGraphBuilder.class);
65
66 private final BuildResumptionDataRepository buildResumptionDataRepository;
67 private final PomlessCollectionStrategy pomlessCollectionStrategy;
68 private final MultiModuleCollectionStrategy multiModuleCollectionStrategy;
69 private final RequestPomCollectionStrategy requestPomCollectionStrategy;
70 private final ProjectSelector projectSelector;
71
72
73
74
75
76 @Deprecated
77 public DefaultGraphBuilder() {
78 this(null, null, null, null);
79 }
80
81 @Inject
82 public DefaultGraphBuilder(
83 BuildResumptionDataRepository buildResumptionDataRepository,
84 PomlessCollectionStrategy pomlessCollectionStrategy,
85 MultiModuleCollectionStrategy multiModuleCollectionStrategy,
86 RequestPomCollectionStrategy requestPomCollectionStrategy) {
87 this.buildResumptionDataRepository = buildResumptionDataRepository;
88 this.pomlessCollectionStrategy = pomlessCollectionStrategy;
89 this.multiModuleCollectionStrategy = multiModuleCollectionStrategy;
90 this.requestPomCollectionStrategy = requestPomCollectionStrategy;
91 this.projectSelector = new ProjectSelector();
92 }
93
94 @Override
95 public Result<ProjectDependencyGraph> build(MavenSession session) {
96 try {
97 Result<ProjectDependencyGraph> result = sessionDependencyGraph(session);
98
99 if (result == null) {
100 final List<MavenProject> projects = getProjectsForMavenReactor(session);
101 validateProjects(projects, session.getRequest());
102 enrichRequestFromResumptionData(projects, session.getRequest());
103 result = reactorDependencyGraph(session, projects);
104 }
105
106 return result;
107 } catch (final ProjectBuildingException | DuplicateProjectException | MavenExecutionException e) {
108 return Result.error(Collections.singletonList(new DefaultModelProblem(null, null, null, null, 0, 0, e)));
109 } catch (final CycleDetectedException e) {
110 String message = "The projects in the reactor contain a cyclic reference: " + e.getMessage();
111 ProjectCycleException error = new ProjectCycleException(message, e);
112 return Result.error(
113 Collections.singletonList(new DefaultModelProblem(null, null, null, null, 0, 0, error)));
114 }
115 }
116
117 private Result<ProjectDependencyGraph> sessionDependencyGraph(final MavenSession session)
118 throws CycleDetectedException, DuplicateProjectException {
119 Result<ProjectDependencyGraph> result = null;
120
121 if (session.getProjectDependencyGraph() != null || session.getProjects() != null) {
122 final ProjectDependencyGraph graph =
123 new DefaultProjectDependencyGraph(session.getAllProjects(), session.getProjects());
124
125 result = Result.success(graph);
126 }
127
128 return result;
129 }
130
131 private Result<ProjectDependencyGraph> reactorDependencyGraph(MavenSession session, List<MavenProject> projects)
132 throws CycleDetectedException, DuplicateProjectException, MavenExecutionException {
133 ProjectDependencyGraph projectDependencyGraph = new DefaultProjectDependencyGraph(projects);
134 List<MavenProject> activeProjects = projectDependencyGraph.getSortedProjects();
135 List<MavenProject> allSortedProjects = projectDependencyGraph.getSortedProjects();
136 activeProjects = trimProjectsToRequest(activeProjects, projectDependencyGraph, session.getRequest());
137 activeProjects =
138 trimSelectedProjects(activeProjects, allSortedProjects, projectDependencyGraph, session.getRequest());
139 activeProjects = trimResumedProjects(activeProjects, projectDependencyGraph, session.getRequest());
140 activeProjects = trimExcludedProjects(activeProjects, projectDependencyGraph, session.getRequest());
141
142 if (activeProjects.size() != projectDependencyGraph.getSortedProjects().size()) {
143 projectDependencyGraph = new FilteredProjectDependencyGraph(projectDependencyGraph, activeProjects);
144 }
145
146 return Result.success(projectDependencyGraph);
147 }
148
149 private List<MavenProject> trimProjectsToRequest(
150 List<MavenProject> activeProjects, ProjectDependencyGraph graph, MavenExecutionRequest request)
151 throws MavenExecutionException {
152 List<MavenProject> result = activeProjects;
153
154 if (request.getPom() != null) {
155 result = getProjectsInRequestScope(request, activeProjects);
156
157 List<MavenProject> sortedProjects = graph.getSortedProjects();
158 result.sort(comparing(sortedProjects::indexOf));
159
160 result = includeAlsoMakeTransitively(result, request, graph);
161 }
162
163 return result;
164 }
165
166 private List<MavenProject> trimSelectedProjects(
167 List<MavenProject> projects,
168 List<MavenProject> allSortedProjects,
169 ProjectDependencyGraph graph,
170 MavenExecutionRequest request)
171 throws MavenExecutionException {
172 List<MavenProject> result = projects;
173
174 ProjectActivation projectActivation = request.getProjectActivation();
175 Set<String> requiredSelectors = projectActivation.getRequiredActiveProjectSelectors();
176 Set<String> optionalSelectors = projectActivation.getOptionalActiveProjectSelectors();
177 if (!requiredSelectors.isEmpty() || !optionalSelectors.isEmpty()) {
178 Set<MavenProject> selectedProjects = new HashSet<>(requiredSelectors.size() + optionalSelectors.size());
179 selectedProjects.addAll(
180 projectSelector.getRequiredProjectsBySelectors(request, allSortedProjects, requiredSelectors));
181 selectedProjects.addAll(
182 projectSelector.getOptionalProjectsBySelectors(request, allSortedProjects, optionalSelectors));
183
184
185 if (!selectedProjects.isEmpty()) {
186 result = new ArrayList<>(selectedProjects);
187
188 result = includeAlsoMakeTransitively(result, request, graph);
189
190
191 List<MavenProject> sortedProjects = graph.getSortedProjects();
192 result.sort(comparing(sortedProjects::indexOf));
193 }
194 }
195
196 return result;
197 }
198
199 private List<MavenProject> trimResumedProjects(
200 List<MavenProject> projects, ProjectDependencyGraph graph, MavenExecutionRequest request)
201 throws MavenExecutionException {
202 List<MavenProject> result = projects;
203
204 if (request.getResumeFrom() != null && !request.getResumeFrom().isEmpty()) {
205 File reactorDirectory = projectSelector.getBaseDirectoryFromRequest(request);
206
207 String selector = request.getResumeFrom();
208
209 MavenProject resumingFromProject = projects.stream()
210 .filter(project -> projectSelector.isMatchingProject(project, selector, reactorDirectory))
211 .findFirst()
212 .orElseThrow(() -> new MavenExecutionException(
213 "Could not find project to resume reactor build from: " + selector + " vs "
214 + formatProjects(projects),
215 request.getPom()));
216 int resumeFromProjectIndex = projects.indexOf(resumingFromProject);
217 List<MavenProject> retainingProjects = result.subList(resumeFromProjectIndex, projects.size());
218
219 result = includeAlsoMakeTransitively(retainingProjects, request, graph);
220 }
221
222 return result;
223 }
224
225 private List<MavenProject> trimExcludedProjects(
226 List<MavenProject> projects, ProjectDependencyGraph graph, MavenExecutionRequest request)
227 throws MavenExecutionException {
228 List<MavenProject> result = projects;
229
230 ProjectActivation projectActivation = request.getProjectActivation();
231 Set<String> requiredSelectors = projectActivation.getRequiredInactiveProjectSelectors();
232 Set<String> optionalSelectors = projectActivation.getOptionalInactiveProjectSelectors();
233 if (!requiredSelectors.isEmpty() || !optionalSelectors.isEmpty()) {
234 Set<MavenProject> excludedProjects = new HashSet<>(requiredSelectors.size() + optionalSelectors.size());
235 List<MavenProject> allProjects = graph.getAllProjects();
236 excludedProjects.addAll(
237 projectSelector.getRequiredProjectsBySelectors(request, allProjects, requiredSelectors));
238 excludedProjects.addAll(
239 projectSelector.getOptionalProjectsBySelectors(request, allProjects, optionalSelectors));
240
241 result = new ArrayList<>(projects);
242 result.removeAll(excludedProjects);
243
244 if (result.isEmpty()) {
245 boolean isPlural = excludedProjects.size() > 1;
246 String message = String.format(
247 "The project exclusion%s in --projects/-pl resulted in an "
248 + "empty reactor, please correct %s.",
249 isPlural ? "s" : "", isPlural ? "them" : "it");
250 throw new MavenExecutionException(message, request.getPom());
251 }
252 }
253
254 return result;
255 }
256
257 private List<MavenProject> includeAlsoMakeTransitively(
258 List<MavenProject> projects, MavenExecutionRequest request, ProjectDependencyGraph graph)
259 throws MavenExecutionException {
260 List<MavenProject> result = projects;
261
262 String makeBehavior = request.getMakeBehavior();
263 boolean makeBoth = MavenExecutionRequest.REACTOR_MAKE_BOTH.equals(makeBehavior);
264
265 boolean makeUpstream = makeBoth || MavenExecutionRequest.REACTOR_MAKE_UPSTREAM.equals(makeBehavior);
266 boolean makeDownstream = makeBoth || MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM.equals(makeBehavior);
267
268 if ((makeBehavior != null && !makeBehavior.isEmpty()) && !makeUpstream && !makeDownstream) {
269 throw new MavenExecutionException("Invalid reactor make behavior: " + makeBehavior, request.getPom());
270 }
271
272 if (makeUpstream || makeDownstream) {
273 Set<MavenProject> projectsSet = new HashSet<>(projects);
274
275 for (MavenProject project : projects) {
276 if (makeUpstream) {
277 projectsSet.addAll(graph.getUpstreamProjects(project, true));
278 }
279 if (makeDownstream) {
280 projectsSet.addAll(graph.getDownstreamProjects(project, true));
281 }
282 }
283
284 result = new ArrayList<>(projectsSet);
285
286
287 List<MavenProject> sortedProjects = graph.getSortedProjects();
288 result.sort(comparing(sortedProjects::indexOf));
289 }
290
291 return result;
292 }
293
294 private void enrichRequestFromResumptionData(List<MavenProject> projects, MavenExecutionRequest request) {
295 if (request.isResume()) {
296 projects.stream()
297 .filter(MavenProject::isExecutionRoot)
298 .findFirst()
299 .ifPresent(rootProject -> buildResumptionDataRepository.applyResumptionData(request, rootProject));
300 }
301 }
302
303 private List<MavenProject> getProjectsInRequestScope(MavenExecutionRequest request, List<MavenProject> projects)
304 throws MavenExecutionException {
305 if (request.getPom() == null) {
306 return projects;
307 }
308
309 MavenProject requestPomProject = projects.stream()
310 .filter(project -> request.getPom().equals(project.getFile()))
311 .findFirst()
312 .orElseThrow(() -> new MavenExecutionException(
313 "Could not find a project in reactor matching the request POM", request.getPom()));
314
315 List<MavenProject> modules = requestPomProject.getCollectedProjects() != null
316 ? requestPomProject.getCollectedProjects()
317 : Collections.emptyList();
318
319 List<MavenProject> result = new ArrayList<>(modules);
320 result.add(requestPomProject);
321 return result;
322 }
323
324 private String formatProjects(List<MavenProject> projects) {
325 StringBuilder projectNames = new StringBuilder();
326 Iterator<MavenProject> iterator = projects.iterator();
327 while (iterator.hasNext()) {
328 MavenProject project = iterator.next();
329 projectNames.append(project.getGroupId()).append(":").append(project.getArtifactId());
330 if (iterator.hasNext()) {
331 projectNames.append(", ");
332 }
333 }
334 return projectNames.toString();
335 }
336
337
338
339
340
341
342
343 private List<MavenProject> getProjectsForMavenReactor(MavenSession session) throws ProjectBuildingException {
344 MavenExecutionRequest request = session.getRequest();
345 request.getProjectBuildingRequest().setRepositorySession(session.getRepositorySession());
346
347
348 if (request.getPom() == null) {
349 return pomlessCollectionStrategy.collectProjects(request);
350 }
351
352
353 if (request.getMakeBehavior() != null || !request.getProjectActivation().isEmpty()) {
354 List<MavenProject> projects = multiModuleCollectionStrategy.collectProjects(request);
355 if (!projects.isEmpty()) {
356 return projects;
357 }
358 }
359
360
361 return requestPomCollectionStrategy.collectProjects(request);
362 }
363
364 private void validateProjects(List<MavenProject> projects, MavenExecutionRequest request)
365 throws MavenExecutionException {
366 Map<String, MavenProject> projectsMap = new HashMap<>();
367
368 List<MavenProject> projectsInRequestScope = getProjectsInRequestScope(request, projects);
369 for (MavenProject p : projectsInRequestScope) {
370 String projectKey = ArtifactUtils.key(p.getGroupId(), p.getArtifactId(), p.getVersion());
371
372 projectsMap.put(projectKey, p);
373 }
374
375 for (MavenProject project : projects) {
376
377 for (Plugin plugin : project.getBuildPlugins()) {
378 if (plugin.isExtensions()) {
379 String pluginKey =
380 ArtifactUtils.key(plugin.getGroupId(), plugin.getArtifactId(), plugin.getVersion());
381
382 if (projectsMap.containsKey(pluginKey)) {
383 LOGGER.warn(
384 "'{}' uses '{}' as extension which is not possible within the same reactor build. "
385 + "This plugin was pulled from the local repository!",
386 project.getName(),
387 plugin.getKey());
388 }
389 }
390 }
391 }
392 }
393 }