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