001package org.apache.maven.graph;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.File;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Collection;
026import java.util.HashMap;
027import java.util.LinkedHashSet;
028import java.util.List;
029import java.util.Map;
030
031import org.apache.maven.DefaultMaven;
032import org.apache.maven.MavenExecutionException;
033import org.apache.maven.ProjectCycleException;
034import org.apache.maven.artifact.ArtifactUtils;
035import org.apache.maven.execution.MavenExecutionRequest;
036import org.apache.maven.execution.MavenExecutionResult;
037import org.apache.maven.execution.MavenSession;
038import org.apache.maven.execution.ProjectDependencyGraph;
039import org.apache.maven.model.Plugin;
040import org.apache.maven.model.building.DefaultModelProblem;
041import org.apache.maven.model.building.ModelProblem;
042import org.apache.maven.model.building.ModelProblemUtils;
043import org.apache.maven.model.building.ModelSource;
044import org.apache.maven.model.building.Result;
045import org.apache.maven.model.building.UrlModelSource;
046import org.apache.maven.project.MavenProject;
047import org.apache.maven.project.ProjectBuilder;
048import org.apache.maven.project.ProjectBuildingException;
049import org.apache.maven.project.ProjectBuildingRequest;
050import org.apache.maven.project.ProjectBuildingResult;
051import org.codehaus.plexus.component.annotations.Component;
052import org.codehaus.plexus.component.annotations.Requirement;
053import org.codehaus.plexus.logging.Logger;
054import org.codehaus.plexus.util.StringUtils;
055import org.codehaus.plexus.util.dag.CycleDetectedException;
056
057import com.google.common.collect.Lists;
058
059@Component( role = GraphBuilder.class, hint = GraphBuilder.HINT )
060public class DefaultGraphBuilder
061    implements GraphBuilder
062{
063    @Requirement
064    private Logger logger;
065
066    @Requirement
067    protected ProjectBuilder projectBuilder;
068
069    @Override
070    public Result<ProjectDependencyGraph> build( MavenSession session )
071    {
072        if ( session.getProjectDependencyGraph() != null )
073        {
074            return dependencyGraph( session, session.getProjects(), false );
075        }
076        
077        List<MavenProject> projects = session.getProjects();
078
079        if ( projects == null )
080        {
081            try
082            {
083                projects = getProjectsForMavenReactor( session );
084            }
085            catch ( ProjectBuildingException e )
086            {
087                return Result.error( Lists.newArrayList( new DefaultModelProblem( null, null, null, null, 0, 0, e ) ) );
088            }
089
090            validateProjects( projects );
091
092            return dependencyGraph( session, projects, true );
093        }
094        else
095        {
096            return dependencyGraph( session, projects, false );
097        }
098    }
099    
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}