| package org.apache.maven.graph; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import javax.inject.Inject; |
| import javax.inject.Named; |
| import javax.inject.Singleton; |
| |
| import org.apache.maven.MavenExecutionException; |
| import org.apache.maven.ProjectCycleException; |
| import org.apache.maven.artifact.ArtifactUtils; |
| import org.apache.maven.execution.BuildResumptionDataRepository; |
| import org.apache.maven.execution.MavenExecutionRequest; |
| import org.apache.maven.execution.MavenSession; |
| import org.apache.maven.execution.ProjectDependencyGraph; |
| import org.apache.maven.model.Plugin; |
| import org.apache.maven.model.building.DefaultModelProblem; |
| import org.apache.maven.model.building.Result; |
| import org.apache.maven.project.DuplicateProjectException; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.maven.project.ProjectBuildingException; |
| import org.apache.maven.project.collector.MultiModuleCollectionStrategy; |
| import org.apache.maven.project.collector.PomlessCollectionStrategy; |
| import org.apache.maven.project.collector.RequestPomCollectionStrategy; |
| import org.codehaus.plexus.util.StringUtils; |
| import org.codehaus.plexus.util.dag.CycleDetectedException; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import static java.util.Comparator.comparing; |
| |
| /** |
| * Builds the {@link ProjectDependencyGraph inter-dependencies graph} between projects in the reactor. |
| */ |
| @Named( GraphBuilder.HINT ) |
| @Singleton |
| public class DefaultGraphBuilder |
| implements GraphBuilder |
| { |
| private static final Logger LOGGER = LoggerFactory.getLogger( DefaultGraphBuilder.class ); |
| |
| private final BuildResumptionDataRepository buildResumptionDataRepository; |
| private final PomlessCollectionStrategy pomlessCollectionStrategy; |
| private final MultiModuleCollectionStrategy multiModuleCollectionStrategy; |
| private final RequestPomCollectionStrategy requestPomCollectionStrategy; |
| |
| @Inject |
| public DefaultGraphBuilder( BuildResumptionDataRepository buildResumptionDataRepository, |
| PomlessCollectionStrategy pomlessCollectionStrategy, |
| MultiModuleCollectionStrategy multiModuleCollectionStrategy, |
| RequestPomCollectionStrategy requestPomCollectionStrategy ) |
| { |
| this.buildResumptionDataRepository = buildResumptionDataRepository; |
| this.pomlessCollectionStrategy = pomlessCollectionStrategy; |
| this.multiModuleCollectionStrategy = multiModuleCollectionStrategy; |
| this.requestPomCollectionStrategy = requestPomCollectionStrategy; |
| } |
| |
| @Override |
| public Result<ProjectDependencyGraph> build( MavenSession session ) |
| { |
| try |
| { |
| Result<ProjectDependencyGraph> result = sessionDependencyGraph( session ); |
| |
| if ( result == null ) |
| { |
| final List<MavenProject> projects = getProjectsForMavenReactor( session ); |
| validateProjects( projects, session.getRequest() ); |
| enrichRequestFromResumptionData( projects, session.getRequest() ); |
| result = reactorDependencyGraph( session, projects ); |
| } |
| |
| return result; |
| } |
| catch ( final ProjectBuildingException | DuplicateProjectException | MavenExecutionException e ) |
| { |
| return Result.error( Collections.singletonList |
| ( new DefaultModelProblem ( null, null, null, null, 0, 0, e ) ) ); |
| } |
| catch ( final CycleDetectedException e ) |
| { |
| String message = "The projects in the reactor contain a cyclic reference: " + e.getMessage(); |
| ProjectCycleException error = new ProjectCycleException( message, e ); |
| return Result.error( Collections.singletonList( |
| new DefaultModelProblem( null, null, null, null, 0, 0, error ) ) ); |
| } |
| } |
| |
| private Result<ProjectDependencyGraph> sessionDependencyGraph( final MavenSession session ) |
| throws CycleDetectedException, DuplicateProjectException |
| { |
| Result<ProjectDependencyGraph> result = null; |
| |
| if ( session.getProjectDependencyGraph() != null || session.getProjects() != null ) |
| { |
| final ProjectDependencyGraph graph = |
| new DefaultProjectDependencyGraph( session.getAllProjects(), session.getProjects() ); |
| |
| result = Result.success( graph ); |
| } |
| |
| return result; |
| } |
| |
| private Result<ProjectDependencyGraph> reactorDependencyGraph( MavenSession session, List<MavenProject> projects ) |
| throws CycleDetectedException, DuplicateProjectException, MavenExecutionException |
| { |
| ProjectDependencyGraph projectDependencyGraph = new DefaultProjectDependencyGraph( projects ); |
| List<MavenProject> activeProjects = projectDependencyGraph.getSortedProjects(); |
| activeProjects = trimProjectsToRequest( activeProjects, projectDependencyGraph, session.getRequest() ); |
| activeProjects = trimSelectedProjects( activeProjects, projectDependencyGraph, session.getRequest() ); |
| activeProjects = trimResumedProjects( activeProjects, projectDependencyGraph, session.getRequest() ); |
| activeProjects = trimExcludedProjects( activeProjects, session.getRequest() ); |
| |
| if ( activeProjects.size() != projectDependencyGraph.getSortedProjects().size() ) |
| { |
| projectDependencyGraph = new FilteredProjectDependencyGraph( projectDependencyGraph, activeProjects ); |
| } |
| |
| return Result.success( projectDependencyGraph ); |
| } |
| |
| private List<MavenProject> trimProjectsToRequest( List<MavenProject> activeProjects, |
| ProjectDependencyGraph graph, |
| MavenExecutionRequest request ) |
| throws MavenExecutionException |
| { |
| List<MavenProject> result = activeProjects; |
| |
| if ( request.getPom() != null ) |
| { |
| result = getProjectsInRequestScope( request, activeProjects ); |
| |
| List<MavenProject> sortedProjects = graph.getSortedProjects(); |
| result.sort( comparing( sortedProjects::indexOf ) ); |
| |
| result = includeAlsoMakeTransitively( result, request, graph ); |
| } |
| |
| return result; |
| } |
| |
| private List<MavenProject> trimSelectedProjects( List<MavenProject> projects, ProjectDependencyGraph graph, |
| MavenExecutionRequest request ) |
| throws MavenExecutionException |
| { |
| List<MavenProject> result = projects; |
| |
| if ( !request.getSelectedProjects().isEmpty() ) |
| { |
| File reactorDirectory = getReactorDirectory( request ); |
| |
| Collection<MavenProject> selectedProjects = new LinkedHashSet<>(); |
| |
| for ( String selector : request.getSelectedProjects() ) |
| { |
| MavenProject selectedProject = projects.stream() |
| .filter( project -> isMatchingProject( project, selector, reactorDirectory ) ) |
| .findFirst() |
| .orElseThrow( () -> new MavenExecutionException( |
| "Could not find the selected project in the reactor: " + selector, request.getPom() ) ); |
| selectedProjects.add( selectedProject ); |
| |
| List<MavenProject> children = selectedProject.getCollectedProjects(); |
| if ( children != null ) |
| { |
| selectedProjects.addAll( children ); |
| } |
| } |
| |
| result = new ArrayList<>( selectedProjects ); |
| |
| result = includeAlsoMakeTransitively( result, request, graph ); |
| |
| // Order the new list in the original order |
| List<MavenProject> sortedProjects = graph.getSortedProjects(); |
| result.sort( comparing( sortedProjects::indexOf ) ); |
| } |
| |
| return result; |
| } |
| |
| private List<MavenProject> trimResumedProjects( List<MavenProject> projects, ProjectDependencyGraph graph, |
| MavenExecutionRequest request ) |
| throws MavenExecutionException |
| { |
| List<MavenProject> result = projects; |
| |
| if ( StringUtils.isNotEmpty( request.getResumeFrom() ) ) |
| { |
| File reactorDirectory = getReactorDirectory( request ); |
| |
| String selector = request.getResumeFrom(); |
| |
| MavenProject resumingFromProject = projects.stream() |
| .filter( project -> isMatchingProject( project, selector, reactorDirectory ) ) |
| .findFirst() |
| .orElseThrow( () -> new MavenExecutionException( |
| "Could not find project to resume reactor build from: " + selector + " vs " |
| + formatProjects( projects ), request.getPom() ) ); |
| int resumeFromProjectIndex = projects.indexOf( resumingFromProject ); |
| List<MavenProject> retainingProjects = result.subList( resumeFromProjectIndex, projects.size() ); |
| |
| result = includeAlsoMakeTransitively( retainingProjects, request, graph ); |
| } |
| |
| return result; |
| } |
| |
| private List<MavenProject> trimExcludedProjects( List<MavenProject> projects, MavenExecutionRequest request ) |
| throws MavenExecutionException |
| { |
| List<MavenProject> result = projects; |
| |
| if ( !request.getExcludedProjects().isEmpty() ) |
| { |
| File reactorDirectory = getReactorDirectory( request ); |
| |
| result = new ArrayList<>( projects ); |
| |
| for ( String selector : request.getExcludedProjects() ) |
| { |
| MavenProject excludedProject = projects.stream() |
| .filter( project -> isMatchingProject( project, selector, reactorDirectory ) ) |
| .findFirst() |
| .orElseThrow( () -> new MavenExecutionException( "Could not find the selected project in " |
| + "the reactor: " + selector, request.getPom() ) ); |
| result.remove( excludedProject ); |
| } |
| } |
| |
| return result; |
| } |
| |
| private List<MavenProject> includeAlsoMakeTransitively( List<MavenProject> projects, MavenExecutionRequest request, |
| ProjectDependencyGraph graph ) |
| throws MavenExecutionException |
| { |
| List<MavenProject> result = projects; |
| |
| String makeBehavior = request.getMakeBehavior(); |
| boolean makeBoth = MavenExecutionRequest.REACTOR_MAKE_BOTH.equals( makeBehavior ); |
| |
| boolean makeUpstream = makeBoth || MavenExecutionRequest.REACTOR_MAKE_UPSTREAM.equals( makeBehavior ); |
| boolean makeDownstream = makeBoth || MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM.equals( makeBehavior ); |
| |
| if ( StringUtils.isNotEmpty( makeBehavior ) && !makeUpstream && !makeDownstream ) |
| { |
| throw new MavenExecutionException( "Invalid reactor make behavior: " + makeBehavior, |
| request.getPom() ); |
| } |
| |
| if ( makeUpstream || makeDownstream ) |
| { |
| Set<MavenProject> projectsSet = new HashSet<>( projects ); |
| |
| for ( MavenProject project : projects ) |
| { |
| if ( makeUpstream ) |
| { |
| projectsSet.addAll( graph.getUpstreamProjects( project, true ) ); |
| } |
| if ( makeDownstream ) |
| { |
| projectsSet.addAll( graph.getDownstreamProjects( project, true ) ); |
| } |
| } |
| |
| result = new ArrayList<>( projectsSet ); |
| |
| // Order the new list in the original order |
| List<MavenProject> sortedProjects = graph.getSortedProjects(); |
| result.sort( comparing( sortedProjects::indexOf ) ); |
| } |
| |
| return result; |
| } |
| |
| private void enrichRequestFromResumptionData( List<MavenProject> projects, MavenExecutionRequest request ) |
| { |
| if ( request.isResume() ) |
| { |
| projects.stream() |
| .filter( MavenProject::isExecutionRoot ) |
| .findFirst() |
| .ifPresent( rootProject -> |
| buildResumptionDataRepository.applyResumptionData( request, rootProject ) ); |
| } |
| } |
| |
| private List<MavenProject> getProjectsInRequestScope( MavenExecutionRequest request, List<MavenProject> projects ) |
| throws MavenExecutionException |
| { |
| if ( request.getPom() == null ) |
| { |
| return projects; |
| } |
| |
| MavenProject requestPomProject = projects.stream() |
| .filter( project -> request.getPom().equals( project.getFile() ) ) |
| .findFirst() |
| .orElseThrow( () -> new MavenExecutionException( |
| "Could not find a project in reactor matching the request POM", request.getPom() ) ); |
| |
| List<MavenProject> modules = requestPomProject.getCollectedProjects() != null |
| ? requestPomProject.getCollectedProjects() : Collections.emptyList(); |
| |
| List<MavenProject> result = new ArrayList<>( modules ); |
| result.add( requestPomProject ); |
| return result; |
| } |
| |
| private String formatProjects( List<MavenProject> projects ) |
| { |
| StringBuilder projectNames = new StringBuilder(); |
| Iterator<MavenProject> iterator = projects.iterator(); |
| while ( iterator.hasNext() ) |
| { |
| MavenProject project = iterator.next(); |
| projectNames.append( project.getGroupId() ).append( ":" ).append( project.getArtifactId() ); |
| if ( iterator.hasNext() ) |
| { |
| projectNames.append( ", " ); |
| } |
| } |
| return projectNames.toString(); |
| } |
| |
| private boolean isMatchingProject( MavenProject project, String selector, File reactorDirectory ) |
| { |
| // [groupId]:artifactId |
| if ( selector.indexOf( ':' ) >= 0 ) |
| { |
| String id = ':' + project.getArtifactId(); |
| |
| if ( id.equals( selector ) ) |
| { |
| return true; |
| } |
| |
| id = project.getGroupId() + id; |
| |
| if ( id.equals( selector ) ) |
| { |
| return true; |
| } |
| } |
| |
| // relative path, e.g. "sub", "../sub" or "." |
| else if ( reactorDirectory != null ) |
| { |
| File selectedProject = new File( new File( reactorDirectory, selector ).toURI().normalize() ); |
| |
| if ( selectedProject.isFile() ) |
| { |
| return selectedProject.equals( project.getFile() ); |
| } |
| else if ( selectedProject.isDirectory() ) |
| { |
| return selectedProject.equals( project.getBasedir() ); |
| } |
| } |
| |
| return false; |
| } |
| |
| private File getReactorDirectory( MavenExecutionRequest request ) |
| { |
| if ( request.getBaseDirectory() != null ) |
| { |
| return new File( request.getBaseDirectory() ); |
| } |
| |
| return null; |
| } |
| |
| // //////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| // |
| // Project collection |
| // |
| // //////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| private List<MavenProject> getProjectsForMavenReactor( MavenSession session ) |
| throws ProjectBuildingException |
| { |
| MavenExecutionRequest request = session.getRequest(); |
| request.getProjectBuildingRequest().setRepositorySession( session.getRepositorySession() ); |
| |
| // 1. Collect project for invocation without a POM. |
| if ( request.getPom() == null ) |
| { |
| return pomlessCollectionStrategy.collectProjects( request ); |
| } |
| |
| // 2. Collect projects for all modules in the multi-module project. |
| List<MavenProject> projects = multiModuleCollectionStrategy.collectProjects( request ); |
| if ( !projects.isEmpty() ) |
| { |
| return projects; |
| } |
| |
| // 3. Collect projects for explicitly requested POM. |
| return requestPomCollectionStrategy.collectProjects( request ); |
| } |
| |
| private void validateProjects( List<MavenProject> projects, MavenExecutionRequest request ) |
| throws MavenExecutionException |
| { |
| Map<String, MavenProject> projectsMap = new HashMap<>(); |
| |
| List<MavenProject> projectsInRequestScope = getProjectsInRequestScope( request, projects ); |
| for ( MavenProject p : projectsInRequestScope ) |
| { |
| String projectKey = ArtifactUtils.key( p.getGroupId(), p.getArtifactId(), p.getVersion() ); |
| |
| projectsMap.put( projectKey, p ); |
| } |
| |
| for ( MavenProject project : projects ) |
| { |
| // MNG-1911 / MNG-5572: Building plugins with extensions cannot be part of reactor |
| for ( Plugin plugin : project.getBuildPlugins() ) |
| { |
| if ( plugin.isExtensions() ) |
| { |
| String pluginKey = ArtifactUtils.key( plugin.getGroupId(), plugin.getArtifactId(), |
| plugin.getVersion() ); |
| |
| if ( projectsMap.containsKey( pluginKey ) ) |
| { |
| LOGGER.warn( "'{}' uses '{}' as extension which is not possible within the same reactor build. " |
| + "This plugin was pulled from the local repository!", project.getName(), plugin.getKey() ); |
| } |
| } |
| } |
| } |
| } |
| |
| } |