[MNG-6118] Make Maven aware of all projects in a multi module project when using -f or when changing directory to a submodule.

This closes #373
This commit is contained in:
Maarten Mulders 2020-07-24 16:52:02 +02:00 committed by Martin Kanters
parent 8393b9e0d9
commit a5b292ddf8
10 changed files with 738 additions and 164 deletions

View File

@ -21,7 +21,6 @@ package org.apache.maven.graph;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@ -36,7 +35,6 @@ import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.maven.DefaultMaven;
import org.apache.maven.MavenExecutionException;
import org.apache.maven.ProjectCycleException;
import org.apache.maven.artifact.ArtifactUtils;
@ -46,20 +44,17 @@ 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.ModelProblem;
import org.apache.maven.model.building.ModelProblemUtils;
import org.apache.maven.model.building.ModelSource;
import org.apache.maven.model.building.Result;
import org.apache.maven.model.building.UrlModelSource;
import org.apache.maven.project.DuplicateProjectException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.project.ProjectBuildingResult;
import org.codehaus.plexus.logging.Logger;
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;
@ -71,15 +66,24 @@ import static java.util.Comparator.comparing;
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
private Logger logger;
@Inject
protected ProjectBuilder projectBuilder;
@Inject
private BuildResumptionDataRepository buildResumptionDataRepository;
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 )
@ -91,7 +95,7 @@ public class DefaultGraphBuilder
if ( result == null )
{
final List<MavenProject> projects = getProjectsForMavenReactor( session );
validateProjects( projects );
validateProjects( projects, session.getRequest() );
enrichRequestFromResumptionData( projects, session.getRequest() );
result = reactorDependencyGraph( session, projects );
}
@ -133,6 +137,7 @@ public class DefaultGraphBuilder
{
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() );
@ -145,6 +150,26 @@ public class DefaultGraphBuilder
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
@ -287,6 +312,28 @@ public class DefaultGraphBuilder
}
}
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();
@ -361,76 +408,32 @@ public class DefaultGraphBuilder
throws ProjectBuildingException
{
MavenExecutionRequest request = session.getRequest();
request.getProjectBuildingRequest().setRepositorySession( session.getRepositorySession() );
List<MavenProject> projects = new ArrayList<>();
// We have no POM file.
//
// 1. Collect project for invocation without a POM.
if ( request.getPom() == null )
{
ModelSource modelSource = new UrlModelSource( DefaultMaven.class.getResource( "project/standalone.xml" ) );
MavenProject project = projectBuilder.build( modelSource, request.getProjectBuildingRequest() )
.getProject();
project.setExecutionRoot( true );
projects.add( project );
request.setProjectPresent( false );
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;
}
List<File> files = Arrays.asList( request.getPom().getAbsoluteFile() );
collectProjects( projects, files, request );
return projects;
// 3. Collect projects for explicitly requested POM.
return requestPomCollectionStrategy.collectProjects( request );
}
private void collectProjects( List<MavenProject> projects, List<File> files, MavenExecutionRequest request )
throws ProjectBuildingException
{
ProjectBuildingRequest projectBuildingRequest = request.getProjectBuildingRequest();
List<ProjectBuildingResult> results = projectBuilder.build( files, request.isRecursive(),
projectBuildingRequest );
boolean problems = false;
for ( ProjectBuildingResult result : results )
{
projects.add( result.getProject() );
if ( !result.getProblems().isEmpty() && logger.isWarnEnabled() )
{
logger.warn( "" );
logger.warn( "Some problems were encountered while building the effective model for "
+ result.getProject().getId() );
for ( ModelProblem problem : result.getProblems() )
{
String loc = ModelProblemUtils.formatLocation( problem, result.getProjectId() );
logger.warn( problem.getMessage() + ( StringUtils.isNotEmpty( loc ) ? " @ " + loc : "" ) );
}
problems = true;
}
}
if ( problems )
{
logger.warn( "" );
logger.warn( "It is highly recommended to fix these problems"
+ " because they threaten the stability of your build." );
logger.warn( "" );
logger.warn( "For this reason, future Maven versions might no"
+ " longer support building such malformed projects." );
logger.warn( "" );
}
}
private void validateProjects( List<MavenProject> projects )
private void validateProjects( List<MavenProject> projects, MavenExecutionRequest request )
throws MavenExecutionException
{
Map<String, MavenProject> projectsMap = new HashMap<>();
for ( MavenProject p : projects )
List<MavenProject> projectsInRequestScope = getProjectsInRequestScope( request, projects );
for ( MavenProject p : projectsInRequestScope )
{
String projectKey = ArtifactUtils.key( p.getGroupId(), p.getArtifactId(), p.getVersion() );
@ -449,9 +452,8 @@ public class DefaultGraphBuilder
if ( projectsMap.containsKey( pluginKey ) )
{
logger.warn( project.getName() + " uses " + plugin.getKey()
+ " as extensions, which is not possible within the same reactor build. "
+ "This plugin was pulled from the local repository!" );
LOGGER.warn( "{} uses {} as extensions, which is not possible within the same reactor build. "
+ "This plugin was pulled from the local repository!", project.getName(), plugin.getKey() );
}
}
}

View File

@ -106,7 +106,7 @@ public class DefaultModelBuildingListener
catch ( PluginResolutionException | PluginManagerException | PluginVersionResolutionException e )
{
event.getProblems().add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE )
.setMessage( "Unresolveable build extension: " + e.getMessage() )
.setMessage( "Unresolvable build extension: " + e.getMessage() )
.setException( e ) );
}

View File

@ -0,0 +1,98 @@
package org.apache.maven.project.collector;
/*
* 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 org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.model.building.ModelProblem;
import org.apache.maven.model.building.ModelProblemUtils;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.project.ProjectBuildingResult;
import org.codehaus.plexus.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.File;
import java.util.List;
/**
* Utility to collect projects for a given set of pom.xml files.
*/
@Named
@Singleton
public class DefaultProjectsCollector implements ProjectsCollector
{
private static final Logger LOGGER = LoggerFactory.getLogger( DefaultProjectsCollector.class );
private final ProjectBuilder projectBuilder;
@Inject
public DefaultProjectsCollector( ProjectBuilder projectBuilder )
{
this.projectBuilder = projectBuilder;
}
@Override
public void collectProjects( List<MavenProject> projects, List<File> files, MavenExecutionRequest request )
throws ProjectBuildingException
{
ProjectBuildingRequest projectBuildingRequest = request.getProjectBuildingRequest();
List<ProjectBuildingResult> results = projectBuilder.build( files, request.isRecursive(),
projectBuildingRequest );
boolean problems = false;
for ( ProjectBuildingResult result : results )
{
projects.add( result.getProject() );
if ( !result.getProblems().isEmpty() && LOGGER.isWarnEnabled() )
{
LOGGER.warn( "" );
LOGGER.warn( "Some problems were encountered while building the effective model for {}",
result.getProject().getId() );
for ( ModelProblem problem : result.getProblems() )
{
String loc = ModelProblemUtils.formatLocation( problem, result.getProjectId() );
LOGGER.warn( "{}{}", problem.getMessage(), ( StringUtils.isNotEmpty( loc ) ? " @ " + loc : "" ) );
}
problems = true;
}
}
if ( problems )
{
LOGGER.warn( "" );
LOGGER.warn( "It is highly recommended to fix these problems"
+ " because they threaten the stability of your build." );
LOGGER.warn( "" );
LOGGER.warn( "For this reason, future Maven versions might no"
+ " longer support building such malformed projects." );
LOGGER.warn( "" );
}
}
}

View File

@ -0,0 +1,195 @@
package org.apache.maven.project.collector;
/*
* 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 org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.building.ModelProblem;
import org.apache.maven.model.locator.ModelLocator;
import org.apache.maven.plugin.PluginManagerException;
import org.apache.maven.plugin.PluginResolutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.ProjectBuildingResult;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.transfer.ArtifactNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
/**
* Strategy for collecting Maven projects from the multi-module project root, even when executed in a submodule.
*/
@Named( "MultiModuleCollectionStrategy" )
@Singleton
public class MultiModuleCollectionStrategy implements ProjectCollectionStrategy
{
private final Logger logger = LoggerFactory.getLogger( getClass() );
private final ModelLocator modelLocator;
private final ProjectsCollector projectsCollector;
@Inject
public MultiModuleCollectionStrategy( ModelLocator modelLocator, ProjectsCollector projectsCollector )
{
this.modelLocator = modelLocator;
this.projectsCollector = projectsCollector;
}
@Override
public List<MavenProject> collectProjects( MavenExecutionRequest request ) throws ProjectBuildingException
{
File moduleProjectPomFile = getMultiModuleProjectPomFile( request );
List<File> files = Collections.singletonList( moduleProjectPomFile.getAbsoluteFile() );
try
{
List<MavenProject> projects = new ArrayList<>();
projectsCollector.collectProjects( projects, files, request );
boolean isRequestedProjectCollected = isRequestedProjectCollected( request, projects );
if ( isRequestedProjectCollected )
{
return projects;
}
else
{
logger.debug( "Multi module project collection failed: {}"
+ "Detected a POM file next to a .mvn folder in a parent directory ({}). "
+ "Maven assumed that POM file to be the parent of the requested project ({}), but it turned "
+ "out that it was not. Another project collection strategy will be executed as result.",
System.lineSeparator(), moduleProjectPomFile.getAbsolutePath(),
request.getPom().getAbsolutePath() );
return Collections.emptyList();
}
}
catch ( ProjectBuildingException e )
{
boolean fallThrough = isModuleOutsideRequestScopeDependingOnPluginModule( request, e );
if ( fallThrough )
{
logger.debug( "Multi module project collection failed: {}"
+ "Detected that one of the modules of this multi module project uses another module as "
+ "plugin extension which still needed to be built. This is not possible within the same "
+ "reactor build. Another project collection strategy will be executed as result.",
System.lineSeparator() );
return Collections.emptyList();
}
throw e;
}
}
private File getMultiModuleProjectPomFile( MavenExecutionRequest request )
{
if ( request.getPom().getParentFile().equals( request.getMultiModuleProjectDirectory() ) )
{
return request.getPom();
}
else
{
File multiModuleProjectPom = modelLocator.locatePom( request.getMultiModuleProjectDirectory() );
if ( !multiModuleProjectPom.exists() )
{
logger.info( "Maven detected that the requested POM file is part of a multi module project, "
+ "but could not find a pom.xml file in the multi module root directory: '"
+ request.getMultiModuleProjectDirectory() + "'. " );
logger.info( "The reactor is limited to all projects under: " + request.getPom().getParent() );
return request.getPom();
}
return multiModuleProjectPom;
}
}
/**
* multiModuleProjectDirectory in MavenExecutionRequest is not always the parent of the request pom.
* We should always check whether the request pom project is collected.
* The integration tests for MNG-6223 are examples for this scenario.
*
* @return true if the collected projects contain the requested project (for example with -f)
*/
private boolean isRequestedProjectCollected( MavenExecutionRequest request, List<MavenProject> projects )
{
return projects.stream()
.map( MavenProject::getFile )
.anyMatch( request.getPom()::equals );
}
/**
* This method finds out whether collecting projects failed because of the following scenario:
* - A multi module project containing a module which is a plugin and another module which depends on it.
* - Just the plugin is being built with the -f <pom> flag.
* - Because of inter-module dependency collection, all projects in the multi-module project are collected.
* - The plugin is not yet installed in a repository.
*
* Therefore the build fails because the plugin is not found and plugins cannot be built in the same session.
*
* The integration test for <a href="https://issues.apache.org/jira/browse/MNG-5572">MNG-5572</a> is an
* example of this scenario.
*
* @return true if the module which fails to collect the inter-module plugin is not part of the build.
*/
private boolean isModuleOutsideRequestScopeDependingOnPluginModule( MavenExecutionRequest request,
ProjectBuildingException exception )
{
return exception.getResults().stream()
.map( ProjectBuildingResult::getProject )
.filter( Objects::nonNull )
.filter( project -> request.getPom().equals( project.getFile() ) )
.findFirst()
.map( requestPomProject ->
{
List<MavenProject> modules = requestPomProject.getCollectedProjects() != null
? requestPomProject.getCollectedProjects() : Collections.emptyList();
List<MavenProject> projectsInRequestScope = new ArrayList<>( modules );
projectsInRequestScope.add( requestPomProject );
Predicate<ProjectBuildingResult> projectsOutsideOfRequestScope =
pr -> !projectsInRequestScope.contains( pr.getProject() );
Predicate<Exception> pluginArtifactNotFoundException =
exc -> exc instanceof PluginManagerException
&& exc.getCause() instanceof PluginResolutionException
&& exc.getCause().getCause() instanceof ArtifactResolutionException
&& exc.getCause().getCause().getCause() instanceof ArtifactNotFoundException;
Predicate<Plugin> isPluginPartOfRequestScope = plugin -> projectsInRequestScope.stream()
.anyMatch( project -> project.getGroupId().equals( plugin.getGroupId() )
&& project.getArtifactId().equals( plugin.getArtifactId() )
&& project.getVersion().equals( plugin.getVersion() ) );
return exception.getResults().stream()
.filter( projectsOutsideOfRequestScope )
.flatMap( projectBuildingResult -> projectBuildingResult.getProblems().stream() )
.map( ModelProblem::getException )
.filter( pluginArtifactNotFoundException )
.map( exc -> ( ( PluginResolutionException ) exc.getCause() ).getPlugin() )
.anyMatch( isPluginPartOfRequestScope );
} ).orElse( false );
}
}

View File

@ -0,0 +1,67 @@
package org.apache.maven.project.collector;
/*
* 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 org.apache.maven.DefaultMaven;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.model.building.ModelSource;
import org.apache.maven.model.building.UrlModelSource;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.ProjectBuildingRequest;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.ArrayList;
import java.util.List;
/**
* Strategy to collect projects for building when the Maven invocation is not in a folder that contains a pom.xml.
*/
@Named( "PomlessCollectionStrategy" )
@Singleton
public class PomlessCollectionStrategy
implements ProjectCollectionStrategy
{
private final ProjectBuilder projectBuilder;
@Inject
public PomlessCollectionStrategy( ProjectBuilder projectBuilder )
{
this.projectBuilder = projectBuilder;
}
@Override
public List<MavenProject> collectProjects( final MavenExecutionRequest request )
throws ProjectBuildingException
{
ProjectBuildingRequest buildingRequest = request.getProjectBuildingRequest();
ModelSource modelSource = new UrlModelSource( DefaultMaven.class.getResource( "project/standalone.xml" ) );
MavenProject project = projectBuilder.build( modelSource, buildingRequest ).getProject();
project.setExecutionRoot( true );
request.setProjectPresent( false );
final List<MavenProject> result = new ArrayList<>();
result.add( project );
return result;
}
}

View File

@ -0,0 +1,41 @@
package org.apache.maven.project.collector;
/*
* 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 org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingException;
import java.util.List;
/**
* Describes strategies for finding projects that Maven could build.
*/
public interface ProjectCollectionStrategy
{
/**
*
* @param request
* @return
* @throws ProjectBuildingException
*/
List<MavenProject> collectProjects( final MavenExecutionRequest request )
throws ProjectBuildingException;
}

View File

@ -0,0 +1,43 @@
package org.apache.maven.project.collector;
/*
* 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 org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingException;
import java.io.File;
import java.util.List;
/**
* Facade to collect projects for a given set of pom.xml files.
*/
public interface ProjectsCollector
{
/**
* Collect Maven projects from a list of POM files.
* @param projects List that will be filled with the found projects.
* @param files List of POM files.
* @param request The {@link MavenExecutionRequest}
* @throws ProjectBuildingException In case the POMs are not used.
*/
void collectProjects( List<MavenProject> projects, List<File> files, MavenExecutionRequest request )
throws ProjectBuildingException;
}

View File

@ -0,0 +1,57 @@
package org.apache.maven.project.collector;
/*
* 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 org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingException;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Strategy to collect projects based on the <code>-f</code> CLI parameter or the pom.xml in the working directory.
*/
@Named( "RequestPomCollectionStrategy" )
@Singleton
public class RequestPomCollectionStrategy implements ProjectCollectionStrategy
{
private final ProjectsCollector projectsCollector;
@Inject
public RequestPomCollectionStrategy( ProjectsCollector projectsCollector )
{
this.projectsCollector = projectsCollector;
}
@Override
public List<MavenProject> collectProjects( MavenExecutionRequest request ) throws ProjectBuildingException
{
List<File> files = Collections.singletonList( request.getPom().getAbsoluteFile() );
List<MavenProject> projects = new ArrayList<>();
projectsCollector.collectProjects( projects, files, request );
return projects;
}
}

View File

@ -116,6 +116,11 @@ public abstract class AbstractCoreMavenComponentTestCase
.setPluginArtifactRepositories( getPluginArtifactRepositories() )
.setGoals( Arrays.asList( "package" ) );
if ( pom != null )
{
request.setMultiModuleProjectDirectory( pom.getParentFile() );
}
return request;
}

View File

@ -19,37 +19,45 @@ package org.apache.maven.graph;
* under the License.
*/
import com.google.common.collect.ImmutableMap;
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.Dependency;
import org.apache.maven.model.Parent;
import org.apache.maven.model.building.Result;
import org.apache.maven.model.locator.DefaultModelLocator;
import org.apache.maven.model.locator.ModelLocator;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.project.ProjectBuildingResult;
import org.apache.maven.project.collector.DefaultProjectsCollector;
import org.apache.maven.project.collector.MultiModuleCollectionStrategy;
import org.apache.maven.project.collector.PomlessCollectionStrategy;
import org.apache.maven.project.collector.ProjectsCollector;
import org.apache.maven.project.collector.RequestPomCollectionStrategy;
import org.codehaus.plexus.util.StringUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.function.Function.identity;
import static junit.framework.TestCase.assertEquals;
import static org.apache.maven.execution.MavenExecutionRequest.*;
import static org.apache.maven.execution.MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM;
import static org.apache.maven.execution.MavenExecutionRequest.REACTOR_MAKE_UPSTREAM;
import static org.apache.maven.graph.DefaultGraphBuilderTest.ScenarioBuilder.scenario;
import static org.mockito.ArgumentMatchers.*;
@ -59,22 +67,39 @@ import static org.mockito.Mockito.when;
@RunWith( Parameterized.class )
public class DefaultGraphBuilderTest
{
/*
The multi-module structure in this project is displayed as follows:
module-parent
module-independent (without parent declaration)
module-a
module-b (depends on module-a)
module-c
module-c-1
module-c-2 (depends on module-b)
*/
private static final String PARENT_MODULE = "module-parent";
private static final String INDEPENDENT_MODULE = "module-independent";
private static final String MODULE_A = "module-a";
private static final String MODULE_B = "module-b"; // depends on module-a
private static final String MODULE_C = "module-c"; // depends on module-b
private static final String MODULE_B = "module-b";
private static final String MODULE_C = "module-c";
private static final String MODULE_C_1 = "module-c-1";
private static final String MODULE_C_2 = "module-c-2";
@InjectMocks
private DefaultGraphBuilder graphBuilder;
@Mock
private ProjectBuilder projectBuilder;
private final ProjectBuilder projectBuilder = mock( ProjectBuilder.class );
private final MavenSession session = mock( MavenSession.class );
private final MavenExecutionRequest mavenExecutionRequest = mock( MavenExecutionRequest.class );
@Mock
private MavenSession session;
private final ProjectsCollector projectsCollector = new DefaultProjectsCollector( projectBuilder );
@Mock
private MavenExecutionRequest mavenExecutionRequest;
// Not using mocks for these strategies - a mock would just copy the actual implementation.
private final ModelLocator modelLocator = new DefaultModelLocator();
private final PomlessCollectionStrategy pomlessCollectionStrategy = new PomlessCollectionStrategy( projectBuilder );
private final MultiModuleCollectionStrategy multiModuleCollectionStrategy = new MultiModuleCollectionStrategy( modelLocator, projectsCollector );
private final RequestPomCollectionStrategy requestPomCollectionStrategy = new RequestPomCollectionStrategy( projectsCollector );
private Map<String, MavenProject> artifactIdProjectMap;
@ -85,73 +110,85 @@ public class DefaultGraphBuilderTest
private final String parameterResumeFrom;
private final String parameterMakeBehavior;
private final List<String> parameterExpectedResult;
private final File parameterRequestedPom;
@Parameters(name = "{index}. {0}")
public static Collection<Object[]> parameters()
{
return asList(
scenario( "Full reactor" )
.expectResult( asList( INDEPENDENT_MODULE, MODULE_A, MODULE_B, MODULE_C ) ),
scenario( "Full reactor in order" )
.expectResult( PARENT_MODULE, MODULE_C, MODULE_C_1, MODULE_A, MODULE_B, MODULE_C_2, INDEPENDENT_MODULE ),
scenario( "Selected project" )
.selectedProjects( singletonList( MODULE_B ) )
.expectResult( singletonList( MODULE_B ) ),
.selectedProjects( MODULE_B )
.expectResult( MODULE_B ),
scenario( "Excluded project" )
.excludedProjects( singletonList( MODULE_B ) )
.expectResult( asList( INDEPENDENT_MODULE, MODULE_A, MODULE_C ) ),
.excludedProjects( MODULE_B )
.expectResult( PARENT_MODULE, MODULE_C, MODULE_C_1, MODULE_A, MODULE_C_2, INDEPENDENT_MODULE ),
scenario( "Resuming from project" )
.resumeFrom( MODULE_B )
.expectResult( asList( MODULE_B, MODULE_C ) ),
.expectResult( MODULE_B, MODULE_C_2, INDEPENDENT_MODULE ),
scenario( "Selected project with also make dependencies" )
.selectedProjects( singletonList( MODULE_C ) )
.selectedProjects( MODULE_C_2 )
.makeBehavior( REACTOR_MAKE_UPSTREAM )
.expectResult( asList( MODULE_A, MODULE_B, MODULE_C ) ),
.expectResult( PARENT_MODULE, MODULE_C, MODULE_A, MODULE_B, MODULE_C_2 ),
scenario( "Selected project with also make dependents" )
.selectedProjects( singletonList( MODULE_B ) )
.selectedProjects( MODULE_B )
.makeBehavior( REACTOR_MAKE_DOWNSTREAM )
.expectResult( asList( MODULE_B, MODULE_C ) ),
.expectResult( MODULE_B, MODULE_C_2 ),
scenario( "Resuming from project with also make dependencies" )
.makeBehavior( REACTOR_MAKE_UPSTREAM )
.resumeFrom( MODULE_C )
.expectResult( asList( MODULE_A, MODULE_B, MODULE_C ) ),
scenario( "Selected project with resume from an also make dependency (MNG-4960 IT#1)" )
.selectedProjects( singletonList( MODULE_C ) )
.resumeFrom( MODULE_C_2 )
.expectResult( PARENT_MODULE, MODULE_C, MODULE_A, MODULE_B, MODULE_C_2, INDEPENDENT_MODULE ),
scenario( "Selected project with resume from and also make dependency (MNG-4960 IT#1)" )
.selectedProjects( MODULE_C_2 )
.resumeFrom( MODULE_B )
.makeBehavior( REACTOR_MAKE_UPSTREAM )
.expectResult( asList( MODULE_A, MODULE_B, MODULE_C ) ),
scenario( "Selected project with resume from an also make dependent (MNG-4960 IT#2)" )
.selectedProjects( singletonList( MODULE_B ) )
.resumeFrom( MODULE_C )
.expectResult( PARENT_MODULE, MODULE_C, MODULE_A, MODULE_B, MODULE_C_2 ),
scenario( "Selected project with resume from and also make dependent (MNG-4960 IT#2)" )
.selectedProjects( MODULE_B )
.resumeFrom( MODULE_C_2 )
.makeBehavior( REACTOR_MAKE_DOWNSTREAM )
.expectResult( singletonList( MODULE_C ) ),
.expectResult( MODULE_C_2 ),
scenario( "Excluding an also make dependency from selectedProject does take its transitive dependency" )
.selectedProjects( singletonList( MODULE_C ) )
.excludedProjects( singletonList( MODULE_B ) )
.selectedProjects( MODULE_C_2 )
.excludedProjects( MODULE_B )
.makeBehavior( REACTOR_MAKE_UPSTREAM )
.expectResult( asList( MODULE_A, MODULE_C ) ),
.expectResult( PARENT_MODULE, MODULE_C, MODULE_A, MODULE_C_2 ),
scenario( "Excluding an also make dependency from resumeFrom does take its transitive dependency" )
.resumeFrom( MODULE_C )
.excludedProjects( singletonList( MODULE_B ) )
.resumeFrom( MODULE_C_2 )
.excludedProjects( MODULE_B )
.makeBehavior( REACTOR_MAKE_UPSTREAM )
.expectResult( asList( MODULE_A, MODULE_C ) ),
.expectResult( PARENT_MODULE, MODULE_C, MODULE_A, MODULE_C_2, INDEPENDENT_MODULE ),
scenario( "Resume from exclude project downstream" )
.resumeFrom( MODULE_A )
.excludedProjects( singletonList( MODULE_B ) )
.expectResult( asList( MODULE_A, MODULE_C ) ),
.excludedProjects( MODULE_B )
.expectResult( MODULE_A, MODULE_C_2, INDEPENDENT_MODULE ),
scenario( "Exclude the project we are resuming from (as proposed in MNG-6676)" )
.resumeFrom( MODULE_B )
.excludedProjects( singletonList( MODULE_B ) )
.expectResult( singletonList( MODULE_C ) ),
.excludedProjects( MODULE_B )
.expectResult( MODULE_C_2, INDEPENDENT_MODULE ),
scenario( "Selected projects in wrong order are resumed correctly in order" )
.selectedProjects( asList( MODULE_C, MODULE_B, MODULE_A ) )
.selectedProjects( MODULE_C_2, MODULE_B, MODULE_A )
.resumeFrom( MODULE_B )
.expectResult( asList( MODULE_B, MODULE_C ) ),
.expectResult( MODULE_B, MODULE_C_2 ),
scenario( "Duplicate projects are filtered out" )
.selectedProjects( asList( MODULE_A, MODULE_A ) )
.expectResult( singletonList( MODULE_A ) )
.selectedProjects( MODULE_A, MODULE_A )
.expectResult( MODULE_A ),
scenario( "Select reactor by specific pom" )
.requestedPom( MODULE_C )
.expectResult( MODULE_C, MODULE_C_1, MODULE_C_2 ),
scenario( "Select reactor by specific pom with also make dependencies" )
.requestedPom( MODULE_C )
.makeBehavior( REACTOR_MAKE_UPSTREAM )
.expectResult( PARENT_MODULE, MODULE_C, MODULE_C_1, MODULE_A, MODULE_B, MODULE_C_2 ),
scenario( "Select reactor by specific pom with also make dependents" )
.requestedPom( MODULE_B )
.makeBehavior( REACTOR_MAKE_DOWNSTREAM )
.expectResult( MODULE_B, MODULE_C_2 )
);
}
public DefaultGraphBuilderTest( String description, List<String> selectedProjects, List<String> excludedProjects, String resumedFrom, String makeBehavior, List<String> expectedReactorProjects )
public DefaultGraphBuilderTest( String description, List<String> selectedProjects, List<String> excludedProjects, String resumedFrom, String makeBehavior, List<String> expectedReactorProjects, File requestedPom )
{
this.parameterDescription = description;
this.parameterSelectedProjects = selectedProjects;
@ -159,6 +196,7 @@ public class DefaultGraphBuilderTest
this.parameterResumeFrom = resumedFrom;
this.parameterMakeBehavior = makeBehavior;
this.parameterExpectedResult = expectedReactorProjects;
this.parameterRequestedPom = requestedPom;
}
@Test
@ -171,6 +209,7 @@ public class DefaultGraphBuilderTest
when( mavenExecutionRequest.getSelectedProjects() ).thenReturn( selectedProjects );
when( mavenExecutionRequest.getExcludedProjects() ).thenReturn( excludedProjects );
when( mavenExecutionRequest.getMakeBehavior() ).thenReturn( parameterMakeBehavior );
when( mavenExecutionRequest.getPom() ).thenReturn( parameterRequestedPom );
if ( StringUtils.isNotEmpty( parameterResumeFrom ) )
{
when( mavenExecutionRequest.getResumeFrom() ).thenReturn( ":" + parameterResumeFrom );
@ -190,40 +229,47 @@ public class DefaultGraphBuilderTest
@Before
public void before() throws Exception
{
MockitoAnnotations.initMocks( this );
graphBuilder = new DefaultGraphBuilder(
mock( BuildResumptionDataRepository.class ),
pomlessCollectionStrategy,
multiModuleCollectionStrategy,
requestPomCollectionStrategy
);
ProjectBuildingRequest projectBuildingRequest = mock( ProjectBuildingRequest.class );
ProjectBuildingResult projectBuildingResult1 = mock( ProjectBuildingResult.class );
ProjectBuildingResult projectBuildingResult2 = mock( ProjectBuildingResult.class );
ProjectBuildingResult projectBuildingResult3 = mock( ProjectBuildingResult.class );
ProjectBuildingResult projectBuildingResult4 = mock( ProjectBuildingResult.class );
MavenProject projectIndependentModule = getMavenProject( "independent-module" );
MavenProject projectModuleA = getMavenProject( "module-a" );
MavenProject projectModuleB = getMavenProject( "module-b" );
MavenProject projectModuleC = getMavenProject( "module-c" );
projectModuleB.setDependencies( singletonList( toDependency( projectModuleA) ) );
projectModuleC.setDependencies( singletonList( toDependency( projectModuleB) ) );
// Create projects
MavenProject projectParent = getMavenProject( PARENT_MODULE );
MavenProject projectIndependentModule = getMavenProject( INDEPENDENT_MODULE );
MavenProject projectModuleA = getMavenProject( MODULE_A, projectParent );
MavenProject projectModuleB = getMavenProject( MODULE_B, projectParent );
MavenProject projectModuleC = getMavenProject( MODULE_C, projectParent );
MavenProject projectModuleC1 = getMavenProject( MODULE_C_1, projectModuleC );
MavenProject projectModuleC2 = getMavenProject( MODULE_C_2, projectModuleC );
artifactIdProjectMap = Stream.of( projectParent, projectIndependentModule, projectModuleA, projectModuleB, projectModuleC, projectModuleC1, projectModuleC2 )
.collect( Collectors.toMap( MavenProject::getArtifactId, identity() ) );
// Set dependencies and modules
projectModuleB.setDependencies( singletonList( toDependency( projectModuleA ) ) );
projectModuleC2.setDependencies( singletonList( toDependency( projectModuleB ) ) );
projectParent.setCollectedProjects( asList( projectIndependentModule, projectModuleA, projectModuleB, projectModuleC, projectModuleC1, projectModuleC2 ) );
projectModuleC.setCollectedProjects( asList( projectModuleC1, projectModuleC2 ) );
// Set up needed mocks
when( session.getRequest() ).thenReturn( mavenExecutionRequest );
when( session.getProjects() ).thenReturn( null ); // needed, otherwise it will be an empty list by default
when( mavenExecutionRequest.getProjectBuildingRequest() ).thenReturn( mock( ProjectBuildingRequest.class ) );
List<ProjectBuildingResult> projectBuildingResults = createProjectBuildingResultMocks( artifactIdProjectMap.values() );
when( projectBuilder.build( anyList(), anyBoolean(), any( ProjectBuildingRequest.class ) ) ).thenReturn( projectBuildingResults );
}
when( mavenExecutionRequest.getProjectBuildingRequest() ).thenReturn( projectBuildingRequest );
when( mavenExecutionRequest.getPom() ).thenReturn( new File( "/tmp/unit-test" ) );
when( projectBuildingResult1.getProject() ).thenReturn( projectIndependentModule );
when( projectBuildingResult2.getProject() ).thenReturn( projectModuleA );
when( projectBuildingResult3.getProject() ).thenReturn( projectModuleB );
when( projectBuildingResult4.getProject() ).thenReturn( projectModuleC );
when( projectBuilder.build( anyList(), anyBoolean(), any( ProjectBuildingRequest.class ) ) )
.thenReturn( asList( projectBuildingResult1, projectBuildingResult2, projectBuildingResult3, projectBuildingResult4 ) );
artifactIdProjectMap = ImmutableMap.of(
INDEPENDENT_MODULE, projectIndependentModule,
MODULE_A, projectModuleA,
MODULE_B, projectModuleB,
MODULE_C, projectModuleC
);
private MavenProject getMavenProject( String artifactId, MavenProject parentProject )
{
MavenProject project = getMavenProject( artifactId );
Parent parent = new Parent();
parent.setGroupId( parentProject.getGroupId() );
parent.setArtifactId( parentProject.getArtifactId() );
project.getModel().setParent( parent );
return project;
}
private MavenProject getMavenProject( String artifactId )
@ -232,6 +278,8 @@ public class DefaultGraphBuilderTest
mavenProject.setGroupId( "unittest" );
mavenProject.setArtifactId( artifactId );
mavenProject.setVersion( "1.0" );
mavenProject.setPomFile( new File ( artifactId, "pom.xml" ) );
mavenProject.setCollectedProjects( new ArrayList<>() );
return mavenProject;
}
@ -244,6 +292,17 @@ public class DefaultGraphBuilderTest
return dependency;
}
private List<ProjectBuildingResult> createProjectBuildingResultMocks( Collection<MavenProject> projects )
{
return projects.stream()
.map( project -> {
ProjectBuildingResult result = mock( ProjectBuildingResult.class );
when( result.getProject() ).thenReturn( project );
return result;
} )
.collect( Collectors.toList() );
}
static class ScenarioBuilder
{
private String description;
@ -251,6 +310,7 @@ public class DefaultGraphBuilderTest
private List<String> excludedProjects = emptyList();
private String resumeFrom = "";
private String makeBehavior = "";
private File requestedPom = new File( PARENT_MODULE, "pom.xml" );
private ScenarioBuilder() { }
@ -261,15 +321,15 @@ public class DefaultGraphBuilderTest
return scenarioBuilder;
}
public ScenarioBuilder selectedProjects( List<String> selectedProjects )
public ScenarioBuilder selectedProjects( String... selectedProjects )
{
this.selectedProjects = selectedProjects;
this.selectedProjects = asList( selectedProjects );
return this;
}
public ScenarioBuilder excludedProjects( List<String> excludedProjects )
public ScenarioBuilder excludedProjects( String... excludedProjects )
{
this.excludedProjects = excludedProjects;
this.excludedProjects = asList( excludedProjects );
return this;
}
@ -285,10 +345,16 @@ public class DefaultGraphBuilderTest
return this;
}
public Object[] expectResult( List<String> expectedReactorProjects )
public ScenarioBuilder requestedPom( String requestedPom )
{
this.requestedPom = new File( requestedPom, "pom.xml" );
return this;
}
public Object[] expectResult( String... expectedReactorProjects )
{
return new Object[] {
description, selectedProjects, excludedProjects, resumeFrom, makeBehavior, expectedReactorProjects
description, selectedProjects, excludedProjects, resumeFrom, makeBehavior, asList( expectedReactorProjects ), requestedPom
};
}
}