View Javadoc
1   package org.apache.maven.graph;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Collection;
26  import java.util.HashMap;
27  import java.util.LinkedHashSet;
28  import java.util.List;
29  import java.util.Map;
30  
31  import com.google.common.collect.Lists;
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  @Component( role = GraphBuilder.class, hint = GraphBuilder.HINT )
59  public class DefaultGraphBuilder
60      implements GraphBuilder
61  {
62  
63      @Requirement
64      private Logger logger;
65  
66      @Requirement
67      protected ProjectBuilder projectBuilder;
68  
69      @Override
70      public Result<ProjectDependencyGraph> build( MavenSession session )
71      {
72          try
73          {
74              Result<ProjectDependencyGraph> result = sessionDependencyGraph( session );
75  
76              if ( result == null )
77              {
78                  final List<MavenProject> projects = getProjectsForMavenReactor( session );
79                  validateProjects( projects );
80                  result = reactorDependencyGraph( session, projects );
81              }
82  
83              return result;
84          }
85          catch ( final ProjectBuildingException e )
86          {
87              return Result.error( Lists.newArrayList( new DefaultModelProblem( null, null, null, null, 0, 0, e ) ) );
88          }
89          catch ( final CycleDetectedException e )
90          {
91              String message = "The projects in the reactor contain a cyclic reference: " + e.getMessage();
92              ProjectCycleException error = new ProjectCycleException( message, e );
93              return Result.error( Lists.newArrayList( new DefaultModelProblem( null, null, null, null, 0, 0, error ) ) );
94          }
95          catch ( final DuplicateProjectException e )
96          {
97              return Result.error( Lists.newArrayList( new DefaultModelProblem( null, null, null, null, 0, 0, e ) ) );
98          }
99          catch ( final MavenExecutionException e )
100         {
101             return Result.error( Lists.newArrayList( new DefaultModelProblem( null, null, null, null, 0, 0, e ) ) );
102         }
103     }
104 
105     private Result<ProjectDependencyGraph> sessionDependencyGraph( final MavenSession session )
106         throws CycleDetectedException, DuplicateProjectException
107     {
108         Result<ProjectDependencyGraph> result = null;
109 
110         if ( session.getProjectDependencyGraph() != null || session.getProjects() != null )
111         {
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     {
124         ProjectDependencyGraph projectDependencyGraph = new DefaultProjectDependencyGraph( projects );
125         List<MavenProject> activeProjects = projectDependencyGraph.getSortedProjects();
126         activeProjects = trimSelectedProjects( activeProjects, projectDependencyGraph, session.getRequest() );
127         activeProjects = trimExcludedProjects( activeProjects, session.getRequest() );
128         activeProjects = trimResumedProjects( activeProjects, session.getRequest() );
129 
130         if ( activeProjects.size() != projectDependencyGraph.getSortedProjects().size() )
131         {
132             projectDependencyGraph = new FilteredProjectDependencyGraph( projectDependencyGraph, activeProjects );
133         }
134 
135         return Result.success( projectDependencyGraph );
136     }
137 
138     private List<MavenProject> trimSelectedProjects( List<MavenProject> projects, ProjectDependencyGraph graph,
139                                                      MavenExecutionRequest request )
140         throws MavenExecutionException
141     {
142         List<MavenProject> result = projects;
143 
144         if ( !request.getSelectedProjects().isEmpty() )
145         {
146             File reactorDirectory = null;
147             if ( request.getBaseDirectory() != null )
148             {
149                 reactorDirectory = new File( request.getBaseDirectory() );
150             }
151 
152             Collection<MavenProject> selectedProjects = new LinkedHashSet<>( projects.size() );
153 
154             for ( String selector : request.getSelectedProjects() )
155             {
156                 MavenProject selectedProject = null;
157 
158                 for ( MavenProject project : projects )
159                 {
160                     if ( isMatchingProject( project, selector, reactorDirectory ) )
161                     {
162                         selectedProject = project;
163                         break;
164                     }
165                 }
166 
167                 if ( selectedProject != null )
168                 {
169                     selectedProjects.add( selectedProject );
170                 }
171                 else
172                 {
173                     throw new MavenExecutionException( "Could not find the selected project in the reactor: "
174                         + selector, request.getPom() );
175                 }
176             }
177 
178             boolean makeUpstream = false;
179             boolean makeDownstream = false;
180 
181             if ( MavenExecutionRequest.REACTOR_MAKE_UPSTREAM.equals( request.getMakeBehavior() ) )
182             {
183                 makeUpstream = true;
184             }
185             else if ( MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM.equals( request.getMakeBehavior() ) )
186             {
187                 makeDownstream = true;
188             }
189             else if ( MavenExecutionRequest.REACTOR_MAKE_BOTH.equals( request.getMakeBehavior() ) )
190             {
191                 makeUpstream = true;
192                 makeDownstream = true;
193             }
194             else if ( StringUtils.isNotEmpty( request.getMakeBehavior() ) )
195             {
196                 throw new MavenExecutionException( "Invalid reactor make behavior: " + request.getMakeBehavior(),
197                                                    request.getPom() );
198             }
199 
200             if ( makeUpstream || makeDownstream )
201             {
202                 for ( MavenProject selectedProject : new ArrayList<>( selectedProjects ) )
203                 {
204                     if ( makeUpstream )
205                     {
206                         selectedProjects.addAll( graph.getUpstreamProjects( selectedProject, true ) );
207                     }
208                     if ( makeDownstream )
209                     {
210                         selectedProjects.addAll( graph.getDownstreamProjects( selectedProject, true ) );
211                     }
212                 }
213             }
214 
215             result = new ArrayList<>( selectedProjects.size() );
216 
217             for ( MavenProject project : projects )
218             {
219                 if ( selectedProjects.contains( project ) )
220                 {
221                     result.add( project );
222                 }
223             }
224         }
225 
226         return result;
227     }
228 
229     private List<MavenProject> trimExcludedProjects( List<MavenProject> projects, MavenExecutionRequest request )
230         throws MavenExecutionException
231     {
232         List<MavenProject> result = projects;
233 
234         if ( !request.getExcludedProjects().isEmpty() )
235         {
236             File reactorDirectory = null;
237 
238             if ( request.getBaseDirectory() != null )
239             {
240                 reactorDirectory = new File( request.getBaseDirectory() );
241             }
242 
243             Collection<MavenProject> excludedProjects = new LinkedHashSet<>( projects.size() );
244 
245             for ( String selector : request.getExcludedProjects() )
246             {
247                 MavenProject excludedProject = null;
248 
249                 for ( MavenProject project : projects )
250                 {
251                     if ( isMatchingProject( project, selector, reactorDirectory ) )
252                     {
253                         excludedProject = project;
254                         break;
255                     }
256                 }
257 
258                 if ( excludedProject != null )
259                 {
260                     excludedProjects.add( excludedProject );
261                 }
262                 else
263                 {
264                     throw new MavenExecutionException( "Could not find the selected project in the reactor: "
265                         + selector, request.getPom() );
266                 }
267             }
268 
269             result = new ArrayList<>( projects.size() );
270             for ( MavenProject project : projects )
271             {
272                 if ( !excludedProjects.contains( project ) )
273                 {
274                     result.add( project );
275                 }
276             }
277         }
278 
279         return result;
280     }
281 
282     private List<MavenProject> trimResumedProjects( List<MavenProject> projects, MavenExecutionRequest request )
283         throws MavenExecutionException
284     {
285         List<MavenProject> result = projects;
286 
287         if ( StringUtils.isNotEmpty( request.getResumeFrom() ) )
288         {
289             File reactorDirectory = null;
290             if ( request.getBaseDirectory() != null )
291             {
292                 reactorDirectory = new File( request.getBaseDirectory() );
293             }
294 
295             String selector = request.getResumeFrom();
296 
297             result = new ArrayList<>( projects.size() );
298 
299             boolean resumed = false;
300 
301             for ( MavenProject project : projects )
302             {
303                 if ( !resumed && isMatchingProject( project, selector, reactorDirectory ) )
304                 {
305                     resumed = true;
306                 }
307 
308                 if ( resumed )
309                 {
310                     result.add( project );
311                 }
312             }
313 
314             if ( !resumed )
315             {
316                 throw new MavenExecutionException( "Could not find project to resume reactor build from: " + selector
317                     + " vs " + projects, request.getPom() );
318             }
319         }
320 
321         return result;
322     }
323 
324     private boolean isMatchingProject( MavenProject project, String selector, File reactorDirectory )
325     {
326         // [groupId]:artifactId
327         if ( selector.indexOf( ':' ) >= 0 )
328         {
329             String id = ':' + project.getArtifactId();
330 
331             if ( id.equals( selector ) )
332             {
333                 return true;
334             }
335 
336             id = project.getGroupId() + id;
337 
338             if ( id.equals( selector ) )
339             {
340                 return true;
341             }
342         }
343 
344         // relative path, e.g. "sub", "../sub" or "."
345         else if ( reactorDirectory != null )
346         {
347             File selectedProject = new File( new File( reactorDirectory, selector ).toURI().normalize() );
348 
349             if ( selectedProject.isFile() )
350             {
351                 return selectedProject.equals( project.getFile() );
352             }
353             else if ( selectedProject.isDirectory() )
354             {
355                 return selectedProject.equals( project.getBasedir() );
356             }
357         }
358 
359         return false;
360     }
361 
362     // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
363     //
364     // Project collection
365     //
366     // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
367 
368     private List<MavenProject> getProjectsForMavenReactor( MavenSession session )
369         throws ProjectBuildingException
370     {
371         MavenExecutionRequest request = session.getRequest();
372 
373         request.getProjectBuildingRequest().setRepositorySession( session.getRepositorySession() );
374 
375         List<MavenProject> projects = new ArrayList<>();
376 
377         // We have no POM file.
378         //
379         if ( request.getPom() == null )
380         {
381             ModelSource modelSource = new UrlModelSource( DefaultMaven.class.getResource( "project/standalone.xml" ) );
382             MavenProject project = projectBuilder.build( modelSource, request.getProjectBuildingRequest() )
383                 .getProject();
384             project.setExecutionRoot( true );
385             projects.add( project );
386             request.setProjectPresent( false );
387             return projects;
388         }
389 
390         List<File> files = Arrays.asList( request.getPom().getAbsoluteFile() );
391         collectProjects( projects, files, request );
392         return projects;
393     }
394 
395     private void collectProjects( List<MavenProject> projects, List<File> files, MavenExecutionRequest request )
396         throws ProjectBuildingException
397     {
398         ProjectBuildingRequest projectBuildingRequest = request.getProjectBuildingRequest();
399 
400         List<ProjectBuildingResult> results = projectBuilder.build( files, request.isRecursive(),
401                                                                     projectBuildingRequest );
402 
403         boolean problems = false;
404 
405         for ( ProjectBuildingResult result : results )
406         {
407             projects.add( result.getProject() );
408 
409             if ( !result.getProblems().isEmpty() && logger.isWarnEnabled() )
410             {
411                 logger.warn( "" );
412                 logger.warn( "Some problems were encountered while building the effective model for "
413                     + result.getProject().getId() );
414 
415                 for ( ModelProblem problem : result.getProblems() )
416                 {
417                     String loc = ModelProblemUtils.formatLocation( problem, result.getProjectId() );
418                     logger.warn( problem.getMessage() + ( StringUtils.isNotEmpty( loc ) ? " @ " + loc : "" ) );
419                 }
420 
421                 problems = true;
422             }
423         }
424 
425         if ( problems )
426         {
427             logger.warn( "" );
428             logger.warn( "It is highly recommended to fix these problems"
429                 + " because they threaten the stability of your build." );
430             logger.warn( "" );
431             logger.warn( "For this reason, future Maven versions might no"
432                 + " longer support building such malformed projects." );
433             logger.warn( "" );
434         }
435     }
436 
437     private void validateProjects( List<MavenProject> projects )
438     {
439         Map<String, MavenProject> projectsMap = new HashMap<>();
440 
441         for ( MavenProject p : projects )
442         {
443             String projectKey = ArtifactUtils.key( p.getGroupId(), p.getArtifactId(), p.getVersion() );
444 
445             projectsMap.put( projectKey, p );
446         }
447 
448         for ( MavenProject project : projects )
449         {
450             // MNG-1911 / MNG-5572: Building plugins with extensions cannot be part of reactor
451             for ( Plugin plugin : project.getBuildPlugins() )
452             {
453                 if ( plugin.isExtensions() )
454                 {
455                     String pluginKey = ArtifactUtils.key( plugin.getGroupId(), plugin.getArtifactId(),
456                                                           plugin.getVersion() );
457 
458                     if ( projectsMap.containsKey( pluginKey ) )
459                     {
460                         logger.warn( project.getName() + " uses " + plugin.getKey()
461                             + " as extensions, which is not possible within the same reactor build. "
462                             + "This plugin was pulled from the local repository!" );
463                     }
464                 }
465             }
466         }
467     }
468 
469 }