View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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   * Builds the {@link ProjectDependencyGraph inter-dependencies graph} between projects in the reactor.
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         // [groupId]:artifactId
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         // relative path, e.g. "sub", "../sub" or "."
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     // Project collection
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         // We have no POM file.
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             // MNG-1911 / MNG-5572: Building plugins with extensions cannot be part of reactor
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 }