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 org.apache.maven.DefaultMaven;
32  import org.apache.maven.MavenExecutionException;
33  import org.apache.maven.ProjectCycleException;
34  import org.apache.maven.artifact.ArtifactUtils;
35  import org.apache.maven.execution.MavenExecutionRequest;
36  import org.apache.maven.execution.MavenExecutionResult;
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.MavenProject;
47  import org.apache.maven.project.ProjectBuilder;
48  import org.apache.maven.project.ProjectBuildingException;
49  import org.apache.maven.project.ProjectBuildingRequest;
50  import org.apache.maven.project.ProjectBuildingResult;
51  import org.codehaus.plexus.component.annotations.Component;
52  import org.codehaus.plexus.component.annotations.Requirement;
53  import org.codehaus.plexus.logging.Logger;
54  import org.codehaus.plexus.util.StringUtils;
55  import org.codehaus.plexus.util.dag.CycleDetectedException;
56  
57  import com.google.common.collect.Lists;
58  
59  @Component( role = GraphBuilder.class, hint = GraphBuilder.HINT )
60  public class DefaultGraphBuilder
61      implements GraphBuilder
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          if ( session.getProjectDependencyGraph() != null )
73          {
74              return dependencyGraph( session, session.getProjects(), false );
75          }
76          
77          List<MavenProject> projects = session.getProjects();
78  
79          if ( projects == null )
80          {
81              try
82              {
83                  projects = getProjectsForMavenReactor( session );
84              }
85              catch ( ProjectBuildingException e )
86              {
87                  return Result.error( Lists.newArrayList( new DefaultModelProblem( null, null, null, null, 0, 0, e ) ) );
88              }
89  
90              validateProjects( projects );
91  
92              return dependencyGraph( session, projects, true );
93          }
94          else
95          {
96              return dependencyGraph( session, projects, false );
97          }
98      }
99      
100     private Result<ProjectDependencyGraph> dependencyGraph( MavenSession session, List<MavenProject> projects,
101                                                             boolean applyMakeBehaviour )
102     {
103         MavenExecutionRequest request = session.getRequest();
104 
105         ProjectDependencyGraph projectDependencyGraph = null;
106 
107         try
108         {
109             projectDependencyGraph = new DefaultProjectDependencyGraph( projects );
110 
111             if ( applyMakeBehaviour )
112             {
113                 List<MavenProject> activeProjects = projectDependencyGraph.getSortedProjects();
114 
115                 activeProjects = trimSelectedProjects( activeProjects, projectDependencyGraph, request );
116                 activeProjects = trimExcludedProjects( activeProjects, request );
117                 activeProjects = trimResumedProjects( activeProjects, request );
118 
119                 if ( activeProjects.size() != projectDependencyGraph.getSortedProjects().size() )
120                 {
121                     projectDependencyGraph =
122                         new FilteredProjectDependencyGraph( projectDependencyGraph, activeProjects );
123                 }
124             }
125         }
126         catch ( CycleDetectedException e )
127         {
128             String message = "The projects in the reactor contain a cyclic reference: " + e.getMessage();
129             ProjectCycleException error = new ProjectCycleException( message, e );
130             return Result.error( Lists.newArrayList( new DefaultModelProblem( null, null, null, null, 0, 0, error ) ) );
131         }
132         catch ( org.apache.maven.project.DuplicateProjectException e )
133         {
134             return Result.error( Lists.newArrayList( new DefaultModelProblem( null, null, null, null, 0, 0, e ) ) );
135         }
136         catch ( MavenExecutionException e )
137         {
138             return Result.error( Lists.newArrayList( new DefaultModelProblem( null, null, null, null, 0, 0, e ) ) );
139         }
140 
141         session.setProjects( projectDependencyGraph.getSortedProjects() );
142         session.setProjectDependencyGraph( projectDependencyGraph );
143 
144         return Result.success( projectDependencyGraph );
145     }
146 
147     private List<MavenProject> trimSelectedProjects( List<MavenProject> projects, ProjectDependencyGraph graph,
148                                                      MavenExecutionRequest request )
149         throws MavenExecutionException
150     {
151         List<MavenProject> result = projects;
152 
153         if ( !request.getSelectedProjects().isEmpty() )
154         {
155             File reactorDirectory = null;
156             if ( request.getBaseDirectory() != null )
157             {
158                 reactorDirectory = new File( request.getBaseDirectory() );
159             }
160 
161             Collection<MavenProject> selectedProjects = new LinkedHashSet<>( projects.size() );
162 
163             for ( String selector : request.getSelectedProjects() )
164             {
165                 MavenProject selectedProject = null;
166 
167                 for ( MavenProject project : projects )
168                 {
169                     if ( isMatchingProject( project, selector, reactorDirectory ) )
170                     {
171                         selectedProject = project;
172                         break;
173                     }
174                 }
175 
176                 if ( selectedProject != null )
177                 {
178                     selectedProjects.add( selectedProject );
179                 }
180                 else
181                 {
182                     throw new MavenExecutionException( "Could not find the selected project in the reactor: "
183                         + selector, request.getPom() );
184                 }
185             }
186 
187             boolean makeUpstream = false;
188             boolean makeDownstream = false;
189 
190             if ( MavenExecutionRequest.REACTOR_MAKE_UPSTREAM.equals( request.getMakeBehavior() ) )
191             {
192                 makeUpstream = true;
193             }
194             else if ( MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM.equals( request.getMakeBehavior() ) )
195             {
196                 makeDownstream = true;
197             }
198             else if ( MavenExecutionRequest.REACTOR_MAKE_BOTH.equals( request.getMakeBehavior() ) )
199             {
200                 makeUpstream = true;
201                 makeDownstream = true;
202             }
203             else if ( StringUtils.isNotEmpty( request.getMakeBehavior() ) )
204             {
205                 throw new MavenExecutionException( "Invalid reactor make behavior: " + request.getMakeBehavior(),
206                                                    request.getPom() );
207             }
208 
209             if ( makeUpstream || makeDownstream )
210             {
211                 for ( MavenProject selectedProject : new ArrayList<>( selectedProjects ) )
212                 {
213                     if ( makeUpstream )
214                     {
215                         selectedProjects.addAll( graph.getUpstreamProjects( selectedProject, true ) );
216                     }
217                     if ( makeDownstream )
218                     {
219                         selectedProjects.addAll( graph.getDownstreamProjects( selectedProject, true ) );
220                     }
221                 }
222             }
223 
224             result = new ArrayList<>( selectedProjects.size() );
225 
226             for ( MavenProject project : projects )
227             {
228                 if ( selectedProjects.contains( project ) )
229                 {
230                     result.add( project );
231                 }
232             }
233         }
234 
235         return result;
236     }
237 
238     private List<MavenProject> trimExcludedProjects( List<MavenProject> projects, MavenExecutionRequest request )
239         throws MavenExecutionException
240     {
241         List<MavenProject> result = projects;
242 
243         if ( !request.getExcludedProjects().isEmpty() )
244         {
245             File reactorDirectory = null;
246 
247             if ( request.getBaseDirectory() != null )
248             {
249                 reactorDirectory = new File( request.getBaseDirectory() );
250             }
251 
252             Collection<MavenProject> excludedProjects = new LinkedHashSet<>( projects.size() );
253 
254             for ( String selector : request.getExcludedProjects() )
255             {
256                 MavenProject excludedProject = null;
257 
258                 for ( MavenProject project : projects )
259                 {
260                     if ( isMatchingProject( project, selector, reactorDirectory ) )
261                     {
262                         excludedProject = project;
263                         break;
264                     }
265                 }
266 
267                 if ( excludedProject != null )
268                 {
269                     excludedProjects.add( excludedProject );
270                 }
271                 else
272                 {
273                     throw new MavenExecutionException( "Could not find the selected project in the reactor: "
274                         + selector, request.getPom() );
275                 }
276             }
277 
278             result = new ArrayList<>( projects.size() );
279             for ( MavenProject project : projects )
280             {
281                 if ( !excludedProjects.contains( project ) )
282                 {
283                     result.add( project );
284                 }
285             }
286         }
287 
288         return result;
289     }
290 
291     private List<MavenProject> trimResumedProjects( List<MavenProject> projects, MavenExecutionRequest request )
292         throws MavenExecutionException
293     {
294         List<MavenProject> result = projects;
295 
296         if ( StringUtils.isNotEmpty( request.getResumeFrom() ) )
297         {
298             File reactorDirectory = null;
299             if ( request.getBaseDirectory() != null )
300             {
301                 reactorDirectory = new File( request.getBaseDirectory() );
302             }
303 
304             String selector = request.getResumeFrom();
305 
306             result = new ArrayList<>( projects.size() );
307 
308             boolean resumed = false;
309 
310             for ( MavenProject project : projects )
311             {
312                 if ( !resumed && isMatchingProject( project, selector, reactorDirectory ) )
313                 {
314                     resumed = true;
315                 }
316 
317                 if ( resumed )
318                 {
319                     result.add( project );
320                 }
321             }
322 
323             if ( !resumed )
324             {
325                 throw new MavenExecutionException( "Could not find project to resume reactor build from: " + selector
326                     + " vs " + projects, request.getPom() );
327             }
328         }
329 
330         return result;
331     }
332 
333     private boolean isMatchingProject( MavenProject project, String selector, File reactorDirectory )
334     {
335         // [groupId]:artifactId
336         if ( selector.indexOf( ':' ) >= 0 )
337         {
338             String id = ':' + project.getArtifactId();
339 
340             if ( id.equals( selector ) )
341             {
342                 return true;
343             }
344 
345             id = project.getGroupId() + id;
346 
347             if ( id.equals( selector ) )
348             {
349                 return true;
350             }
351         }
352 
353         // relative path, e.g. "sub", "../sub" or "."
354         else if ( reactorDirectory != null )
355         {
356             File selectedProject = new File( new File( reactorDirectory, selector ).toURI().normalize() );
357 
358             if ( selectedProject.isFile() )
359             {
360                 return selectedProject.equals( project.getFile() );
361             }
362             else if ( selectedProject.isDirectory() )
363             {
364                 return selectedProject.equals( project.getBasedir() );
365             }
366         }
367 
368         return false;
369     }
370 
371     private MavenExecutionResult addExceptionToResult( MavenExecutionResult result, Throwable e )
372     {
373         if ( !result.getExceptions().contains( e ) )
374         {
375             result.addException( e );
376         }
377 
378         return result;
379     }
380 
381     // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
382     //
383     // Project collection
384     //
385     // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
386 
387     private List<MavenProject> getProjectsForMavenReactor( MavenSession session )
388         throws ProjectBuildingException
389     {
390         MavenExecutionRequest request = session.getRequest();
391 
392         request.getProjectBuildingRequest().setRepositorySession( session.getRepositorySession() );
393 
394         List<MavenProject> projects = new ArrayList<>();
395 
396         // We have no POM file.
397         //
398         if ( request.getPom() == null )
399         {
400             ModelSource modelSource = new UrlModelSource( DefaultMaven.class.getResource( "project/standalone.xml" ) );
401             MavenProject project = projectBuilder.build( modelSource, request.getProjectBuildingRequest() )
402                 .getProject();
403             project.setExecutionRoot( true );
404             projects.add( project );
405             request.setProjectPresent( false );
406             return projects;
407         }
408 
409         List<File> files = Arrays.asList( request.getPom().getAbsoluteFile() );
410         collectProjects( projects, files, request );
411         return projects;
412     }
413 
414     private void collectProjects( List<MavenProject> projects, List<File> files, MavenExecutionRequest request )
415         throws ProjectBuildingException
416     {
417         ProjectBuildingRequest projectBuildingRequest = request.getProjectBuildingRequest();
418 
419         List<ProjectBuildingResult> results = projectBuilder.build( files, request.isRecursive(),
420                                                                     projectBuildingRequest );
421 
422         boolean problems = false;
423 
424         for ( ProjectBuildingResult result : results )
425         {
426             projects.add( result.getProject() );
427 
428             if ( !result.getProblems().isEmpty() && logger.isWarnEnabled() )
429             {
430                 logger.warn( "" );
431                 logger.warn( "Some problems were encountered while building the effective model for "
432                     + result.getProject().getId() );
433 
434                 for ( ModelProblem problem : result.getProblems() )
435                 {
436                     String loc = ModelProblemUtils.formatLocation( problem, result.getProjectId() );
437                     logger.warn( problem.getMessage() + ( StringUtils.isNotEmpty( loc ) ? " @ " + loc : "" ) );
438                 }
439 
440                 problems = true;
441             }
442         }
443 
444         if ( problems )
445         {
446             logger.warn( "" );
447             logger.warn( "It is highly recommended to fix these problems"
448                 + " because they threaten the stability of your build." );
449             logger.warn( "" );
450             logger.warn( "For this reason, future Maven versions might no"
451                 + " longer support building such malformed projects." );
452             logger.warn( "" );
453         }
454     }
455 
456     private void validateProjects( List<MavenProject> projects )
457     {
458         Map<String, MavenProject> projectsMap = new HashMap<>();
459 
460         for ( MavenProject p : projects )
461         {
462             String projectKey = ArtifactUtils.key( p.getGroupId(), p.getArtifactId(), p.getVersion() );
463 
464             projectsMap.put( projectKey, p );
465         }
466 
467         for ( MavenProject project : projects )
468         {
469             // MNG-1911 / MNG-5572: Building plugins with extensions cannot be part of reactor
470             for ( Plugin plugin : project.getBuildPlugins() )
471             {
472                 if ( plugin.isExtensions() )
473                 {
474                     String pluginKey = ArtifactUtils.key( plugin.getGroupId(), plugin.getArtifactId(),
475                                                           plugin.getVersion() );
476 
477                     if ( projectsMap.containsKey( pluginKey ) )
478                     {
479                         logger.warn( project.getName() + " uses " + plugin.getKey()
480                             + " as extensions, which is not possible within the same reactor build. "
481                             + "This plugin was pulled from the local repository!" );
482                     }
483                 }
484             }
485         }
486     }
487 
488 }