diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
new file mode 100644
index 0000000000..407210823e
--- /dev/null
+++ b/.github/workflows/maven.yml
@@ -0,0 +1,125 @@
+# 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.
+
+name: Java CI
+
+on: [push, pull_request]
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest, macOS-latest]
+ fail-fast: false
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Set up cache for ~./m2/repository
+ uses: actions/cache@v1
+ with:
+ path: ~/.m2/repository
+ key: maven-${{ matrix.os }}-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ maven-${{ matrix.os }}-
+
+ - name: Set up JDK
+ uses: actions/setup-java@v1
+ with:
+ java-version: 8
+
+ - name: Build with Maven
+ run: mvn verify -e -B -V -DdistributionFileName=apache-maven
+
+ - name: Upload built Maven
+ uses: actions/upload-artifact@v2
+ if: ${{ matrix.os == 'ubuntu-latest' }}
+ with:
+ name: built-maven
+ path: apache-maven/target/
+
+ - name: Upload built Maven Wrapper
+ uses: actions/upload-artifact@v2
+ if: ${{ matrix.os == 'ubuntu-latest' }}
+ with:
+ name: built-maven-wrapper
+ path: maven-wrapper/target/maven-wrapper.jar
+
+ integration-test:
+ needs: build
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest, macOS-latest]
+ java: [8, 11, 14]
+ fail-fast: false
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Collect environment context variables
+ shell: bash
+ run: |
+ set +e
+ repo=maven-integration-testing
+ user=${GITHUB_REPOSITORY%/*}
+ branch=${GITHUB_REF#refs/heads/}
+ target_branch=master
+ target_user=apache
+ if [ $branch != "master" ]; then
+ git ls-remote https://github.com/$user/$repo.git | grep $GITHUB_REF > /dev/null
+ if [ $? -eq 0 ]; then
+ echo "Found a branch \"$branch\" in fork \"$user/$repo\", configuring this for the integration tests to be run against."
+ target_branch=$branch
+ target_user=$user
+ else
+ echo "Could not find fork \"$user/$repo\" or a branch \"$branch\" in this fork. Falling back to \"$target_branch\" in \"$target_user/$repo\"."
+ fi
+ else
+ echo "Integration tests will run against $target_user/$repo for master builds."
+ fi
+ echo "::set-env name=REPO_BRANCH::$target_branch"
+ echo "::set-env name=REPO_USER::$target_user"
+
+ - name: Checkout maven-integration-testing
+ uses: actions/checkout@v2
+ with:
+ repository: ${{ env.REPO_USER }}/maven-integration-testing
+ path: maven-integration-testing/
+ ref: ${{ env.REPO_BRANCH }}
+
+ - name: Download built Maven
+ uses: actions/download-artifact@v2
+ with:
+ name: built-maven
+ path: built-maven/
+
+ - name: Download built Maven Wrapper
+ uses: actions/download-artifact@v2
+ with:
+ name: built-maven-wrapper
+ path: built-maven-wrapper/
+
+ - name: Set up JDK
+ uses: actions/setup-java@v1
+ with:
+ java-version: ${{ matrix.java }}
+
+ - name: Running integration tests
+ shell: bash
+ run: mvn install -e -B -V -Prun-its,embedded -Dmaven.repo.local=$GITHUB_WORKSPACE/repo/ -DmavenDistro="$GITHUB_WORKSPACE/built-maven/apache-maven-bin.zip" -DwrapperDistroDir="$GITHUB_WORKSPACE/built-maven/" -DmavenWrapper="$GITHUB_WORKSPACE/built-maven-wrapper/maven-wrapper.jar" -f maven-integration-testing/pom.xml
\ No newline at end of file
diff --git a/Jenkinsfile b/Jenkinsfile
index 278a19df1f..5786cea460 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -21,10 +21,10 @@ properties([buildDiscarder(logRotator(artifactNumToKeepStr: '5', numToKeepStr: e
def buildOs = 'linux'
def buildJdk = '8'
-def buildMvn = '3.6.2'
+def buildMvn = '3.6.3'
def runITsOses = ['linux', 'windows']
def runITsJdks = ['8', '11', '14', '15']
-def runITsMvn = '3.6.2'
+def runITsMvn = '3.6.3'
def runITscommand = "mvn clean install -Prun-its,embedded -B -U -V" // -DmavenDistro=... -Dmaven.test.failure.ignore=true
def tests
diff --git a/maven-artifact/src/main/java/org/apache/maven/artifact/versioning/ComparableVersion.java b/maven-artifact/src/main/java/org/apache/maven/artifact/versioning/ComparableVersion.java
index 987be48b3e..9a436f0158 100644
--- a/maven-artifact/src/main/java/org/apache/maven/artifact/versioning/ComparableVersion.java
+++ b/maven-artifact/src/main/java/org/apache/maven/artifact/versioning/ComparableVersion.java
@@ -530,8 +530,16 @@ public class ComparableVersion
{
return 0; // 1-0 = 1- (normalize) = 1
}
- Item first = get( 0 );
- return first.compareTo( null );
+ // Compare the entire list of items with null - not just the first one, MNG-6964
+ for ( Item i : this )
+ {
+ int result = i.compareTo( null );
+ if ( result != 0 )
+ {
+ return result;
+ }
+ }
+ return 0;
}
switch ( item.getType() )
{
@@ -582,6 +590,32 @@ public class ComparableVersion
}
return buffer.toString();
}
+
+ /**
+ * Return the contents in the same format that is used when you call toString() on a List.
+ */
+ private String toListString()
+ {
+ StringBuilder buffer = new StringBuilder();
+ buffer.append( "[" );
+ for ( Item item : this )
+ {
+ if ( buffer.length() > 1 )
+ {
+ buffer.append( ", " );
+ }
+ if ( item instanceof ListItem )
+ {
+ buffer.append( ( (ListItem ) item ).toListString() );
+ }
+ else
+ {
+ buffer.append( item );
+ }
+ }
+ buffer.append( "]" );
+ return buffer.toString();
+ }
}
public ComparableVersion( String version )
@@ -768,7 +802,8 @@ public class ComparableVersion
// CHECKSTYLE_ON: LineLength
public static void main( String... args )
{
- System.out.println( "Display parameters as parsed by Maven (in canonical form) and comparison result:" );
+ System.out.println( "Display parameters as parsed by Maven (in canonical form and as a list of tokens) and"
+ + " comparison result:" );
if ( args.length == 0 )
{
return;
@@ -787,7 +822,7 @@ public class ComparableVersion
+ ( ( compare == 0 ) ? "==" : ( ( compare < 0 ) ? "<" : ">" ) ) + ' ' + version );
}
- System.out.println( ( i++ ) + ". " + version + " == " + c.getCanonical() );
+ System.out.println( ( i++ ) + ". " + version + " -> " + c.getCanonical() + " " + c.items.toListString() );
prev = c;
}
diff --git a/maven-artifact/src/test/java/org/apache/maven/artifact/versioning/ComparableVersionTest.java b/maven-artifact/src/test/java/org/apache/maven/artifact/versioning/ComparableVersionTest.java
index 70fc1d8ecc..97fb46d55f 100644
--- a/maven-artifact/src/test/java/org/apache/maven/artifact/versioning/ComparableVersionTest.java
+++ b/maven-artifact/src/test/java/org/apache/maven/artifact/versioning/ComparableVersionTest.java
@@ -295,6 +295,21 @@ public class ComparableVersionTest
checkVersionsArrayEqual( arr );
}
+ /**
+ * Test MNG-6964 edge cases
+ * for qualifiers that start with "-0.", which was showing A == C and B == C but A < B.
+ */
+ public void testMng6964()
+ {
+ String a = "1-0.alpha";
+ String b = "1-0.beta";
+ String c = "1";
+
+ checkVersionsOrder( a, c ); // Now a < c, but before MNG-6964 they were equal
+ checkVersionsOrder( b, c ); // Now b < c, but before MNG-6964 they were equal
+ checkVersionsOrder( a, b ); // Should still be true
+ }
+
public void testLocaleIndependent()
{
Locale orig = Locale.getDefault();
diff --git a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionData.java b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionData.java
index 8889b8312a..b0e91db13c 100644
--- a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionData.java
+++ b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionData.java
@@ -20,6 +20,9 @@ package org.apache.maven.execution;
*/
import java.util.List;
+import java.util.Optional;
+
+import static java.util.Collections.emptyList;
/**
* This class holds the information required to enable resuming a Maven build with {@code --resume}.
@@ -32,7 +35,7 @@ public class BuildResumptionData
private final String resumeFrom;
/**
- * List of projects to skip if the build would be resumed from {@link #resumeFrom}.
+ * List of projects to skip.
*/
private final List projectsToSkip;
@@ -42,13 +45,23 @@ public class BuildResumptionData
this.projectsToSkip = projectsToSkip;
}
- public String getResumeFrom()
+ /**
+ * Returns the project where the next build can resume from.
+ * This is usually the first failed project in the order of the reactor.
+ * @return An optional containing the group and artifact id of the project. It does not make sense to resume
+ * the build when the first project of the reactor has failed, so then it will return an empty optional.
+ */
+ public Optional getResumeFrom()
{
- return this.resumeFrom;
+ return Optional.ofNullable( this.resumeFrom );
}
+ /**
+ * A list of projects which can be skipped in the next build.
+ * @return A list of group and artifact ids. Can be empty when no projects can be skipped.
+ */
public List getProjectsToSkip()
{
- return this.projectsToSkip;
+ return ( projectsToSkip != null ) ? projectsToSkip : emptyList();
}
}
diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionAnalyzer.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionAnalyzer.java
index 3d100dc891..ac7c1ff15a 100644
--- a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionAnalyzer.java
+++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionAnalyzer.java
@@ -22,6 +22,7 @@ package org.apache.maven.execution;
import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.model.Dependency;
import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -56,16 +57,31 @@ public class DefaultBuildResumptionAnalyzer implements BuildResumptionAnalyzer
final MavenProject resumeFromProject = failedProjects.get( 0 );
+ final String resumeFromSelector;
+ final List projectsToSkip;
if ( isFailedProjectFirstInBuild( result, resumeFromProject ) )
{
- LOGGER.info( "The first module in the build failed, resuming the build would not make sense." );
- return Optional.empty();
+ // As the first module in the build failed, there is no need to specify this as the resumeFrom project.
+ resumeFromSelector = null;
+ projectsToSkip = determineProjectsToSkip( result, failedProjects, 0 );
+ }
+ else
+ {
+ resumeFromSelector = resumeFromProject.getGroupId() + ":" + resumeFromProject.getArtifactId();
+ List allProjects = result.getTopologicallySortedProjects();
+ int resumeFromProjectIndex = allProjects.indexOf( resumeFromProject );
+ projectsToSkip = determineProjectsToSkip( result, failedProjects, resumeFromProjectIndex + 1 );
}
- final String resumeFromSelector = resumeFromProject.getGroupId() + ":" + resumeFromProject.getArtifactId();
- final List projectsToSkip = determineProjectsToSkip( result, failedProjects, resumeFromProject );
-
- return Optional.of( new BuildResumptionData( resumeFromSelector, projectsToSkip ) );
+ boolean canBuildBeResumed = StringUtils.isNotEmpty( resumeFromSelector ) || !projectsToSkip.isEmpty();
+ if ( canBuildBeResumed )
+ {
+ return Optional.of( new BuildResumptionData( resumeFromSelector, projectsToSkip ) );
+ }
+ else
+ {
+ return Optional.empty();
+ }
}
private boolean isFailedProjectFirstInBuild( final MavenExecutionResult result, final MavenProject failedProject )
@@ -82,6 +98,7 @@ public class DefaultBuildResumptionAnalyzer implements BuildResumptionAnalyzer
.filter( LifecycleExecutionException.class::isInstance )
.map( LifecycleExecutionException.class::cast )
.map( LifecycleExecutionException::getProject )
+ .filter( Objects::nonNull )
.sorted( comparing( sortedProjects::indexOf ) )
.collect( Collectors.toList() );
}
@@ -92,16 +109,15 @@ public class DefaultBuildResumptionAnalyzer implements BuildResumptionAnalyzer
* This is not the case these projects are dependent on one of the failed projects.
* @param result The result of the Maven build.
* @param failedProjects The list of failed projects in the build.
- * @param resumeFromProject The project where the build will be resumed with in the next run.
+ * @param startFromProjectIndex Start looking for projects which can be skipped from a certain index.
* @return A list of projects which can be skipped in a later build.
*/
private List determineProjectsToSkip( MavenExecutionResult result,
List failedProjects,
- MavenProject resumeFromProject )
+ int startFromProjectIndex )
{
List allProjects = result.getTopologicallySortedProjects();
- int resumeFromProjectIndex = allProjects.indexOf( resumeFromProject );
- List remainingProjects = allProjects.subList( resumeFromProjectIndex + 1, allProjects.size() );
+ List remainingProjects = allProjects.subList( startFromProjectIndex, allProjects.size() );
List failedProjectsGAList = failedProjects.stream()
.map( GroupArtifactPair::new )
diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionDataRepository.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionDataRepository.java
index eea097f993..28862375ed 100644
--- a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionDataRepository.java
+++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionDataRepository.java
@@ -74,9 +74,15 @@ public class DefaultBuildResumptionDataRepository implements BuildResumptionData
private Properties convertToProperties( final BuildResumptionData buildResumptionData )
{
Properties properties = new Properties();
- properties.setProperty( RESUME_FROM_PROPERTY, buildResumptionData.getResumeFrom() );
- String excludedProjects = String.join( PROPERTY_DELIMITER, buildResumptionData.getProjectsToSkip() );
- properties.setProperty( EXCLUDED_PROJECTS_PROPERTY, excludedProjects );
+
+ buildResumptionData.getResumeFrom()
+ .ifPresent( resumeFrom -> properties.setProperty( RESUME_FROM_PROPERTY, resumeFrom ) );
+
+ if ( !buildResumptionData.getProjectsToSkip().isEmpty() )
+ {
+ String excludedProjects = String.join( PROPERTY_DELIMITER, buildResumptionData.getProjectsToSkip() );
+ properties.setProperty( EXCLUDED_PROJECTS_PROPERTY, excludedProjects );
+ }
return properties;
}
@@ -105,7 +111,7 @@ public class DefaultBuildResumptionDataRepository implements BuildResumptionData
private Properties loadResumptionFile( Path rootBuildDirectory )
{
Properties properties = new Properties();
- Path path = Paths.get( RESUME_PROPERTIES_FILENAME ).resolve( rootBuildDirectory );
+ Path path = rootBuildDirectory.resolve( RESUME_PROPERTIES_FILENAME );
if ( !Files.exists( path ) )
{
LOGGER.warn( "The {} file does not exist. The --resume / -r feature will not work.", path );
@@ -137,9 +143,12 @@ public class DefaultBuildResumptionDataRepository implements BuildResumptionData
if ( properties.containsKey( EXCLUDED_PROJECTS_PROPERTY ) )
{
String propertyValue = properties.getProperty( EXCLUDED_PROJECTS_PROPERTY );
- String[] excludedProjects = propertyValue.split( PROPERTY_DELIMITER );
- request.getExcludedProjects().addAll( Arrays.asList( excludedProjects ) );
- LOGGER.info( "Additionally excluding projects '{}' due to the --resume / -r feature.", propertyValue );
+ if ( !StringUtils.isEmpty( propertyValue ) )
+ {
+ String[] excludedProjects = propertyValue.split( PROPERTY_DELIMITER );
+ request.getExcludedProjects().addAll( Arrays.asList( excludedProjects ) );
+ LOGGER.info( "Additionally excluding projects '{}' due to the --resume / -r feature.", propertyValue );
+ }
}
}
}
diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java
index 9cbdebeddf..fd00df9872 100644
--- a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java
+++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java
@@ -607,9 +607,9 @@ public class DefaultMavenExecutionRequest
}
@Override
- public MavenExecutionRequest setResume()
+ public MavenExecutionRequest setResume( boolean resume )
{
- resume = true;
+ this.resume = resume;
return this;
}
diff --git a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java
index 542c34aade..1ccd7ecb23 100644
--- a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java
+++ b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java
@@ -173,9 +173,10 @@ public interface MavenExecutionRequest
/**
* Sets whether the build should be resumed from the data in the resume.properties file.
+ * @param resume Whether or not to resume a previous build.
* @return This request, never {@code null}.
*/
- MavenExecutionRequest setResume();
+ MavenExecutionRequest setResume( boolean resume );
/**
* @return Whether the build should be resumed from the data in the resume.properties file.
diff --git a/maven-core/src/main/resources/META-INF/maven/extension.xml b/maven-core/src/main/resources/META-INF/maven/extension.xml
index 6bd8369273..67cedc0f49 100644
--- a/maven-core/src/main/resources/META-INF/maven/extension.xml
+++ b/maven-core/src/main/resources/META-INF/maven/extension.xml
@@ -30,6 +30,7 @@ under the License.
org.apache.maven.exception
org.apache.maven.execution
org.apache.maven.execution.scope
+ org.apache.maven.graph
org.apache.maven.lifecycle
org.apache.maven.model
org.apache.maven.monitor
diff --git a/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumptionAnalyzerTest.java b/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumptionAnalyzerTest.java
index 59a8e63c14..6e037763e2 100644
--- a/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumptionAnalyzerTest.java
+++ b/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumptionAnalyzerTest.java
@@ -55,7 +55,7 @@ public class DefaultBuildResumptionAnalyzerTest
Optional result = analyzer.determineBuildResumptionData( executionResult );
assertThat( result.isPresent(), is( true ) );
- assertThat( result.get().getResumeFrom(), is( "test:B" ) );
+ assertThat( result.get().getResumeFrom(), is( Optional.of ( "test:B" ) ) );
}
@Test
@@ -111,7 +111,7 @@ public class DefaultBuildResumptionAnalyzerTest
Optional result = analyzer.determineBuildResumptionData( executionResult );
assertThat( result.isPresent(), is( true ) );
- assertThat( result.get().getResumeFrom(), is( "test:B" ) );
+ assertThat( result.get().getResumeFrom(), is( Optional.of ( "test:B" ) ) );
assertThat( result.get().getProjectsToSkip(), contains( "test:C" ) );
assertThat( result.get().getProjectsToSkip(), not( contains( "test:D" ) ) );
}
diff --git a/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumptionDataRepositoryTest.java b/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumptionDataRepositoryTest.java
index 415b94698a..697c74f9a0 100644
--- a/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumptionDataRepositoryTest.java
+++ b/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumptionDataRepositoryTest.java
@@ -19,6 +19,8 @@ package org.apache.maven.execution;
* under the License.
*/
+import org.apache.maven.model.Build;
+import org.apache.maven.project.MavenProject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@@ -29,6 +31,7 @@ import java.util.Properties;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
@RunWith( MockitoJUnitRunner.class )
@@ -75,4 +78,31 @@ public class DefaultBuildResumptionDataRepositoryTest
assertThat( request.getExcludedProjects(), contains( ":module-a", ":module-b", ":module-c" ) );
}
+
+ @Test
+ public void excludedProjectsAreNotAddedWhenPropertyValueIsEmpty()
+ {
+ MavenExecutionRequest request = new DefaultMavenExecutionRequest();
+ Properties properties = new Properties();
+ properties.setProperty( "excludedProjects", "" );
+
+ repository.applyResumptionProperties( request, properties );
+
+ assertThat( request.getExcludedProjects(), is( empty() ) );
+ }
+
+ @Test
+ public void applyResumptionData_shouldLoadData()
+ {
+ MavenExecutionRequest request = new DefaultMavenExecutionRequest();
+ Build build = new Build();
+ build.setDirectory( "src/test/resources/org/apache/maven/execution/" );
+ MavenProject rootProject = new MavenProject();
+ rootProject.setBuild( build );
+
+ repository.applyResumptionData( request, rootProject );
+
+ assertThat( request.getResumeFrom(), is( "example:module-c" ) );
+ assertThat( request.getExcludedProjects(), contains( "example:module-a", "example:module-b" ) );
+ }
}
diff --git a/maven-core/src/test/resources/org/apache/maven/execution/resume.properties b/maven-core/src/test/resources/org/apache/maven/execution/resume.properties
new file mode 100644
index 0000000000..26caeb53fc
--- /dev/null
+++ b/maven-core/src/test/resources/org/apache/maven/execution/resume.properties
@@ -0,0 +1,2 @@
+resumeFrom=example:module-c
+excludedProjects=example:module-a, example:module-b
\ No newline at end of file
diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
index 4136a1ea5f..b0d649081e 100644
--- a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
+++ b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
@@ -48,7 +48,6 @@ import org.apache.maven.eventspy.internal.EventSpyDispatcher;
import org.apache.maven.exception.DefaultExceptionHandler;
import org.apache.maven.exception.ExceptionHandler;
import org.apache.maven.exception.ExceptionSummary;
-import org.apache.maven.execution.BuildResumptionDataRepository;
import org.apache.maven.execution.DefaultMavenExecutionRequest;
import org.apache.maven.execution.ExecutionListener;
import org.apache.maven.execution.MavenExecutionRequest;
@@ -116,6 +115,7 @@ import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import static java.util.Comparator.comparing;
import static org.apache.maven.cli.ResolveFile.resolveFile;
import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
@@ -169,8 +169,6 @@ public class MavenCli
private Map configurationProcessors;
- private BuildResumptionDataRepository buildResumptionDataRepository;
-
public MavenCli()
{
this( null );
@@ -708,8 +706,6 @@ public class MavenCli
dispatcher = (DefaultSecDispatcher) container.lookup( SecDispatcher.class, "maven" );
- buildResumptionDataRepository = container.lookup( BuildResumptionDataRepository.class );
-
return container;
}
@@ -991,7 +987,7 @@ public class MavenCli
Map references = new LinkedHashMap<>();
- MavenProject project = null;
+ List failedProjects = new ArrayList<>();
for ( Throwable exception : result.getExceptions() )
{
@@ -999,10 +995,9 @@ public class MavenCli
logSummary( summary, references, "", cliRequest.showErrors );
- if ( project == null && exception instanceof LifecycleExecutionException )
+ if ( exception instanceof LifecycleExecutionException )
{
- LifecycleExecutionException lifecycleExecutionException = (LifecycleExecutionException) exception;
- project = lifecycleExecutionException.getProject();
+ failedProjects.add ( ( (LifecycleExecutionException) exception ).getProject() );
}
}
@@ -1031,15 +1026,23 @@ public class MavenCli
}
}
- List sortedProjects = result.getTopologicallySortedProjects();
if ( result.canResume() )
{
- logBuildResumeHint( "mvn -r " );
+ logBuildResumeHint( "mvn -r" );
}
- else if ( project != null && !project.equals( sortedProjects.get( 0 ) ) )
+ else if ( !failedProjects.isEmpty() )
{
- String resumeFromSelector = getResumeFromSelector( sortedProjects, project );
- logBuildResumeHint( "mvn -rf " + resumeFromSelector );
+ List sortedProjects = result.getTopologicallySortedProjects();
+
+ // Sort the failedProjects list in the topologically sorted order.
+ failedProjects.sort( comparing( sortedProjects::indexOf ) );
+
+ MavenProject firstFailedProject = failedProjects.get( 0 );
+ if ( !firstFailedProject.equals( sortedProjects.get( 0 ) ) )
+ {
+ String resumeFromSelector = getResumeFromSelector( sortedProjects, firstFailedProject );
+ logBuildResumeHint( "mvn -rf " + resumeFromSelector );
+ }
}
if ( MavenExecutionRequest.REACTOR_FAIL_NEVER.equals( cliRequest.request.getReactorFailureBehavior() ) )
@@ -1079,22 +1082,22 @@ public class MavenCli
* This method is made package-private for testing purposes.
*
* @param mavenProjects Maven projects which are part of build execution.
- * @param failedProject Project which has failed.
+ * @param firstFailedProject The first project which has failed.
* @return Value for -rf flag to resume build exactly from place where it failed ({@code :artifactId} in general
* and {@code groupId:artifactId} when there is a name clash).
*/
- String getResumeFromSelector( List mavenProjects, MavenProject failedProject )
+ String getResumeFromSelector( List mavenProjects, MavenProject firstFailedProject )
{
boolean hasOverlappingArtifactId = mavenProjects.stream()
- .filter( project -> failedProject.getArtifactId().equals( project.getArtifactId() ) )
+ .filter( project -> firstFailedProject.getArtifactId().equals( project.getArtifactId() ) )
.count() > 1;
if ( hasOverlappingArtifactId )
{
- return failedProject.getGroupId() + ":" + failedProject.getArtifactId();
+ return firstFailedProject.getGroupId() + ":" + firstFailedProject.getArtifactId();
}
- return ":" + failedProject.getArtifactId();
+ return ":" + firstFailedProject.getArtifactId();
}
private void logSummary( ExceptionSummary summary, Map references, String indent,
@@ -1536,7 +1539,7 @@ public class MavenCli
if ( commandLine.hasOption( CLIManager.RESUME ) )
{
- request.setResume();
+ request.setResume( true );
}
if ( commandLine.hasOption( CLIManager.RESUME_FROM ) )