[MNG-6863] --also-make is being ignored when calling --resume-from

[MNG-6676] Resume reactor build after skipped project using -pl !X -rf X combination

Co-authored-by: Martin Kanters <Martin.Kanters@infosupport.com>
This commit is contained in:
Martin Kanters 2020-04-24 12:01:27 +02:00 committed by rfscholte
parent 26b35a8ca9
commit 4bdbc9bd80
2 changed files with 369 additions and 60 deletions

View File

@ -125,8 +125,8 @@ public class DefaultGraphBuilder
ProjectDependencyGraph projectDependencyGraph = new DefaultProjectDependencyGraph( projects ); ProjectDependencyGraph projectDependencyGraph = new DefaultProjectDependencyGraph( projects );
List<MavenProject> activeProjects = projectDependencyGraph.getSortedProjects(); List<MavenProject> activeProjects = projectDependencyGraph.getSortedProjects();
activeProjects = trimSelectedProjects( activeProjects, projectDependencyGraph, session.getRequest() ); activeProjects = trimSelectedProjects( activeProjects, projectDependencyGraph, session.getRequest() );
activeProjects = trimResumedProjects( activeProjects, projectDependencyGraph, session.getRequest() );
activeProjects = trimExcludedProjects( activeProjects, session.getRequest() ); activeProjects = trimExcludedProjects( activeProjects, session.getRequest() );
activeProjects = trimResumedProjects( activeProjects, session.getRequest() );
if ( activeProjects.size() != projectDependencyGraph.getSortedProjects().size() ) if ( activeProjects.size() != projectDependencyGraph.getSortedProjects().size() )
{ {
@ -144,6 +144,8 @@ public class DefaultGraphBuilder
if ( !request.getSelectedProjects().isEmpty() ) if ( !request.getSelectedProjects().isEmpty() )
{ {
result = new ArrayList<>( projects.size() );
File reactorDirectory = null; File reactorDirectory = null;
if ( request.getBaseDirectory() != null ) if ( request.getBaseDirectory() != null )
{ {
@ -176,52 +178,54 @@ public class DefaultGraphBuilder
} }
} }
boolean makeUpstream = false; result.addAll( selectedProjects );
boolean makeDownstream = false;
if ( MavenExecutionRequest.REACTOR_MAKE_UPSTREAM.equals( request.getMakeBehavior() ) ) result = includeAlsoMakeTransitively( result, request, graph );
{
makeUpstream = true;
}
else if ( MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM.equals( request.getMakeBehavior() ) )
{
makeDownstream = true;
}
else if ( MavenExecutionRequest.REACTOR_MAKE_BOTH.equals( request.getMakeBehavior() ) )
{
makeUpstream = true;
makeDownstream = true;
}
else if ( StringUtils.isNotEmpty( request.getMakeBehavior() ) )
{
throw new MavenExecutionException( "Invalid reactor make behavior: " + request.getMakeBehavior(),
request.getPom() );
} }
if ( makeUpstream || makeDownstream ) return result;
{
for ( MavenProject selectedProject : new ArrayList<>( selectedProjects ) )
{
if ( makeUpstream )
{
selectedProjects.addAll( graph.getUpstreamProjects( selectedProject, true ) );
}
if ( makeDownstream )
{
selectedProjects.addAll( graph.getDownstreamProjects( selectedProject, true ) );
}
}
} }
result = new ArrayList<>( selectedProjects.size() ); private List<MavenProject> trimResumedProjects( List<MavenProject> projects, ProjectDependencyGraph graph,
MavenExecutionRequest request )
throws MavenExecutionException
{
List<MavenProject> result = projects;
if ( StringUtils.isNotEmpty( request.getResumeFrom() ) )
{
File reactorDirectory = null;
if ( request.getBaseDirectory() != null )
{
reactorDirectory = new File( request.getBaseDirectory() );
}
String selector = request.getResumeFrom();
result = new ArrayList<>( projects.size() );
boolean resumed = false;
for ( MavenProject project : projects ) for ( MavenProject project : projects )
{ {
if ( selectedProjects.contains( project ) ) if ( !resumed && isMatchingProject( project, selector, reactorDirectory ) )
{
resumed = true;
}
if ( resumed )
{ {
result.add( project ); result.add( project );
} }
} }
if ( !resumed )
{
throw new MavenExecutionException( "Could not find project to resume reactor build from: " + selector
+ " vs " + formatProjects( projects ), request.getPom() );
}
result = includeAlsoMakeTransitively( result, request, graph );
} }
return result; return result;
@ -280,45 +284,60 @@ public class DefaultGraphBuilder
return result; return result;
} }
private List<MavenProject> trimResumedProjects( List<MavenProject> projects, MavenExecutionRequest request ) private List<MavenProject> includeAlsoMakeTransitively( List<MavenProject> projects, MavenExecutionRequest request,
ProjectDependencyGraph graph )
throws MavenExecutionException throws MavenExecutionException
{ {
List<MavenProject> result = projects; List<MavenProject> result;
if ( StringUtils.isNotEmpty( request.getResumeFrom() ) ) boolean makeUpstream = false;
boolean makeDownstream = false;
if ( MavenExecutionRequest.REACTOR_MAKE_UPSTREAM.equals( request.getMakeBehavior() ) )
{ {
File reactorDirectory = null; makeUpstream = true;
if ( request.getBaseDirectory() != null ) }
else if ( MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM.equals( request.getMakeBehavior() ) )
{ {
reactorDirectory = new File( request.getBaseDirectory() ); makeDownstream = true;
}
else if ( MavenExecutionRequest.REACTOR_MAKE_BOTH.equals( request.getMakeBehavior() ) )
{
makeUpstream = true;
makeDownstream = true;
}
else if ( StringUtils.isNotEmpty( request.getMakeBehavior() ) )
{
throw new MavenExecutionException( "Invalid reactor make behavior: " + request.getMakeBehavior(),
request.getPom() );
} }
String selector = request.getResumeFrom(); if ( makeUpstream || makeDownstream )
{
for ( MavenProject project : new ArrayList<>( projects ) )
{
if ( makeUpstream )
{
projects.addAll( graph.getUpstreamProjects( project, true ) );
}
if ( makeDownstream )
{
projects.addAll( graph.getDownstreamProjects( project, true ) );
}
}
}
result = new ArrayList<>( projects.size() ); result = new ArrayList<>( projects.size() );
boolean resumed = false; for ( MavenProject project : graph.getSortedProjects() )
for ( MavenProject project : projects )
{ {
if ( !resumed && isMatchingProject( project, selector, reactorDirectory ) ) if ( projects.contains( project ) )
{
resumed = true;
}
if ( resumed )
{ {
result.add( project ); result.add( project );
} }
} }
if ( !resumed )
{
throw new MavenExecutionException( "Could not find project to resume reactor build from: " + selector
+ " vs " + formatProjects( projects ), request.getPom() );
}
}
return result; return result;
} }

View File

@ -0,0 +1,290 @@
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 com.google.common.collect.ImmutableMap;
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.building.Result;
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.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.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static junit.framework.TestCase.assertEquals;
import static org.apache.maven.execution.MavenExecutionRequest.*;
import static org.apache.maven.execution.MavenExecutionRequest.REACTOR_MAKE_UPSTREAM;
import static org.apache.maven.graph.DefaultGraphBuilderTest.ScenarioBuilder.scenario;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith( Parameterized.class )
public class DefaultGraphBuilderTest
{
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
@InjectMocks
private DefaultGraphBuilder graphBuilder;
@Mock
private ProjectBuilder projectBuilder;
@Mock
private MavenSession session;
@Mock
private MavenExecutionRequest mavenExecutionRequest;
private Map<String, MavenProject> artifactIdProjectMap;
// Parameters for the test
private final String parameterDescription;
private final List<String> parameterSelectedProjects;
private final List<String> parameterExcludedProjects;
private final String parameterResumeFrom;
private final String parameterMakeBehavior;
private final List<String> parameterExpectedResult;
@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( "Selected project" )
.selectedProjects( singletonList( MODULE_B ) )
.expectResult( singletonList( MODULE_B ) ),
scenario( "Excluded project" )
.excludedProjects( singletonList( MODULE_B ) )
.expectResult( asList( INDEPENDENT_MODULE, MODULE_A, MODULE_C ) ),
scenario( "Resuming from project" )
.resumeFrom( MODULE_B )
.expectResult( asList( MODULE_B, MODULE_C ) ),
scenario( "Selected project with also make dependencies" )
.selectedProjects( singletonList( MODULE_C ) )
.makeBehavior( REACTOR_MAKE_UPSTREAM )
.expectResult( asList( MODULE_A, MODULE_B, MODULE_C ) ),
scenario( "Selected project with also make dependents" )
.selectedProjects( singletonList( MODULE_B ) )
.makeBehavior( REACTOR_MAKE_DOWNSTREAM )
.expectResult( asList( MODULE_B, MODULE_C ) ),
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_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 )
.makeBehavior( REACTOR_MAKE_DOWNSTREAM )
.expectResult( singletonList( MODULE_C ) ),
scenario( "Excluding an also make dependency from selectedProject does take its transitive dependency" )
.selectedProjects( singletonList( MODULE_C ) )
.excludedProjects( singletonList( MODULE_B ) )
.makeBehavior( REACTOR_MAKE_UPSTREAM )
.expectResult( asList( MODULE_A, MODULE_C ) ),
scenario( "Excluding an also make dependency from resumeFrom does take its transitive dependency" )
.resumeFrom( MODULE_C )
.excludedProjects( singletonList( MODULE_B ) )
.makeBehavior( REACTOR_MAKE_UPSTREAM )
.expectResult( asList( MODULE_A, MODULE_C ) ),
scenario( "Resume from exclude project downstream" )
.resumeFrom( MODULE_A )
.excludedProjects( singletonList( MODULE_B ) )
.expectResult( asList( MODULE_A, MODULE_C ) ),
scenario( "Exclude the project we are resuming from (as proposed in MNG-6676)" )
.resumeFrom( MODULE_B )
.excludedProjects( singletonList( MODULE_B ) )
.expectResult( singletonList( MODULE_C ) )
);
}
public DefaultGraphBuilderTest( String description, List<String> selectedProjects, List<String> excludedProjects, String resumedFrom, String makeBehavior, List<String> expectedReactorProjects )
{
this.parameterDescription = description;
this.parameterSelectedProjects = selectedProjects;
this.parameterExcludedProjects = excludedProjects;
this.parameterResumeFrom = resumedFrom;
this.parameterMakeBehavior = makeBehavior;
this.parameterExpectedResult = expectedReactorProjects;
}
@Test
public void testGetReactorProjects()
{
// Given
List<String> selectedProjects = parameterSelectedProjects.stream().map( p -> ":" + p ).collect( Collectors.toList() );
List<String> excludedProjects = parameterExcludedProjects.stream().map( p -> ":" + p ).collect( Collectors.toList() );
when( mavenExecutionRequest.getSelectedProjects() ).thenReturn( selectedProjects );
when( mavenExecutionRequest.getExcludedProjects() ).thenReturn( excludedProjects );
when( mavenExecutionRequest.getMakeBehavior() ).thenReturn( parameterMakeBehavior );
if ( StringUtils.isNotEmpty( parameterResumeFrom ) )
{
when( mavenExecutionRequest.getResumeFrom() ).thenReturn( ":" + parameterResumeFrom );
}
// When
Result<ProjectDependencyGraph> result = graphBuilder.build( session );
// Then
List<MavenProject> actualReactorProjects = result.get().getSortedProjects();
List<MavenProject> expectedReactorProjects = parameterExpectedResult.stream()
.map( artifactIdProjectMap::get )
.collect( Collectors.toList());
assertEquals( parameterDescription, expectedReactorProjects, actualReactorProjects );
}
@Before
public void before() throws Exception
{
MockitoAnnotations.initMocks( this );
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) ) );
when( session.getRequest() ).thenReturn( mavenExecutionRequest );
when( session.getProjects() ).thenReturn( null ); // needed, otherwise it will be an empty list by default
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 mavenProject = new MavenProject();
mavenProject.setGroupId( "unittest" );
mavenProject.setArtifactId( artifactId );
mavenProject.setVersion( "1.0" );
return mavenProject;
}
private Dependency toDependency( MavenProject mavenProject )
{
Dependency dependency = new Dependency();
dependency.setGroupId( mavenProject.getGroupId() );
dependency.setArtifactId( mavenProject.getArtifactId() );
dependency.setVersion( mavenProject.getVersion() );
return dependency;
}
static class ScenarioBuilder
{
private String description;
private List<String> selectedProjects = emptyList();
private List<String> excludedProjects = emptyList();
private String resumeFrom = "";
private String makeBehavior = "";
private ScenarioBuilder() { }
public static ScenarioBuilder scenario( String description )
{
ScenarioBuilder scenarioBuilder = new ScenarioBuilder();
scenarioBuilder.description = description;
return scenarioBuilder;
}
public ScenarioBuilder selectedProjects( List<String> selectedProjects )
{
this.selectedProjects = selectedProjects;
return this;
}
public ScenarioBuilder excludedProjects( List<String> excludedProjects )
{
this.excludedProjects = excludedProjects;
return this;
}
public ScenarioBuilder resumeFrom( String resumeFrom )
{
this.resumeFrom = resumeFrom;
return this;
}
public ScenarioBuilder makeBehavior( String makeBehavior )
{
this.makeBehavior = makeBehavior;
return this;
}
public Object[] expectResult( List<String> expectedReactorProjects )
{
return new Object[] {
description, selectedProjects, excludedProjects, resumeFrom, makeBehavior, expectedReactorProjects
};
}
}
}