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.Arrays;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.Iterator;
32 import java.util.LinkedHashSet;
33 import java.util.List;
34 import java.util.Map;
35
36 import org.apache.maven.DefaultMaven;
37 import org.apache.maven.MavenExecutionException;
38 import org.apache.maven.ProjectCycleException;
39 import org.apache.maven.artifact.ArtifactUtils;
40 import org.apache.maven.execution.MavenExecutionRequest;
41 import org.apache.maven.execution.MavenSession;
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.ModelProblem;
46 import org.apache.maven.model.building.ModelProblemUtils;
47 import org.apache.maven.model.building.ModelSource;
48 import org.apache.maven.model.building.Result;
49 import org.apache.maven.model.building.UrlModelSource;
50 import org.apache.maven.project.DuplicateProjectException;
51 import org.apache.maven.project.MavenProject;
52 import org.apache.maven.project.ProjectBuilder;
53 import org.apache.maven.project.ProjectBuildingException;
54 import org.apache.maven.project.ProjectBuildingRequest;
55 import org.apache.maven.project.ProjectBuildingResult;
56 import org.codehaus.plexus.util.StringUtils;
57 import org.codehaus.plexus.util.dag.CycleDetectedException;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
60
61
62
63
64 @Singleton
65 @Named(GraphBuilder.HINT)
66 public class DefaultGraphBuilder implements GraphBuilder {
67
68 private final Logger logger = LoggerFactory.getLogger(getClass());
69
70 @Inject
71 protected ProjectBuilder projectBuilder;
72
73 @Override
74 public Result<ProjectDependencyGraph> build(MavenSession session) {
75 try {
76 Result<ProjectDependencyGraph> result = sessionDependencyGraph(session);
77
78 if (result == null) {
79 final List<MavenProject> projects = getProjectsForMavenReactor(session);
80 validateProjects(projects);
81 result = reactorDependencyGraph(session, projects);
82 }
83
84 return result;
85 } catch (final ProjectBuildingException | DuplicateProjectException | MavenExecutionException e) {
86 return Result.error(Collections.singletonList(new DefaultModelProblem(null, null, null, null, 0, 0, e)));
87 } catch (final CycleDetectedException e) {
88 String message = "The projects in the reactor contain a cyclic reference: " + e.getMessage();
89 ProjectCycleException error = new ProjectCycleException(message, e);
90 return Result.error(
91 Collections.singletonList(new DefaultModelProblem(null, null, null, null, 0, 0, error)));
92 }
93 }
94
95 private Result<ProjectDependencyGraph> sessionDependencyGraph(final MavenSession session)
96 throws CycleDetectedException, DuplicateProjectException {
97 Result<ProjectDependencyGraph> result = null;
98
99 if (session.getProjectDependencyGraph() != null || session.getProjects() != null) {
100 ProjectDependencyGraph graph = new DefaultProjectDependencyGraph(session.getAllProjects());
101 if (session.getProjects() != null) {
102 graph = new FilteredProjectDependencyGraph(graph, session.getProjects());
103 }
104
105 result = Result.success(graph);
106 }
107
108 return result;
109 }
110
111 private Result<ProjectDependencyGraph> reactorDependencyGraph(MavenSession session, List<MavenProject> projects)
112 throws CycleDetectedException, DuplicateProjectException, MavenExecutionException {
113 ProjectDependencyGraph projectDependencyGraph = new DefaultProjectDependencyGraph(projects);
114 List<MavenProject> activeProjects = projectDependencyGraph.getSortedProjects();
115 activeProjects = trimSelectedProjects(activeProjects, projectDependencyGraph, session.getRequest());
116 activeProjects = trimExcludedProjects(activeProjects, session.getRequest());
117 activeProjects = trimResumedProjects(activeProjects, session.getRequest());
118
119 if (activeProjects.size() != projectDependencyGraph.getSortedProjects().size()) {
120 projectDependencyGraph = new FilteredProjectDependencyGraph(projectDependencyGraph, activeProjects);
121 }
122
123 return Result.success(projectDependencyGraph);
124 }
125
126 private List<MavenProject> trimSelectedProjects(
127 List<MavenProject> projects, ProjectDependencyGraph graph, MavenExecutionRequest request)
128 throws MavenExecutionException {
129 List<MavenProject> result = projects;
130
131 if (!request.getSelectedProjects().isEmpty()) {
132 File reactorDirectory = null;
133 if (request.getBaseDirectory() != null) {
134 reactorDirectory = new File(request.getBaseDirectory());
135 }
136
137 Collection<MavenProject> selectedProjects = new LinkedHashSet<>(projects.size());
138
139 for (String selector : request.getSelectedProjects()) {
140 MavenProject selectedProject = null;
141
142 for (MavenProject project : projects) {
143 if (isMatchingProject(project, selector, reactorDirectory)) {
144 selectedProject = project;
145 break;
146 }
147 }
148
149 if (selectedProject != null) {
150 selectedProjects.add(selectedProject);
151 } else {
152 throw new MavenExecutionException(
153 "Could not find the selected project in the reactor: " + selector, request.getPom());
154 }
155 }
156
157 boolean makeUpstream = false;
158 boolean makeDownstream = false;
159
160 if (MavenExecutionRequest.REACTOR_MAKE_UPSTREAM.equals(request.getMakeBehavior())) {
161 makeUpstream = true;
162 } else if (MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM.equals(request.getMakeBehavior())) {
163 makeDownstream = true;
164 } else if (MavenExecutionRequest.REACTOR_MAKE_BOTH.equals(request.getMakeBehavior())) {
165 makeUpstream = true;
166 makeDownstream = true;
167 } else if (StringUtils.isNotEmpty(request.getMakeBehavior())) {
168 throw new MavenExecutionException(
169 "Invalid reactor make behavior: " + request.getMakeBehavior(), request.getPom());
170 }
171
172 if (makeUpstream || makeDownstream) {
173 for (MavenProject selectedProject : new ArrayList<>(selectedProjects)) {
174 if (makeUpstream) {
175 selectedProjects.addAll(graph.getUpstreamProjects(selectedProject, true));
176 }
177 if (makeDownstream) {
178 selectedProjects.addAll(graph.getDownstreamProjects(selectedProject, true));
179 }
180 }
181 }
182
183 result = new ArrayList<>(selectedProjects.size());
184
185 for (MavenProject project : projects) {
186 if (selectedProjects.contains(project)) {
187 result.add(project);
188 }
189 }
190 }
191
192 return result;
193 }
194
195 private List<MavenProject> trimExcludedProjects(List<MavenProject> projects, MavenExecutionRequest request)
196 throws MavenExecutionException {
197 List<MavenProject> result = projects;
198
199 if (!request.getExcludedProjects().isEmpty()) {
200 File reactorDirectory = null;
201
202 if (request.getBaseDirectory() != null) {
203 reactorDirectory = new File(request.getBaseDirectory());
204 }
205
206 Collection<MavenProject> excludedProjects = new LinkedHashSet<>(projects.size());
207
208 for (String selector : request.getExcludedProjects()) {
209 MavenProject excludedProject = null;
210
211 for (MavenProject project : projects) {
212 if (isMatchingProject(project, selector, reactorDirectory)) {
213 excludedProject = project;
214 break;
215 }
216 }
217
218 if (excludedProject != null) {
219 excludedProjects.add(excludedProject);
220 } else {
221 throw new MavenExecutionException(
222 "Could not find the selected project in the reactor: " + selector, request.getPom());
223 }
224 }
225
226 result = new ArrayList<>(projects.size());
227 for (MavenProject project : projects) {
228 if (!excludedProjects.contains(project)) {
229 result.add(project);
230 }
231 }
232 }
233
234 return result;
235 }
236
237 private List<MavenProject> trimResumedProjects(List<MavenProject> projects, MavenExecutionRequest request)
238 throws MavenExecutionException {
239 List<MavenProject> result = projects;
240
241 if (StringUtils.isNotEmpty(request.getResumeFrom())) {
242 File reactorDirectory = null;
243 if (request.getBaseDirectory() != null) {
244 reactorDirectory = new File(request.getBaseDirectory());
245 }
246
247 String selector = request.getResumeFrom();
248
249 result = new ArrayList<>(projects.size());
250
251 boolean resumed = false;
252
253 for (MavenProject project : projects) {
254 if (!resumed && isMatchingProject(project, selector, reactorDirectory)) {
255 resumed = true;
256 }
257
258 if (resumed) {
259 result.add(project);
260 }
261 }
262
263 if (!resumed) {
264 throw new MavenExecutionException(
265 "Could not find project to resume reactor build from: " + selector + " vs "
266 + formatProjects(projects),
267 request.getPom());
268 }
269 }
270
271 return result;
272 }
273
274 private String formatProjects(List<MavenProject> projects) {
275 StringBuilder projectNames = new StringBuilder();
276 Iterator<MavenProject> iterator = projects.iterator();
277 while (iterator.hasNext()) {
278 MavenProject project = iterator.next();
279 projectNames.append(project.getGroupId()).append(":").append(project.getArtifactId());
280 if (iterator.hasNext()) {
281 projectNames.append(", ");
282 }
283 }
284 return projectNames.toString();
285 }
286
287 private boolean isMatchingProject(MavenProject project, String selector, File reactorDirectory) {
288
289 if (selector.indexOf(':') >= 0) {
290 String id = ':' + project.getArtifactId();
291
292 if (id.equals(selector)) {
293 return true;
294 }
295
296 id = project.getGroupId() + id;
297
298 if (id.equals(selector)) {
299 return true;
300 }
301 }
302
303
304 else if (reactorDirectory != null) {
305 File selectedProject =
306 new File(new File(reactorDirectory, selector).toURI().normalize());
307
308 if (selectedProject.isFile()) {
309 return selectedProject.equals(project.getFile());
310 } else if (selectedProject.isDirectory()) {
311 return selectedProject.equals(project.getBasedir());
312 }
313 }
314
315 return false;
316 }
317
318
319
320
321
322
323
324 private List<MavenProject> getProjectsForMavenReactor(MavenSession session) throws ProjectBuildingException {
325 MavenExecutionRequest request = session.getRequest();
326
327 request.getProjectBuildingRequest().setRepositorySession(session.getRepositorySession());
328
329 List<MavenProject> projects = new ArrayList<>();
330
331
332
333 if (request.getPom() == null) {
334 ModelSource modelSource = new UrlModelSource(DefaultMaven.class.getResource("project/standalone.xml"));
335 MavenProject project = projectBuilder
336 .build(modelSource, request.getProjectBuildingRequest())
337 .getProject();
338 project.setExecutionRoot(true);
339 projects.add(project);
340 request.setProjectPresent(false);
341 return projects;
342 }
343
344 List<File> files = Arrays.asList(request.getPom().getAbsoluteFile());
345 collectProjects(projects, files, request);
346 return projects;
347 }
348
349 private void collectProjects(List<MavenProject> projects, List<File> files, MavenExecutionRequest request)
350 throws ProjectBuildingException {
351 ProjectBuildingRequest projectBuildingRequest = request.getProjectBuildingRequest();
352
353 List<ProjectBuildingResult> results =
354 projectBuilder.build(files, request.isRecursive(), projectBuildingRequest);
355
356 boolean problems = false;
357
358 for (ProjectBuildingResult result : results) {
359 projects.add(result.getProject());
360
361 if (!result.getProblems().isEmpty() && logger.isWarnEnabled()) {
362 logger.warn("");
363 logger.warn("Some problems were encountered while building the effective model for "
364 + result.getProject().getId());
365
366 for (ModelProblem problem : result.getProblems()) {
367 String loc = ModelProblemUtils.formatLocation(problem, result.getProjectId());
368 logger.warn(problem.getMessage() + (StringUtils.isNotEmpty(loc) ? " @ " + loc : ""));
369 }
370
371 problems = true;
372 }
373 }
374
375 if (problems) {
376 logger.warn("");
377 logger.warn("It is highly recommended to fix these problems"
378 + " because they threaten the stability of your build.");
379 logger.warn("");
380 logger.warn("For this reason, future Maven versions might no"
381 + " longer support building such malformed projects.");
382 logger.warn("");
383 }
384 }
385
386 private void validateProjects(List<MavenProject> projects) {
387 Map<String, MavenProject> projectsMap = new HashMap<>();
388
389 for (MavenProject p : projects) {
390 String projectKey = ArtifactUtils.key(p.getGroupId(), p.getArtifactId(), p.getVersion());
391
392 projectsMap.put(projectKey, p);
393 }
394
395 for (MavenProject project : projects) {
396
397 for (Plugin plugin : project.getBuildPlugins()) {
398 if (plugin.isExtensions()) {
399 String pluginKey =
400 ArtifactUtils.key(plugin.getGroupId(), plugin.getArtifactId(), plugin.getVersion());
401
402 if (projectsMap.containsKey(pluginKey)) {
403 logger.warn(project.getName() + " uses " + plugin.getKey()
404 + " as extensions, which is not possible within the same reactor build. "
405 + "This plugin was pulled from the local repository!");
406 }
407 }
408 }
409 }
410 }
411 }