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