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