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