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