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 processPackagingAttribute(projects, session.getRequest());
103 enrichRequestFromResumptionData(projects, session.getRequest());
104 result = reactorDependencyGraph(session, projects);
105 }
106
107 return result;
108 } catch (final ProjectBuildingException | DuplicateProjectException | MavenExecutionException e) {
109 return Result.error(Collections.singletonList(new DefaultModelProblem(null, null, null, null, 0, 0, e)));
110 } catch (final CycleDetectedException e) {
111 String message = "The projects in the reactor contain a cyclic reference: " + e.getMessage();
112 ProjectCycleException error = new ProjectCycleException(message, e);
113 return Result.error(
114 Collections.singletonList(new DefaultModelProblem(null, null, null, null, 0, 0, error)));
115 }
116 }
117
118 private Result<ProjectDependencyGraph> sessionDependencyGraph(final MavenSession session)
119 throws CycleDetectedException, DuplicateProjectException {
120 Result<ProjectDependencyGraph> result = null;
121
122 if (session.getProjectDependencyGraph() != null || session.getProjects() != null) {
123 ProjectDependencyGraph graph = new DefaultProjectDependencyGraph(session.getAllProjects());
124 if (session.getProjects() != null) {
125 graph = new FilteredProjectDependencyGraph(graph, session.getProjects());
126 }
127
128 result = Result.success(graph);
129 }
130
131 return result;
132 }
133
134 private Result<ProjectDependencyGraph> reactorDependencyGraph(MavenSession session, List<MavenProject> projects)
135 throws CycleDetectedException, DuplicateProjectException, MavenExecutionException {
136 ProjectDependencyGraph projectDependencyGraph = new DefaultProjectDependencyGraph(projects);
137 List<MavenProject> activeProjects = projectDependencyGraph.getSortedProjects();
138 List<MavenProject> allSortedProjects = projectDependencyGraph.getSortedProjects();
139 activeProjects = trimProjectsToRequest(activeProjects, projectDependencyGraph, session.getRequest());
140 activeProjects =
141 trimSelectedProjects(activeProjects, allSortedProjects, projectDependencyGraph, session.getRequest());
142 activeProjects = trimResumedProjects(activeProjects, projectDependencyGraph, session.getRequest());
143 activeProjects = trimExcludedProjects(activeProjects, projectDependencyGraph, session.getRequest());
144
145 if (activeProjects.size() != projectDependencyGraph.getSortedProjects().size()) {
146 projectDependencyGraph = new FilteredProjectDependencyGraph(projectDependencyGraph, activeProjects);
147 }
148
149 return Result.success(projectDependencyGraph);
150 }
151
152 private List<MavenProject> trimProjectsToRequest(
153 List<MavenProject> activeProjects, ProjectDependencyGraph graph, MavenExecutionRequest request)
154 throws MavenExecutionException {
155 List<MavenProject> result = activeProjects;
156
157 if (request.getPom() != null) {
158 result = getProjectsInRequestScope(request, activeProjects);
159
160 List<MavenProject> sortedProjects = graph.getSortedProjects();
161 result.sort(comparing(sortedProjects::indexOf));
162
163 result = includeAlsoMakeTransitively(result, request, graph);
164 }
165
166 return result;
167 }
168
169 private List<MavenProject> trimSelectedProjects(
170 List<MavenProject> projects,
171 List<MavenProject> allSortedProjects,
172 ProjectDependencyGraph graph,
173 MavenExecutionRequest request)
174 throws MavenExecutionException {
175 List<MavenProject> result = projects;
176
177 ProjectActivation projectActivation = request.getProjectActivation();
178
179 Set<String> requiredSelectors = projectActivation.getRequiredActiveProjectSelectors();
180 Set<String> optionalSelectors = projectActivation.getOptionalActiveProjectSelectors();
181 if (!requiredSelectors.isEmpty() || !optionalSelectors.isEmpty()) {
182 Set<MavenProject> selectedProjects =
183 projectSelector.getActiveProjects(request, allSortedProjects, projectActivation.getActivations());
184
185
186 if (!selectedProjects.isEmpty()) {
187 result = new ArrayList<>(selectedProjects);
188
189 result = includeAlsoMakeTransitively(result, request, graph);
190
191
192 List<MavenProject> sortedProjects = graph.getSortedProjects();
193 result.sort(comparing(sortedProjects::indexOf));
194 }
195 }
196
197 return result;
198 }
199
200 private List<MavenProject> trimResumedProjects(
201 List<MavenProject> projects, ProjectDependencyGraph graph, MavenExecutionRequest request)
202 throws MavenExecutionException {
203 List<MavenProject> result = projects;
204
205 if (request.getResumeFrom() != null && !request.getResumeFrom().isEmpty()) {
206 File reactorDirectory = projectSelector.getBaseDirectoryFromRequest(request);
207
208 String selector = request.getResumeFrom();
209
210 MavenProject resumingFromProject = projects.stream()
211 .filter(project -> projectSelector.isMatchingProject(project, selector, reactorDirectory))
212 .findFirst()
213 .orElseThrow(() -> new MavenExecutionException(
214 "Could not find project to resume reactor build from: " + selector + " vs "
215 + formatProjects(projects),
216 request.getPom()));
217 int resumeFromProjectIndex = projects.indexOf(resumingFromProject);
218 List<MavenProject> retainingProjects = result.subList(resumeFromProjectIndex, projects.size());
219
220 result = includeAlsoMakeTransitively(retainingProjects, request, graph);
221 }
222
223 return result;
224 }
225
226 private List<MavenProject> trimExcludedProjects(
227 List<MavenProject> projects, ProjectDependencyGraph graph, MavenExecutionRequest request)
228 throws MavenExecutionException {
229 List<MavenProject> result = projects;
230
231 ProjectActivation projectActivation = request.getProjectActivation();
232
233 projectActivation.getActivations().stream()
234 .filter(pa -> pa.activationSettings().active())
235 .forEach(pas -> {});
236
237 Set<String> requiredSelectors = projectActivation.getRequiredInactiveProjectSelectors();
238 Set<String> optionalSelectors = projectActivation.getOptionalInactiveProjectSelectors();
239 if (!requiredSelectors.isEmpty() || !optionalSelectors.isEmpty()) {
240 Set<MavenProject> excludedProjects = new HashSet<>(requiredSelectors.size() + optionalSelectors.size());
241 List<MavenProject> allProjects = graph.getAllProjects();
242 excludedProjects.addAll(
243 projectSelector.getRequiredProjectsBySelectors(request, allProjects, requiredSelectors));
244 excludedProjects.addAll(
245 projectSelector.getOptionalProjectsBySelectors(request, allProjects, optionalSelectors));
246
247 result = new ArrayList<>(projects);
248 result.removeAll(excludedProjects);
249
250 if (result.isEmpty()) {
251 boolean isPlural = excludedProjects.size() > 1;
252 String message = String.format(
253 "The project exclusion%s in --projects/-pl resulted in an "
254 + "empty reactor, please correct %s.",
255 isPlural ? "s" : "", isPlural ? "them" : "it");
256 throw new MavenExecutionException(message, request.getPom());
257 }
258 }
259
260 return result;
261 }
262
263 private List<MavenProject> includeAlsoMakeTransitively(
264 List<MavenProject> projects, MavenExecutionRequest request, ProjectDependencyGraph graph)
265 throws MavenExecutionException {
266 List<MavenProject> result = projects;
267
268 String makeBehavior = request.getMakeBehavior();
269 boolean makeBoth = MavenExecutionRequest.REACTOR_MAKE_BOTH.equals(makeBehavior);
270
271 boolean makeUpstream = makeBoth || MavenExecutionRequest.REACTOR_MAKE_UPSTREAM.equals(makeBehavior);
272 boolean makeDownstream = makeBoth || MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM.equals(makeBehavior);
273
274 if ((makeBehavior != null && !makeBehavior.isEmpty()) && !makeUpstream && !makeDownstream) {
275 throw new MavenExecutionException("Invalid reactor make behavior: " + makeBehavior, request.getPom());
276 }
277
278 if (makeUpstream || makeDownstream) {
279 Set<MavenProject> projectsSet = new HashSet<>(projects);
280
281 for (MavenProject project : projects) {
282 if (makeUpstream) {
283 projectsSet.addAll(graph.getUpstreamProjects(project, true));
284 }
285 if (makeDownstream) {
286 projectsSet.addAll(graph.getDownstreamProjects(project, true));
287 }
288 }
289
290 result = new ArrayList<>(projectsSet);
291
292
293 List<MavenProject> sortedProjects = graph.getSortedProjects();
294 result.sort(comparing(sortedProjects::indexOf));
295 }
296
297 return result;
298 }
299
300 private void enrichRequestFromResumptionData(List<MavenProject> projects, MavenExecutionRequest request) {
301 if (request.isResume()) {
302 projects.stream()
303 .filter(MavenProject::isExecutionRoot)
304 .findFirst()
305 .ifPresent(rootProject -> buildResumptionDataRepository.applyResumptionData(request, rootProject));
306 }
307 }
308
309 private List<MavenProject> getProjectsInRequestScope(MavenExecutionRequest request, List<MavenProject> projects)
310 throws MavenExecutionException {
311 if (request.getPom() == null) {
312 return projects;
313 }
314
315 MavenProject requestPomProject = projects.stream()
316 .filter(project -> request.getPom().equals(project.getFile()))
317 .findFirst()
318 .orElseThrow(() -> new MavenExecutionException(
319 "Could not find a project in reactor matching the request POM", request.getPom()));
320
321 List<MavenProject> modules = requestPomProject.getCollectedProjects() != null
322 ? requestPomProject.getCollectedProjects()
323 : Collections.emptyList();
324
325 List<MavenProject> result = new ArrayList<>(modules);
326 result.add(requestPomProject);
327 return result;
328 }
329
330 private String formatProjects(List<MavenProject> projects) {
331 StringBuilder projectNames = new StringBuilder();
332 Iterator<MavenProject> iterator = projects.iterator();
333 while (iterator.hasNext()) {
334 MavenProject project = iterator.next();
335 projectNames.append(project.getGroupId()).append(":").append(project.getArtifactId());
336 if (iterator.hasNext()) {
337 projectNames.append(", ");
338 }
339 }
340 return projectNames.toString();
341 }
342
343
344
345
346
347
348
349 private List<MavenProject> getProjectsForMavenReactor(MavenSession session) throws ProjectBuildingException {
350 MavenExecutionRequest request = session.getRequest();
351 request.getProjectBuildingRequest().setRepositorySession(session.getRepositorySession());
352
353
354 if (request.getPom() == null) {
355 return pomlessCollectionStrategy.collectProjects(request);
356 }
357
358
359 if (request.getMakeBehavior() != null || !request.getProjectActivation().isEmpty()) {
360 List<MavenProject> projects = multiModuleCollectionStrategy.collectProjects(request);
361 if (!projects.isEmpty()) {
362 return projects;
363 }
364 }
365
366
367 return requestPomCollectionStrategy.collectProjects(request);
368 }
369
370 private void validateProjects(List<MavenProject> projects, MavenExecutionRequest request)
371 throws MavenExecutionException {
372 Map<String, MavenProject> projectsMap = new HashMap<>();
373
374 List<MavenProject> projectsInRequestScope = getProjectsInRequestScope(request, projects);
375 for (MavenProject p : projectsInRequestScope) {
376 String projectKey = ArtifactUtils.key(p.getGroupId(), p.getArtifactId(), p.getVersion());
377
378 projectsMap.put(projectKey, p);
379 }
380
381 for (MavenProject project : projects) {
382
383 for (Plugin plugin : project.getBuildPlugins()) {
384 if (plugin.isExtensions()) {
385 String pluginKey =
386 ArtifactUtils.key(plugin.getGroupId(), plugin.getArtifactId(), plugin.getVersion());
387
388 if (projectsMap.containsKey(pluginKey)) {
389 LOGGER.warn(
390 "'{}' uses '{}' as extension which is not possible within the same reactor build. "
391 + "This plugin was pulled from the local repository!",
392 project.getName(),
393 plugin.getKey());
394 }
395 }
396 }
397 }
398 }
399
400 private void processPackagingAttribute(List<MavenProject> projects, MavenExecutionRequest request)
401 throws MavenExecutionException {
402 List<MavenProject> projectsInRequestScope = getProjectsInRequestScope(request, projects);
403 for (MavenProject p : projectsInRequestScope) {
404 if ("bom".equals(p.getPackaging())) {
405 LOGGER.info(
406 "The packaging attribute of the '{}' project is configured as 'bom' and changed to 'pom'",
407 p.getName());
408 p.setPackaging("pom");
409 }
410 }
411 }
412 }