From 07b60e49321508ef9982d97622202678491a2e8c Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Mon, 4 May 2020 15:39:41 +0200 Subject: [PATCH 01/31] [MNG-5760] When a build fails, write file with properties to resume the next build from. --- .../maven/cli/BuildResumptionManager.java | 238 ++++++++++++++++++ .../java/org/apache/maven/cli/MavenCli.java | 22 +- .../maven/cli/BuildResumptionManagerTest.java | 126 ++++++++++ 3 files changed, 381 insertions(+), 5 deletions(-) create mode 100644 maven-embedder/src/main/java/org/apache/maven/cli/BuildResumptionManager.java create mode 100644 maven-embedder/src/test/java/org/apache/maven/cli/BuildResumptionManagerTest.java diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/BuildResumptionManager.java b/maven-embedder/src/main/java/org/apache/maven/cli/BuildResumptionManager.java new file mode 100644 index 000000000000..7820c0a716b2 --- /dev/null +++ b/maven-embedder/src/main/java/org/apache/maven/cli/BuildResumptionManager.java @@ -0,0 +1,238 @@ +package org.apache.maven.cli; + +/* + * 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.annotations.VisibleForTesting; +import org.apache.commons.lang3.StringUtils; +import org.apache.maven.execution.MavenExecutionResult; +import org.apache.maven.lifecycle.LifecycleExecutionException; +import org.apache.maven.model.Dependency; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.logging.Logger; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.io.IOException; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Properties; +import java.util.stream.Collectors; + +import static java.util.Comparator.comparing; + +@Named +@Singleton +class BuildResumptionManager +{ + private static final String RESUME_PROPERTIES_FILENAME = "resume.properties"; + + @Inject + private Logger logger; + + public boolean createResumptionFile( MavenExecutionResult result ) + { + Properties properties = determineResumptionProperties( result ); + + if ( properties.isEmpty() ) + { + logger.debug( "Will not create " + RESUME_PROPERTIES_FILENAME + " file: nothing to resume from" ); + return false; + } + + return writeResumptionFile( result, properties ); + } + + @VisibleForTesting + Properties determineResumptionProperties( MavenExecutionResult result ) + { + Properties properties = new Properties(); + + List failedProjects = getFailedProjectsInOrder( result ); + if ( !failedProjects.isEmpty() ) + { + MavenProject resumeFromProject = failedProjects.get( 0 ); + Optional resumeFrom = getResumeFrom( result, resumeFromProject ); + Optional projectsToSkip = determineProjectsToSkip( result, failedProjects, resumeFromProject ); + + resumeFrom.ifPresent( value -> properties.setProperty( "resumeFrom", value ) ); + projectsToSkip.ifPresent( value -> properties.setProperty( "excludedProjects", value ) ); + } + else + { + logger.warn( "Could not create " + RESUME_PROPERTIES_FILENAME + " file: no failed projects found" ); + } + + return properties; + } + + private List getFailedProjectsInOrder( MavenExecutionResult result ) + { + List sortedProjects = result.getTopologicallySortedProjects(); + + return result.getExceptions().stream() + .filter( LifecycleExecutionException.class::isInstance ) + .map( LifecycleExecutionException.class::cast ) + .map( LifecycleExecutionException::getProject ) + .sorted( comparing( sortedProjects::indexOf ) ) + .collect( Collectors.toList() ); + } + + private Optional getResumeFrom( MavenExecutionResult result, MavenProject failedProject ) + { + List allSortedProjects = result.getTopologicallySortedProjects(); + if ( !allSortedProjects.get( 0 ).equals( failedProject ) ) + { + return Optional.of( String.format( "%s:%s", failedProject.getGroupId(), failedProject.getArtifactId() ) ); + } + + return Optional.empty(); + } + + /** + * Projects after the first failed project could have succeeded by using -T or --fail-at-end. + * These projects can be skipped from later builds. + * 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. + * @return An optional containing a comma separated list of projects which can be skipped, + * or an empty optional if no projects can be skipped. + */ + private Optional determineProjectsToSkip( MavenExecutionResult result, List failedProjects, + MavenProject resumeFromProject ) + { + List allProjects = result.getTopologicallySortedProjects(); + int resumeFromProjectIndex = allProjects.indexOf( resumeFromProject ); + List remainingProjects = allProjects.subList( resumeFromProjectIndex + 1, allProjects.size() ); + + List failedProjectsGAList = failedProjects.stream() + .map( GroupArtifactPair::new ) + .collect( Collectors.toList() ); + + String projectsToSkip = remainingProjects.stream() + .filter( project -> result.getBuildSummary( project ) != null ) + .filter( project -> hasNoDependencyOnProjects( project, failedProjectsGAList ) ) + .map( project -> String.format( "%s:%s", project.getGroupId(), project.getArtifactId() ) ) + .collect( Collectors.joining( ", " ) ); + + if ( !StringUtils.isEmpty( projectsToSkip ) ) + { + return Optional.of( projectsToSkip ); + } + + return Optional.empty(); + } + + private boolean hasNoDependencyOnProjects( MavenProject project, List projectsGAs ) + { + return project.getDependencies().stream() + .map( GroupArtifactPair::new ) + .noneMatch( projectsGAs::contains ); + } + + /** + * A helper method to determine the value to resume the build with {@code -rf} taking into account the edge case where multiple modules in the reactor have the same artifactId. + *

+ * {@code -rf :artifactId} will pick up the first module which matches, but when multiple modules in the reactor have the same artifactId, effective failed module might be later in build reactor. + * This means that developer will either have to type groupId or wait for build execution of all modules which were fine, but they are still before one which reported errors. + *

Then the returned value is {@code groupId:artifactId} when there is a name clash and + * {@code :artifactId} if there is no conflict. + * + * @param mavenProjects Maven projects which are part of build execution. + * @param failedProject 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). + */ + public String getResumeFromSelector( List mavenProjects, MavenProject failedProject ) + { + for ( MavenProject buildProject : mavenProjects ) + { + if ( failedProject.getArtifactId().equals( buildProject.getArtifactId() ) && !failedProject.equals( buildProject ) ) + { + return failedProject.getGroupId() + ":" + failedProject.getArtifactId(); + } + } + return ":" + failedProject.getArtifactId(); + } + + private boolean writeResumptionFile( MavenExecutionResult result, Properties properties ) + { + Path resumeProperties = Paths.get( result.getProject().getBuild().getDirectory(), RESUME_PROPERTIES_FILENAME ); + try + { + Files.createDirectories( resumeProperties.getParent() ); + try ( Writer writer = Files.newBufferedWriter( resumeProperties ) ) + { + properties.store( writer, null ); + } + } + catch ( IOException e ) + { + logger.warn( "Could not create " + RESUME_PROPERTIES_FILENAME + " file. ", e ); + return false; + } + + return true; + } + + private static class GroupArtifactPair + { + private final String groupId; + private final String artifactId; + + GroupArtifactPair( MavenProject project ) + { + this.groupId = project.getGroupId(); + this.artifactId = project.getArtifactId(); + } + + GroupArtifactPair( Dependency dependency ) + { + this.groupId = dependency.getGroupId(); + this.artifactId = dependency.getArtifactId(); + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + GroupArtifactPair that = (GroupArtifactPair) o; + return Objects.equals( groupId, that.groupId ) && Objects.equals( artifactId, that.artifactId ); + } + + @Override + public int hashCode() + { + return Objects.hash( groupId, artifactId ); + } + } +} 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 cbdf47fdb531..9dd127ade966 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 @@ -168,6 +168,8 @@ public class MavenCli private Map configurationProcessors; + private BuildResumptionManager buildResumptionManager; + public MavenCli() { this( null ); @@ -705,6 +707,8 @@ protected void configure() dispatcher = (DefaultSecDispatcher) container.lookup( SecDispatcher.class, "maven" ); + buildResumptionManager = container.lookup( BuildResumptionManager.class ); + return container; } @@ -1025,12 +1029,20 @@ private int execute( CliRequest cliRequest ) } } - if ( project != null && !project.equals( result.getTopologicallySortedProjects().get( 0 ) ) ) + boolean resumeFileCreated = buildResumptionManager.createResumptionFile( result ); + + slf4jLogger.error( "" ); + slf4jLogger.error( "After correcting the problems, you can resume the build with the command" ); + + List sortedProjects = result.getTopologicallySortedProjects(); + if ( resumeFileCreated ) { - slf4jLogger.error( "" ); - slf4jLogger.error( "After correcting the problems, you can resume the build with the command" ); - slf4jLogger.error( buffer().a( " " ).strong( "mvn -rf " - + getResumeFrom( result.getTopologicallySortedProjects(), project ) ).toString() ); + slf4jLogger.error( buffer().a( " " ).strong( "mvn -r " ).toString() ); + } + else if ( project != null && !project.equals( sortedProjects.get( 0 ) ) ) + { + String resumeFromSelector = buildResumptionManager.getResumeFromSelector( sortedProjects, project ); + slf4jLogger.error( buffer().a( " " ).strong( "mvn -rf " + resumeFromSelector ).toString() ); } if ( MavenExecutionRequest.REACTOR_FAIL_NEVER.equals( cliRequest.request.getReactorFailureBehavior() ) ) diff --git a/maven-embedder/src/test/java/org/apache/maven/cli/BuildResumptionManagerTest.java b/maven-embedder/src/test/java/org/apache/maven/cli/BuildResumptionManagerTest.java new file mode 100644 index 000000000000..a70da327f35a --- /dev/null +++ b/maven-embedder/src/test/java/org/apache/maven/cli/BuildResumptionManagerTest.java @@ -0,0 +1,126 @@ +package org.apache.maven.cli; + +import org.apache.maven.execution.BuildFailure; +import org.apache.maven.execution.BuildSuccess; +import org.apache.maven.execution.DefaultMavenExecutionResult; +import org.apache.maven.execution.MavenExecutionResult; +import org.apache.maven.lifecycle.LifecycleExecutionException; +import org.apache.maven.model.Dependency; +import org.apache.maven.project.MavenProject; +import org.junit.Before; +import org.junit.Test; + +import java.util.Properties; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class BuildResumptionManagerTest +{ + private final BuildResumptionManager buildResumptionManager = new BuildResumptionManager(); + + private MavenExecutionResult result; + + @Before + public void before() { + result = new DefaultMavenExecutionResult(); + } + + @Test + public void resumeFromGetsDetermined() + { + MavenProject projectA = createSucceededMavenProject( "A" ); + MavenProject projectB = createFailedMavenProject( "B" ); + result.setTopologicallySortedProjects( asList( projectA, projectB ) ); + + Properties properties = buildResumptionManager.determineResumptionProperties( result ); + + assertThat( properties.get( "resumeFrom" ), is( "test:B" ) ); + } + + @Test + public void resumeFromIsIgnoredWhenFirstProjectFails() + { + MavenProject projectA = createFailedMavenProject( "A" ); + MavenProject projectB = createMavenProject( "B" ); + result.setTopologicallySortedProjects( asList( projectA, projectB ) ); + + Properties properties = buildResumptionManager.determineResumptionProperties( result ); + + assertThat( properties.containsKey( "resumeFrom" ), is(false) ); + } + + @Test + public void projectsSucceedingAfterFailedProjectsAreExcluded() + { + MavenProject projectA = createSucceededMavenProject( "A" ); + MavenProject projectB = createFailedMavenProject( "B" ); + MavenProject projectC = createSucceededMavenProject( "C" ); + result.setTopologicallySortedProjects( asList( projectA, projectB, projectC ) ); + + Properties properties = buildResumptionManager.determineResumptionProperties( result ); + + assertThat( properties.get( "excludedProjects" ), is("test:C") ); + } + + @Test + public void projectsDependingOnFailedProjectsAreNotExcluded() + { + MavenProject projectA = createSucceededMavenProject( "A" ); + MavenProject projectB = createFailedMavenProject( "B" ); + MavenProject projectC = createSucceededMavenProject( "C" ); + projectC.setDependencies( singletonList( toDependency( projectB ) ) ); + result.setTopologicallySortedProjects( asList( projectA, projectB, projectC ) ); + + Properties properties = buildResumptionManager.determineResumptionProperties( result ); + + assertThat( properties.containsKey( "excludedProjects" ), is(false) ); + } + + @Test + public void multipleExcludedProjectsAreCommaSeparated() + { + MavenProject projectA = createFailedMavenProject( "A" ); + MavenProject projectB = createSucceededMavenProject( "B" ); + MavenProject projectC = createSucceededMavenProject( "C" ); + result.setTopologicallySortedProjects( asList( projectA, projectB, projectC ) ); + + Properties properties = buildResumptionManager.determineResumptionProperties( result ); + + assertThat( properties.get( "excludedProjects" ), is( "test:B, test:C" ) ); + } + + private MavenProject createMavenProject( String artifactId ) + { + MavenProject project = new MavenProject(); + project.setGroupId( "test" ); + project.setArtifactId( artifactId ); + return project; + } + + private Dependency toDependency( MavenProject mavenProject ) + { + Dependency dependency = new Dependency(); + dependency.setGroupId( mavenProject.getGroupId() ); + dependency.setArtifactId( mavenProject.getArtifactId() ); + dependency.setVersion( mavenProject.getVersion() ); + return dependency; + } + + private MavenProject createSucceededMavenProject( String artifactId ) + { + MavenProject project = createMavenProject( artifactId ); + result.addBuildSummary( new BuildSuccess( project, 0 ) ); + return project; + } + + private MavenProject createFailedMavenProject( String artifactId ) + { + MavenProject project = createMavenProject( artifactId ); + result.addBuildSummary( new BuildFailure( project, 0, new Exception() ) ); + result.addException( new LifecycleExecutionException( "", project ) ); + return project; + } +} From fe48f1b5bb72bf83f6b364eb343a835f03f3c239 Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Fri, 22 May 2020 09:04:45 +0200 Subject: [PATCH 02/31] [MNG-5760] Refactored the getResumeFromSelector logic to be compliant with checkstyle rules. --- .../org/apache/maven/cli/BuildResumptionManager.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/BuildResumptionManager.java b/maven-embedder/src/main/java/org/apache/maven/cli/BuildResumptionManager.java index 7820c0a716b2..ecabc81974b4 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/BuildResumptionManager.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/BuildResumptionManager.java @@ -167,13 +167,15 @@ private boolean hasNoDependencyOnProjects( MavenProject project, List mavenProjects, MavenProject failedProject ) { - for ( MavenProject buildProject : mavenProjects ) + boolean hasOverlappingArtifactId = mavenProjects.stream() + .filter( project -> failedProject.getArtifactId().equals( project.getArtifactId() ) ) + .count() > 1; + + if ( hasOverlappingArtifactId ) { - if ( failedProject.getArtifactId().equals( buildProject.getArtifactId() ) && !failedProject.equals( buildProject ) ) - { - return failedProject.getGroupId() + ":" + failedProject.getArtifactId(); - } + return failedProject.getGroupId() + ":" + failedProject.getArtifactId(); } + return ":" + failedProject.getArtifactId(); } From 883d8067e2f9a39c2e6755b57df6aebc5e280d2b Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Fri, 22 May 2020 09:06:26 +0200 Subject: [PATCH 03/31] [MNG-5760] Added JavaDoc to the method which determines whether a resume-from suggestion should be given or not. --- .../java/org/apache/maven/cli/BuildResumptionManager.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/BuildResumptionManager.java b/maven-embedder/src/main/java/org/apache/maven/cli/BuildResumptionManager.java index ecabc81974b4..900699f6311b 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/BuildResumptionManager.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/BuildResumptionManager.java @@ -100,6 +100,14 @@ private List getFailedProjectsInOrder( MavenExecutionResult result .collect( Collectors.toList() ); } + /** + * Determine the project where the next build can be resumed from. + * If the failed project is the first project of the build, + * it does not make sense to use --resume-from, so the result will be empty. + * @param result The result of the Maven build. + * @param failedProject The first failed project of the build. + * @return An optional containing the resume-from suggestion. + */ private Optional getResumeFrom( MavenExecutionResult result, MavenProject failedProject ) { List allSortedProjects = result.getTopologicallySortedProjects(); From c467598b0ca2d4bcf73ea7a1ae9197f7a803dc82 Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Fri, 22 May 2020 09:25:29 +0200 Subject: [PATCH 04/31] [MNG-5760] Added license to BuildResumptionManagerTest --- .../maven/cli/BuildResumptionManagerTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/maven-embedder/src/test/java/org/apache/maven/cli/BuildResumptionManagerTest.java b/maven-embedder/src/test/java/org/apache/maven/cli/BuildResumptionManagerTest.java index a70da327f35a..5958c802ac6c 100644 --- a/maven-embedder/src/test/java/org/apache/maven/cli/BuildResumptionManagerTest.java +++ b/maven-embedder/src/test/java/org/apache/maven/cli/BuildResumptionManagerTest.java @@ -1,5 +1,24 @@ package org.apache.maven.cli; +/* + * 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.BuildFailure; import org.apache.maven.execution.BuildSuccess; import org.apache.maven.execution.DefaultMavenExecutionResult; From 0dc3c0b7741db9969889c7e569ae556091b225a0 Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Fri, 22 May 2020 15:04:38 +0200 Subject: [PATCH 05/31] [MNG-5760] When -r is given, load resume.properties and apply it to the execution request. --- .../execution}/BuildResumptionManager.java | 128 +++++++++++++----- .../DefaultMavenExecutionRequest.java | 16 +++ .../execution/MavenExecutionRequest.java | 11 ++ .../maven/graph/DefaultGraphBuilder.java | 17 +++ .../BuildResumptionManagerTest.java | 60 +++++++- .../java/org/apache/maven/cli/CLIManager.java | 3 + .../java/org/apache/maven/cli/MavenCli.java | 8 +- 7 files changed, 202 insertions(+), 41 deletions(-) rename {maven-embedder/src/main/java/org/apache/maven/cli => maven-core/src/main/java/org/apache/maven/execution}/BuildResumptionManager.java (74%) rename {maven-embedder/src/test/java/org/apache/maven/cli => maven-core/src/test/java/org/apache/maven/execution}/BuildResumptionManagerTest.java (72%) diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/BuildResumptionManager.java b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java similarity index 74% rename from maven-embedder/src/main/java/org/apache/maven/cli/BuildResumptionManager.java rename to maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java index 900699f6311b..0f6bf398e65c 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/BuildResumptionManager.java +++ b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java @@ -1,4 +1,4 @@ -package org.apache.maven.cli; +package org.apache.maven.execution; /* * Licensed to the Apache Software Foundation (ASF) under one @@ -21,7 +21,6 @@ import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.StringUtils; -import org.apache.maven.execution.MavenExecutionResult; import org.apache.maven.lifecycle.LifecycleExecutionException; import org.apache.maven.model.Dependency; import org.apache.maven.project.MavenProject; @@ -31,10 +30,12 @@ import javax.inject.Named; import javax.inject.Singleton; import java.io.IOException; +import java.io.Reader; import java.io.Writer; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -43,16 +44,24 @@ import static java.util.Comparator.comparing; +/** + * This class contains most of the logic needed for the --resume / -r feature. + * It persists information in a properties file to ensure newer builds, using the -r feature, + * skip successfully built projects. + */ @Named @Singleton -class BuildResumptionManager +public class BuildResumptionManager { private static final String RESUME_PROPERTIES_FILENAME = "resume.properties"; + private static final String RESUME_FROM_PROPERTY = "resumeFrom"; + private static final String EXCLUDED_PROJECTS_PROPERTY = "excludedProjects"; + private static final String PROPERTY_DELIMITER = ", "; @Inject private Logger logger; - - public boolean createResumptionFile( MavenExecutionResult result ) + + public boolean persistResumptionData( MavenExecutionResult result ) { Properties properties = determineResumptionProperties( result ); @@ -65,6 +74,42 @@ public boolean createResumptionFile( MavenExecutionResult result ) return writeResumptionFile( result, properties ); } + public void applyResumptionData( MavenExecutionRequest request, MavenProject rootProject ) + { + Properties properties = loadResumptionFile( rootProject.getBuild().getDirectory() ); + applyResumptionProperties( request, properties ); + } + + /** + * A helper method to determine the value to resume the build with {@code -rf} taking into account the edge case + * where multiple modules in the reactor have the same artifactId. + *

+ * {@code -rf :artifactId} will pick up the first module which matches, but when multiple modules in the reactor + * have the same artifactId, effective failed module might be later in build reactor. + * This means that developer will either have to type groupId or wait for build execution of all modules which + * were fine, but they are still before one which reported errors. + *

Then the returned value is {@code groupId:artifactId} when there is a name clash and + * {@code :artifactId} if there is no conflict. + * + * @param mavenProjects Maven projects which are part of build execution. + * @param failedProject 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). + */ + public String getResumeFromSelector( List mavenProjects, MavenProject failedProject ) + { + boolean hasOverlappingArtifactId = mavenProjects.stream() + .filter( project -> failedProject.getArtifactId().equals( project.getArtifactId() ) ) + .count() > 1; + + if ( hasOverlappingArtifactId ) + { + return failedProject.getGroupId() + ":" + failedProject.getArtifactId(); + } + + return ":" + failedProject.getArtifactId(); + } + @VisibleForTesting Properties determineResumptionProperties( MavenExecutionResult result ) { @@ -77,8 +122,8 @@ Properties determineResumptionProperties( MavenExecutionResult result ) Optional resumeFrom = getResumeFrom( result, resumeFromProject ); Optional projectsToSkip = determineProjectsToSkip( result, failedProjects, resumeFromProject ); - resumeFrom.ifPresent( value -> properties.setProperty( "resumeFrom", value ) ); - projectsToSkip.ifPresent( value -> properties.setProperty( "excludedProjects", value ) ); + resumeFrom.ifPresent( value -> properties.setProperty( RESUME_FROM_PROPERTY, value ) ); + projectsToSkip.ifPresent( value -> properties.setProperty( EXCLUDED_PROJECTS_PROPERTY, value ) ); } else { @@ -144,7 +189,7 @@ private Optional determineProjectsToSkip( MavenExecutionResult result, L .filter( project -> result.getBuildSummary( project ) != null ) .filter( project -> hasNoDependencyOnProjects( project, failedProjectsGAList ) ) .map( project -> String.format( "%s:%s", project.getGroupId(), project.getArtifactId() ) ) - .collect( Collectors.joining( ", " ) ); + .collect( Collectors.joining( PROPERTY_DELIMITER ) ); if ( !StringUtils.isEmpty( projectsToSkip ) ) { @@ -161,32 +206,6 @@ private boolean hasNoDependencyOnProjects( MavenProject project, List - * {@code -rf :artifactId} will pick up the first module which matches, but when multiple modules in the reactor have the same artifactId, effective failed module might be later in build reactor. - * This means that developer will either have to type groupId or wait for build execution of all modules which were fine, but they are still before one which reported errors. - *

Then the returned value is {@code groupId:artifactId} when there is a name clash and - * {@code :artifactId} if there is no conflict. - * - * @param mavenProjects Maven projects which are part of build execution. - * @param failedProject 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). - */ - public String getResumeFromSelector( List mavenProjects, MavenProject failedProject ) - { - boolean hasOverlappingArtifactId = mavenProjects.stream() - .filter( project -> failedProject.getArtifactId().equals( project.getArtifactId() ) ) - .count() > 1; - - if ( hasOverlappingArtifactId ) - { - return failedProject.getGroupId() + ":" + failedProject.getArtifactId(); - } - - return ":" + failedProject.getArtifactId(); - } - private boolean writeResumptionFile( MavenExecutionResult result, Properties properties ) { Path resumeProperties = Paths.get( result.getProject().getBuild().getDirectory(), RESUME_PROPERTIES_FILENAME ); @@ -207,6 +226,47 @@ private boolean writeResumptionFile( MavenExecutionResult result, Properties pro return true; } + private Properties loadResumptionFile( String rootBuildDirectory ) + { + Properties properties = new Properties(); + Path path = Paths.get( rootBuildDirectory, RESUME_PROPERTIES_FILENAME ); + if ( !Files.exists( path ) ) + { + logger.warn( "The " + path + " file does not exist. The --resume / -r feature will not work." ); + return properties; + } + + try ( Reader reader = Files.newBufferedReader( path ) ) + { + properties.load( reader ); + } + catch ( IOException e ) + { + logger.warn( "Unable to read " + path + ". The --resume / -r feature will not work." ); + } + + return properties; + } + + @VisibleForTesting + void applyResumptionProperties( MavenExecutionRequest request, Properties properties ) + { + if ( properties.containsKey( RESUME_FROM_PROPERTY ) && StringUtils.isEmpty( request.getResumeFrom() ) ) + { + String propertyValue = properties.getProperty( RESUME_FROM_PROPERTY ); + request.setResumeFrom( propertyValue ); + logger.info( "Resuming from " + propertyValue + " due to the --resume / -r feature." ); + } + + 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 '" + propertyValue + "' due to the --resume / -r feature." ); + } + } + private static class GroupArtifactPair { private final String groupId; 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 4a039eb3e151..9cbdebeddf00 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 @@ -116,6 +116,8 @@ public class DefaultMavenExecutionRequest private List excludedProjects; + private boolean resume = false; + private String resumeFrom; private String makeBehavior; @@ -300,6 +302,12 @@ public List getExcludedProjects() return excludedProjects; } + @Override + public boolean isResume() + { + return resume; + } + @Override public String getResumeFrom() { @@ -598,6 +606,14 @@ public MavenExecutionRequest setExcludedProjects( List excludedProjects return this; } + @Override + public MavenExecutionRequest setResume() + { + resume = true; + + return this; + } + @Override public MavenExecutionRequest setResumeFrom( String project ) { 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 d006a434c54c..542c34aadea9 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 @@ -171,6 +171,17 @@ public interface MavenExecutionRequest */ List getExcludedProjects(); + /** + * Sets whether the build should be resumed from the data in the resume.properties file. + * @return This request, never {@code null}. + */ + MavenExecutionRequest setResume(); + + /** + * @return Whether the build should be resumed from the data in the resume.properties file. + */ + boolean isResume(); + MavenExecutionRequest setResumeFrom( String project ); String getResumeFrom(); diff --git a/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java b/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java index 99f0266a1bbc..091cbaff892b 100644 --- a/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java +++ b/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java @@ -38,6 +38,7 @@ import org.apache.maven.MavenExecutionException; import org.apache.maven.ProjectCycleException; import org.apache.maven.artifact.ArtifactUtils; +import org.apache.maven.execution.BuildResumptionManager; import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.MavenSession; import org.apache.maven.execution.ProjectDependencyGraph; @@ -73,6 +74,9 @@ public class DefaultGraphBuilder @Inject protected ProjectBuilder projectBuilder; + @Inject + private BuildResumptionManager buildResumptionManager; + @Override public Result build( MavenSession session ) { @@ -84,6 +88,7 @@ public Result build( MavenSession session ) { final List projects = getProjectsForMavenReactor( session ); validateProjects( projects ); + enrichRequestFromResumptionData( projects, session.getRequest() ); result = reactorDependencyGraph( session, projects ); } @@ -341,6 +346,18 @@ else if ( StringUtils.isNotEmpty( request.getMakeBehavior() ) ) return result; } + private void enrichRequestFromResumptionData( List projects, MavenExecutionRequest request ) + { + if ( request.isResume() ) + { + projects.stream() + .filter( MavenProject::isExecutionRoot ) + .findFirst() + .ifPresent( rootProject -> + buildResumptionManager.applyResumptionData( request, rootProject ) ); + } + } + private String formatProjects( List projects ) { StringBuilder projectNames = new StringBuilder(); diff --git a/maven-embedder/src/test/java/org/apache/maven/cli/BuildResumptionManagerTest.java b/maven-core/src/test/java/org/apache/maven/execution/BuildResumptionManagerTest.java similarity index 72% rename from maven-embedder/src/test/java/org/apache/maven/cli/BuildResumptionManagerTest.java rename to maven-core/src/test/java/org/apache/maven/execution/BuildResumptionManagerTest.java index 5958c802ac6c..95a037593646 100644 --- a/maven-embedder/src/test/java/org/apache/maven/cli/BuildResumptionManagerTest.java +++ b/maven-core/src/test/java/org/apache/maven/execution/BuildResumptionManagerTest.java @@ -1,4 +1,4 @@ -package org.apache.maven.cli; +package org.apache.maven.execution; /* * Licensed to the Apache Software Foundation (ASF) under one @@ -19,16 +19,19 @@ * under the License. */ -import org.apache.maven.execution.BuildFailure; -import org.apache.maven.execution.BuildSuccess; -import org.apache.maven.execution.DefaultMavenExecutionResult; -import org.apache.maven.execution.MavenExecutionResult; import org.apache.maven.lifecycle.LifecycleExecutionException; import org.apache.maven.model.Dependency; import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.logging.Logger; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import java.util.ArrayList; +import java.util.List; import java.util.Properties; import static java.util.Arrays.asList; @@ -36,9 +39,14 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +@RunWith( MockitoJUnitRunner.class ) public class BuildResumptionManagerTest { - private final BuildResumptionManager buildResumptionManager = new BuildResumptionManager(); + @Mock + private Logger logger; + + @InjectMocks + private BuildResumptionManager buildResumptionManager; private MavenExecutionResult result; @@ -111,6 +119,46 @@ public void multipleExcludedProjectsAreCommaSeparated() assertThat( properties.get( "excludedProjects" ), is( "test:B, test:C" ) ); } + @Test + public void resumeFromPropertyGetsApplied() + { + MavenExecutionRequest request = new DefaultMavenExecutionRequest(); + Properties properties = new Properties(); + properties.setProperty( "resumeFrom", ":module-a" ); + + buildResumptionManager.applyResumptionProperties( request, properties ); + + assertThat( request.getResumeFrom(), is( ":module-a" ) ); + } + + @Test + public void resumeFromPropertyDoesNotOverrideExistingRequestParameters() + { + MavenExecutionRequest request = new DefaultMavenExecutionRequest(); + request.setResumeFrom( ":module-b" ); + Properties properties = new Properties(); + properties.setProperty( "resumeFrom", ":module-a" ); + + buildResumptionManager.applyResumptionProperties( request, properties ); + + assertThat( request.getResumeFrom(), is( ":module-b" ) ); + } + + @Test + public void excludedProjectsFromPropertyGetsAddedToExistingRequestParameters() + { + MavenExecutionRequest request = new DefaultMavenExecutionRequest(); + List excludedProjects = new ArrayList<>(); + excludedProjects.add( ":module-a" ); + request.setExcludedProjects( excludedProjects ); + Properties properties = new Properties(); + properties.setProperty( "excludedProjects", ":module-b, :module-c" ); + + buildResumptionManager.applyResumptionProperties( request, properties ); + + assertThat( request.getExcludedProjects(), contains( ":module-a", ":module-b", ":module-c" ) ); + } + private MavenProject createMavenProject( String artifactId ) { MavenProject project = new MavenProject(); diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java b/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java index c9e002a10ea8..873fc97620d5 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java @@ -83,6 +83,8 @@ public class CLIManager public static final String FAIL_NEVER = "fn"; + public static final String RESUME = "r"; + public static final String RESUME_FROM = "rf"; public static final String PROJECT_LIST = "pl"; @@ -134,6 +136,7 @@ public CLIManager() options.addOption( Option.builder( FAIL_FAST ).longOpt( "fail-fast" ).desc( "Stop at first failure in reactorized builds" ).build() ); options.addOption( Option.builder( FAIL_AT_END ).longOpt( "fail-at-end" ).desc( "Only fail the build afterwards; allow all non-impacted builds to continue" ).build() ); options.addOption( Option.builder( FAIL_NEVER ).longOpt( "fail-never" ).desc( "NEVER fail the build, regardless of project result" ).build() ); + options.addOption( Option.builder( RESUME ).longOpt( "resume" ).desc( "Resume reactor from the last failed project, using the resume.properties file in the build directory " ).build() ); options.addOption( Option.builder( RESUME_FROM ).longOpt( "resume-from" ).hasArg().desc( "Resume reactor from specified project" ).build() ); options.addOption( Option.builder( PROJECT_LIST ).longOpt( "projects" ).desc( "Comma-delimited list of specified reactor projects to build instead of all projects. A project can be specified by [groupId]:artifactId or by its relative path" ).hasArg().build() ); options.addOption( Option.builder( ALSO_MAKE ).longOpt( "also-make" ).desc( "If project list is specified, also build projects required by the list" ).build() ); 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 9dd127ade966..4ecd09528714 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,6 +48,7 @@ import org.apache.maven.exception.DefaultExceptionHandler; import org.apache.maven.exception.ExceptionHandler; import org.apache.maven.exception.ExceptionSummary; +import org.apache.maven.execution.BuildResumptionManager; import org.apache.maven.execution.DefaultMavenExecutionRequest; import org.apache.maven.execution.ExecutionListener; import org.apache.maven.execution.MavenExecutionRequest; @@ -1029,7 +1030,7 @@ private int execute( CliRequest cliRequest ) } } - boolean resumeFileCreated = buildResumptionManager.createResumptionFile( result ); + boolean resumeFileCreated = buildResumptionManager.persistResumptionData( result ); slf4jLogger.error( "" ); slf4jLogger.error( "After correcting the problems, you can resume the build with the command" ); @@ -1528,6 +1529,11 @@ else if ( modelProcessor != null ) request.setBaseDirectory( request.getPom().getParentFile() ); } + if ( commandLine.hasOption( CLIManager.RESUME ) ) + { + request.setResume(); + } + if ( commandLine.hasOption( CLIManager.RESUME_FROM ) ) { request.setResumeFrom( commandLine.getOptionValue( CLIManager.RESUME_FROM ) ); From a65ecc3b6fa2e961215fd818f86fab4ff5ff516a Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Fri, 22 May 2020 15:14:24 +0200 Subject: [PATCH 06/31] [MNG-5760] Created unit tests for the resumeFromSelector method. --- .../execution/BuildResumptionManagerTest.java | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/maven-core/src/test/java/org/apache/maven/execution/BuildResumptionManagerTest.java b/maven-core/src/test/java/org/apache/maven/execution/BuildResumptionManagerTest.java index 95a037593646..96c0dee98d16 100644 --- a/maven-core/src/test/java/org/apache/maven/execution/BuildResumptionManagerTest.java +++ b/maven-core/src/test/java/org/apache/maven/execution/BuildResumptionManagerTest.java @@ -159,10 +159,41 @@ public void excludedProjectsFromPropertyGetsAddedToExistingRequestParameters() assertThat( request.getExcludedProjects(), contains( ":module-a", ":module-b", ":module-c" ) ); } + @Test + public void resumeFromSelectorIsSuggestedWithoutGroupId() + { + List allProjects = asList( + createMavenProject( "group", "module-a" ), + createMavenProject( "group", "module-b" ) ); + MavenProject failedProject = allProjects.get( 0 ); + + String selector = buildResumptionManager.getResumeFromSelector( allProjects, failedProject ); + + assertThat( selector, is( ":module-a" ) ); + } + + @Test + public void resumeFromSelectorContainsGroupIdWhenArtifactIdIsNotUnique() + { + List allProjects = asList( + createMavenProject( "group-a", "module" ), + createMavenProject( "group-b", "module" ) ); + MavenProject failedProject = allProjects.get( 0 ); + + String selector = buildResumptionManager.getResumeFromSelector( allProjects, failedProject ); + + assertThat( selector, is( "group-a:module" ) ); + } + private MavenProject createMavenProject( String artifactId ) + { + return createMavenProject( "test", artifactId ); + } + + private MavenProject createMavenProject( String groupId, String artifactId ) { MavenProject project = new MavenProject(); - project.setGroupId( "test" ); + project.setGroupId( groupId ); project.setArtifactId( artifactId ); return project; } From 79df2e53cd7d4b5adaa8d39d0567dba664cb2ac1 Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Fri, 22 May 2020 15:17:46 +0200 Subject: [PATCH 07/31] [MNG-5760] Removed dead code, as the `getResumeFrom` method has been moved to the BuildResumptionManager. --- .../java/org/apache/maven/cli/MavenCli.java | 29 ------------------- 1 file changed, 29 deletions(-) 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 4ecd09528714..4877ddd119a5 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 @@ -1063,35 +1063,6 @@ else if ( project != null && !project.equals( sortedProjects.get( 0 ) ) ) } } - /** - * A helper method to determine the value to resume the build with {@code -rf} taking into account the - * edge case where multiple modules in the reactor have the same artifactId. - *

- * {@code -rf :artifactId} will pick up the first module which matches, but when multiple modules in the - * reactor have the same artifactId, effective failed module might be later in build reactor. - * This means that developer will either have to type groupId or wait for build execution of all modules - * which were fine, but they are still before one which reported errors. - *

Then the returned value is {@code groupId:artifactId} when there is a name clash and - * {@code :artifactId} if there is no conflict. - * - * @param mavenProjects Maven projects which are part of build execution. - * @param failedProject 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). - */ - private String getResumeFrom( List mavenProjects, MavenProject failedProject ) - { - for ( MavenProject buildProject : mavenProjects ) - { - if ( failedProject.getArtifactId().equals( buildProject.getArtifactId() ) && !failedProject.equals( - buildProject ) ) - { - return failedProject.getGroupId() + ":" + failedProject.getArtifactId(); - } - } - return ":" + failedProject.getArtifactId(); - } - private void logSummary( ExceptionSummary summary, Map references, String indent, boolean showErrors ) { From 8d7f51c6edd4936265b6e76354214a37da0e87c3 Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Fri, 22 May 2020 16:16:11 +0200 Subject: [PATCH 08/31] [MNG-5760] Fix bug where the build resume hint log could be logged incorrectly. --- .../main/java/org/apache/maven/cli/MavenCli.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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 4877ddd119a5..113af25e35e6 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 @@ -1032,18 +1032,15 @@ private int execute( CliRequest cliRequest ) boolean resumeFileCreated = buildResumptionManager.persistResumptionData( result ); - slf4jLogger.error( "" ); - slf4jLogger.error( "After correcting the problems, you can resume the build with the command" ); - List sortedProjects = result.getTopologicallySortedProjects(); if ( resumeFileCreated ) { - slf4jLogger.error( buffer().a( " " ).strong( "mvn -r " ).toString() ); + logBuildResumeHint( "mvn -r " ); } else if ( project != null && !project.equals( sortedProjects.get( 0 ) ) ) { String resumeFromSelector = buildResumptionManager.getResumeFromSelector( sortedProjects, project ); - slf4jLogger.error( buffer().a( " " ).strong( "mvn -rf " + resumeFromSelector ).toString() ); + logBuildResumeHint( "mvn -rf " + resumeFromSelector ); } if ( MavenExecutionRequest.REACTOR_FAIL_NEVER.equals( cliRequest.request.getReactorFailureBehavior() ) ) @@ -1063,6 +1060,13 @@ else if ( project != null && !project.equals( sortedProjects.get( 0 ) ) ) } } + private void logBuildResumeHint( String resumeBuildHint ) + { + slf4jLogger.error( "" ); + slf4jLogger.error( "After correcting the problems, you can resume the build with the command" ); + slf4jLogger.error( buffer().a( " " ).strong( resumeBuildHint ).toString() ); + } + private void logSummary( ExceptionSummary summary, Map references, String indent, boolean showErrors ) { From fe92235d3de3d9ce7e5409a4e3d35c1beb8aaff8 Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Sat, 23 May 2020 07:59:01 +0200 Subject: [PATCH 09/31] [MNG-5760] Fixed a bug where a failed project would be excluded in the next build when it failed after another failed build. --- .../maven/execution/BuildResumptionManager.java | 2 +- .../execution/BuildResumptionManagerTest.java | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java index 0f6bf398e65c..0fb22e4dadd1 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java +++ b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java @@ -186,7 +186,7 @@ private Optional determineProjectsToSkip( MavenExecutionResult result, L .collect( Collectors.toList() ); String projectsToSkip = remainingProjects.stream() - .filter( project -> result.getBuildSummary( project ) != null ) + .filter( project -> result.getBuildSummary( project ) instanceof BuildSuccess ) .filter( project -> hasNoDependencyOnProjects( project, failedProjectsGAList ) ) .map( project -> String.format( "%s:%s", project.getGroupId(), project.getArtifactId() ) ) .collect( Collectors.joining( PROPERTY_DELIMITER ) ); diff --git a/maven-core/src/test/java/org/apache/maven/execution/BuildResumptionManagerTest.java b/maven-core/src/test/java/org/apache/maven/execution/BuildResumptionManagerTest.java index 96c0dee98d16..5c3129ba9485 100644 --- a/maven-core/src/test/java/org/apache/maven/execution/BuildResumptionManagerTest.java +++ b/maven-core/src/test/java/org/apache/maven/execution/BuildResumptionManagerTest.java @@ -106,6 +106,21 @@ public void projectsDependingOnFailedProjectsAreNotExcluded() assertThat( properties.containsKey( "excludedProjects" ), is(false) ); } + @Test + public void projectsFailingAfterAnotherFailedProjectAreNotExcluded() + { + MavenProject projectA = createSucceededMavenProject( "A" ); + MavenProject projectB = createFailedMavenProject( "B" ); + MavenProject projectC = createSucceededMavenProject( "C" ); + MavenProject projectD = createFailedMavenProject( "D" ); + result.setTopologicallySortedProjects( asList( projectA, projectB, projectC, projectD ) ); + + Properties properties = buildResumptionManager.determineResumptionProperties( result ); + + assertThat( properties.get( "resumeFrom" ), is("test:B") ); + assertThat( properties.get( "excludedProjects" ), is("test:C") ); + } + @Test public void multipleExcludedProjectsAreCommaSeparated() { From 368b60bfdc7b1feb1a3330cb7eb5de52c2798441 Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Sat, 23 May 2020 08:27:50 +0200 Subject: [PATCH 10/31] [MNG-5760] Refactored to call the resumption manager from the DefaultMaven instead of the CLI. As DefaultMaven has the right info to determine the execution root. --- .../java/org/apache/maven/DefaultMaven.java | 30 ++++++++++++++++++- .../execution/BuildResumptionManager.java | 8 ++--- .../LifecycleExecutionException.java | 12 ++++++++ .../java/org/apache/maven/cli/MavenCli.java | 9 +++--- 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java index fc262901b6c8..026f729cae20 100644 --- a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java +++ b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java @@ -30,12 +30,14 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.apache.maven.artifact.ArtifactUtils; +import org.apache.maven.execution.BuildResumptionManager; import org.apache.maven.execution.DefaultMavenExecutionResult; import org.apache.maven.execution.ExecutionEvent; import org.apache.maven.execution.MavenExecutionRequest; @@ -44,6 +46,7 @@ import org.apache.maven.execution.ProjectDependencyGraph; import org.apache.maven.graph.GraphBuilder; import org.apache.maven.internal.aether.DefaultRepositorySystemSessionFactory; +import org.apache.maven.lifecycle.LifecycleExecutionException; import org.apache.maven.lifecycle.internal.ExecutionEventCatapult; import org.apache.maven.lifecycle.internal.LifecycleStarter; import org.apache.maven.model.Prerequisites; @@ -99,6 +102,9 @@ public class DefaultMaven @Named( GraphBuilder.HINT ) private GraphBuilder graphBuilder; + @Inject + private BuildResumptionManager buildResumptionManager; + @Override public MavenExecutionResult execute( MavenExecutionRequest request ) { @@ -312,7 +318,9 @@ private MavenExecutionResult doExecute( MavenExecutionRequest request, MavenSess if ( session.getResult().hasExceptions() ) { - return addExceptionToResult( result, session.getResult().getExceptions().get( 0 ) ); + addExceptionToResult( result, session.getResult().getExceptions().get( 0 ) ); + saveResumptionDataWhenApplicable( result, session ); + return result; } } finally @@ -349,6 +357,26 @@ private void afterSessionEnd( Collection projects, MavenSession se } } + private void saveResumptionDataWhenApplicable( MavenExecutionResult result, MavenSession session ) + { + List lifecycleExecutionExceptions = result.getExceptions().stream() + .filter( LifecycleExecutionException.class::isInstance ) + .map( LifecycleExecutionException.class::cast ) + .collect( Collectors.toList() ); + + if ( !lifecycleExecutionExceptions.isEmpty() ) + { + session.getAllProjects().stream() + .filter( MavenProject::isExecutionRoot ) + .findFirst() + .ifPresent( rootProject -> + { + boolean persisted = buildResumptionManager.persistResumptionData( result, rootProject ); + lifecycleExecutionExceptions.forEach( e -> e.setBuildResumptionDataSaved( persisted ) ); + } ); + } + } + public RepositorySystemSession newRepositorySession( MavenExecutionRequest request ) { return repositorySessionFactory.newRepositorySession( request ); diff --git a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java index 0fb22e4dadd1..2e7e775da8b7 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java +++ b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java @@ -61,7 +61,7 @@ public class BuildResumptionManager @Inject private Logger logger; - public boolean persistResumptionData( MavenExecutionResult result ) + public boolean persistResumptionData( MavenExecutionResult result, MavenProject rootProject ) { Properties properties = determineResumptionProperties( result ); @@ -71,7 +71,7 @@ public boolean persistResumptionData( MavenExecutionResult result ) return false; } - return writeResumptionFile( result, properties ); + return writeResumptionFile( rootProject, properties ); } public void applyResumptionData( MavenExecutionRequest request, MavenProject rootProject ) @@ -206,9 +206,9 @@ private boolean hasNoDependencyOnProjects( MavenProject project, List references = new LinkedHashMap<>(); MavenProject project = null; + boolean isResumptionDataSaved = false; for ( Throwable exception : result.getExceptions() ) { @@ -1001,7 +1002,9 @@ private int execute( CliRequest cliRequest ) if ( project == null && exception instanceof LifecycleExecutionException ) { - project = ( (LifecycleExecutionException) exception ).getProject(); + LifecycleExecutionException lifecycleExecutionException = (LifecycleExecutionException) exception; + project = lifecycleExecutionException.getProject(); + isResumptionDataSaved = lifecycleExecutionException.isBuildResumptionDataSaved(); } } @@ -1030,10 +1033,8 @@ private int execute( CliRequest cliRequest ) } } - boolean resumeFileCreated = buildResumptionManager.persistResumptionData( result ); - List sortedProjects = result.getTopologicallySortedProjects(); - if ( resumeFileCreated ) + if ( isResumptionDataSaved ) { logBuildResumeHint( "mvn -r " ); } From 712932d6f38002f71b888a064037516940e19d10 Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Sat, 23 May 2020 08:29:11 +0200 Subject: [PATCH 11/31] [MNG-5760] Removing the resumption data when a build succeeded. --- .../main/java/org/apache/maven/DefaultMaven.java | 7 +++++++ .../maven/execution/BuildResumptionManager.java | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java index 026f729cae20..4907ac98ccef 100644 --- a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java +++ b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java @@ -322,6 +322,13 @@ private MavenExecutionResult doExecute( MavenExecutionRequest request, MavenSess saveResumptionDataWhenApplicable( result, session ); return result; } + else + { + session.getAllProjects().stream() + .filter( MavenProject::isExecutionRoot ) + .findFirst() + .ifPresent( buildResumptionManager::removeResumptionData ); + } } finally { diff --git a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java index 2e7e775da8b7..e0ab008f8f4f 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java +++ b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java @@ -80,6 +80,19 @@ public void applyResumptionData( MavenExecutionRequest request, MavenProject roo applyResumptionProperties( request, properties ); } + public void removeResumptionData( MavenProject rootProject ) + { + Path resumeProperties = Paths.get( rootProject.getBuild().getDirectory(), RESUME_PROPERTIES_FILENAME ); + try + { + Files.deleteIfExists( resumeProperties ); + } + catch ( IOException e ) + { + logger.warn( "Could not delete " + RESUME_PROPERTIES_FILENAME + " file. ", e ); + } + } + /** * A helper method to determine the value to resume the build with {@code -rf} taking into account the edge case * where multiple modules in the reactor have the same artifactId. From 2763ee0c2237ad71aee056cdc5927d1700b13f26 Mon Sep 17 00:00:00 2001 From: Maarten Mulders Date: Sat, 23 May 2020 19:56:34 +0200 Subject: [PATCH 12/31] [MNG-5760] Extract public interface --- .../execution/BuildResumptionManager.java | 299 ++--------------- .../DefaultBuildResumptionManager.java | 308 ++++++++++++++++++ ...=> DefaultBuildResumptionManagerTest.java} | 4 +- 3 files changed, 336 insertions(+), 275 deletions(-) create mode 100644 maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java rename maven-core/src/test/java/org/apache/maven/execution/{BuildResumptionManagerTest.java => DefaultBuildResumptionManagerTest.java} (98%) diff --git a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java index e0ab008f8f4f..f3d41aa3634e 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java +++ b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java @@ -19,79 +19,40 @@ * under the License. */ -import com.google.common.annotations.VisibleForTesting; -import org.apache.commons.lang3.StringUtils; -import org.apache.maven.lifecycle.LifecycleExecutionException; -import org.apache.maven.model.Dependency; import org.apache.maven.project.MavenProject; -import org.codehaus.plexus.logging.Logger; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; -import java.io.IOException; -import java.io.Reader; -import java.io.Writer; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Properties; -import java.util.stream.Collectors; - -import static java.util.Comparator.comparing; /** - * This class contains most of the logic needed for the --resume / -r feature. - * It persists information in a properties file to ensure newer builds, using the -r feature, - * skip successfully built projects. + * This class describes most of the logic needed for the --resume / -r feature. Its goal is to ensure newer + * builds of the same project that have the -r command-line flag skip successfully built projects during earlier + * invocations of Maven. */ -@Named -@Singleton -public class BuildResumptionManager +public interface BuildResumptionManager { - private static final String RESUME_PROPERTIES_FILENAME = "resume.properties"; - private static final String RESUME_FROM_PROPERTY = "resumeFrom"; - private static final String EXCLUDED_PROJECTS_PROPERTY = "excludedProjects"; - private static final String PROPERTY_DELIMITER = ", "; - - @Inject - private Logger logger; - - public boolean persistResumptionData( MavenExecutionResult result, MavenProject rootProject ) - { - Properties properties = determineResumptionProperties( result ); - - if ( properties.isEmpty() ) - { - logger.debug( "Will not create " + RESUME_PROPERTIES_FILENAME + " file: nothing to resume from" ); - return false; - } - - return writeResumptionFile( rootProject, properties ); - } + /** + * Persists any data needed to resume the build at a later point in time, using a new Maven invocation. This method + * may also decide it is not needed or meaningful to persist such data, and return false to indicate + * so. + * + * @param result The result of the current Maven invocation. + * @param rootProject The root project that is being built. + * @return Whether any data was persisted. + */ + boolean persistResumptionData( final MavenExecutionResult result, final MavenProject rootProject ); - public void applyResumptionData( MavenExecutionRequest request, MavenProject rootProject ) - { - Properties properties = loadResumptionFile( rootProject.getBuild().getDirectory() ); - applyResumptionProperties( request, properties ); - } + /** + * Uses previously stored resumption data to enrich an existing execution request. + * @param request The execution request that will be enriched. + * @param rootProject The root project that is being built. + */ + void applyResumptionData( final MavenExecutionRequest request, final MavenProject rootProject ); - public void removeResumptionData( MavenProject rootProject ) - { - Path resumeProperties = Paths.get( rootProject.getBuild().getDirectory(), RESUME_PROPERTIES_FILENAME ); - try - { - Files.deleteIfExists( resumeProperties ); - } - catch ( IOException e ) - { - logger.warn( "Could not delete " + RESUME_PROPERTIES_FILENAME + " file. ", e ); - } - } + /** + * Removes previously stored resumption data. + * @param rootProject The root project that is being built. + */ + void removeResumptionData( final MavenProject rootProject ); /** * A helper method to determine the value to resume the build with {@code -rf} taking into account the edge case @@ -109,213 +70,5 @@ public void removeResumptionData( MavenProject rootProject ) * @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). */ - public String getResumeFromSelector( List mavenProjects, MavenProject failedProject ) - { - boolean hasOverlappingArtifactId = mavenProjects.stream() - .filter( project -> failedProject.getArtifactId().equals( project.getArtifactId() ) ) - .count() > 1; - - if ( hasOverlappingArtifactId ) - { - return failedProject.getGroupId() + ":" + failedProject.getArtifactId(); - } - - return ":" + failedProject.getArtifactId(); - } - - @VisibleForTesting - Properties determineResumptionProperties( MavenExecutionResult result ) - { - Properties properties = new Properties(); - - List failedProjects = getFailedProjectsInOrder( result ); - if ( !failedProjects.isEmpty() ) - { - MavenProject resumeFromProject = failedProjects.get( 0 ); - Optional resumeFrom = getResumeFrom( result, resumeFromProject ); - Optional projectsToSkip = determineProjectsToSkip( result, failedProjects, resumeFromProject ); - - resumeFrom.ifPresent( value -> properties.setProperty( RESUME_FROM_PROPERTY, value ) ); - projectsToSkip.ifPresent( value -> properties.setProperty( EXCLUDED_PROJECTS_PROPERTY, value ) ); - } - else - { - logger.warn( "Could not create " + RESUME_PROPERTIES_FILENAME + " file: no failed projects found" ); - } - - return properties; - } - - private List getFailedProjectsInOrder( MavenExecutionResult result ) - { - List sortedProjects = result.getTopologicallySortedProjects(); - - return result.getExceptions().stream() - .filter( LifecycleExecutionException.class::isInstance ) - .map( LifecycleExecutionException.class::cast ) - .map( LifecycleExecutionException::getProject ) - .sorted( comparing( sortedProjects::indexOf ) ) - .collect( Collectors.toList() ); - } - - /** - * Determine the project where the next build can be resumed from. - * If the failed project is the first project of the build, - * it does not make sense to use --resume-from, so the result will be empty. - * @param result The result of the Maven build. - * @param failedProject The first failed project of the build. - * @return An optional containing the resume-from suggestion. - */ - private Optional getResumeFrom( MavenExecutionResult result, MavenProject failedProject ) - { - List allSortedProjects = result.getTopologicallySortedProjects(); - if ( !allSortedProjects.get( 0 ).equals( failedProject ) ) - { - return Optional.of( String.format( "%s:%s", failedProject.getGroupId(), failedProject.getArtifactId() ) ); - } - - return Optional.empty(); - } - - /** - * Projects after the first failed project could have succeeded by using -T or --fail-at-end. - * These projects can be skipped from later builds. - * 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. - * @return An optional containing a comma separated list of projects which can be skipped, - * or an empty optional if no projects can be skipped. - */ - private Optional determineProjectsToSkip( MavenExecutionResult result, List failedProjects, - MavenProject resumeFromProject ) - { - List allProjects = result.getTopologicallySortedProjects(); - int resumeFromProjectIndex = allProjects.indexOf( resumeFromProject ); - List remainingProjects = allProjects.subList( resumeFromProjectIndex + 1, allProjects.size() ); - - List failedProjectsGAList = failedProjects.stream() - .map( GroupArtifactPair::new ) - .collect( Collectors.toList() ); - - String projectsToSkip = remainingProjects.stream() - .filter( project -> result.getBuildSummary( project ) instanceof BuildSuccess ) - .filter( project -> hasNoDependencyOnProjects( project, failedProjectsGAList ) ) - .map( project -> String.format( "%s:%s", project.getGroupId(), project.getArtifactId() ) ) - .collect( Collectors.joining( PROPERTY_DELIMITER ) ); - - if ( !StringUtils.isEmpty( projectsToSkip ) ) - { - return Optional.of( projectsToSkip ); - } - - return Optional.empty(); - } - - private boolean hasNoDependencyOnProjects( MavenProject project, List projectsGAs ) - { - return project.getDependencies().stream() - .map( GroupArtifactPair::new ) - .noneMatch( projectsGAs::contains ); - } - - private boolean writeResumptionFile( MavenProject rootProject, Properties properties ) - { - Path resumeProperties = Paths.get( rootProject.getBuild().getDirectory(), RESUME_PROPERTIES_FILENAME ); - try - { - Files.createDirectories( resumeProperties.getParent() ); - try ( Writer writer = Files.newBufferedWriter( resumeProperties ) ) - { - properties.store( writer, null ); - } - } - catch ( IOException e ) - { - logger.warn( "Could not create " + RESUME_PROPERTIES_FILENAME + " file. ", e ); - return false; - } - - return true; - } - - private Properties loadResumptionFile( String rootBuildDirectory ) - { - Properties properties = new Properties(); - Path path = Paths.get( rootBuildDirectory, RESUME_PROPERTIES_FILENAME ); - if ( !Files.exists( path ) ) - { - logger.warn( "The " + path + " file does not exist. The --resume / -r feature will not work." ); - return properties; - } - - try ( Reader reader = Files.newBufferedReader( path ) ) - { - properties.load( reader ); - } - catch ( IOException e ) - { - logger.warn( "Unable to read " + path + ". The --resume / -r feature will not work." ); - } - - return properties; - } - - @VisibleForTesting - void applyResumptionProperties( MavenExecutionRequest request, Properties properties ) - { - if ( properties.containsKey( RESUME_FROM_PROPERTY ) && StringUtils.isEmpty( request.getResumeFrom() ) ) - { - String propertyValue = properties.getProperty( RESUME_FROM_PROPERTY ); - request.setResumeFrom( propertyValue ); - logger.info( "Resuming from " + propertyValue + " due to the --resume / -r feature." ); - } - - 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 '" + propertyValue + "' due to the --resume / -r feature." ); - } - } - - private static class GroupArtifactPair - { - private final String groupId; - private final String artifactId; - - GroupArtifactPair( MavenProject project ) - { - this.groupId = project.getGroupId(); - this.artifactId = project.getArtifactId(); - } - - GroupArtifactPair( Dependency dependency ) - { - this.groupId = dependency.getGroupId(); - this.artifactId = dependency.getArtifactId(); - } - - @Override - public boolean equals( Object o ) - { - if ( this == o ) - { - return true; - } - if ( o == null || getClass() != o.getClass() ) - { - return false; - } - GroupArtifactPair that = (GroupArtifactPair) o; - return Objects.equals( groupId, that.groupId ) && Objects.equals( artifactId, that.artifactId ); - } - - @Override - public int hashCode() - { - return Objects.hash( groupId, artifactId ); - } - } + String getResumeFromSelector( final List mavenProjects, final MavenProject failedProject ); } diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java new file mode 100644 index 000000000000..d08478fd0a7e --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java @@ -0,0 +1,308 @@ +package org.apache.maven.execution; + +/* + * 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.annotations.VisibleForTesting; +import org.apache.commons.lang3.StringUtils; +import org.apache.maven.lifecycle.LifecycleExecutionException; +import org.apache.maven.model.Dependency; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.logging.Logger; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Properties; +import java.util.stream.Collectors; + +import static java.util.Comparator.comparing; + +/** + * This implementation of {@link BuildResumptionManager} persists information in a properties file. The file is stored + * in the build output directory under the Maven execution root. + */ +@Named +@Singleton +public class DefaultBuildResumptionManager implements BuildResumptionManager +{ + private static final String RESUME_PROPERTIES_FILENAME = "resume.properties"; + private static final String RESUME_FROM_PROPERTY = "resumeFrom"; + private static final String EXCLUDED_PROJECTS_PROPERTY = "excludedProjects"; + private static final String PROPERTY_DELIMITER = ", "; + + @Inject + private Logger logger; + + @Override + public boolean persistResumptionData( MavenExecutionResult result, MavenProject rootProject ) + { + Properties properties = determineResumptionProperties( result ); + + if ( properties.isEmpty() ) + { + logger.debug( "Will not create " + RESUME_PROPERTIES_FILENAME + " file: nothing to resume from" ); + return false; + } + + return writeResumptionFile( rootProject, properties ); + } + + @Override + public void applyResumptionData( MavenExecutionRequest request, MavenProject rootProject ) + { + Properties properties = loadResumptionFile( rootProject.getBuild().getDirectory() ); + applyResumptionProperties( request, properties ); + } + + @Override + public void removeResumptionData( MavenProject rootProject ) + { + Path resumeProperties = Paths.get( rootProject.getBuild().getDirectory(), RESUME_PROPERTIES_FILENAME ); + try + { + Files.deleteIfExists( resumeProperties ); + } + catch ( IOException e ) + { + logger.warn( "Could not delete " + RESUME_PROPERTIES_FILENAME + " file. ", e ); + } + } + + @Override + public String getResumeFromSelector( List mavenProjects, MavenProject failedProject ) + { + boolean hasOverlappingArtifactId = mavenProjects.stream() + .filter( project -> failedProject.getArtifactId().equals( project.getArtifactId() ) ) + .count() > 1; + + if ( hasOverlappingArtifactId ) + { + return failedProject.getGroupId() + ":" + failedProject.getArtifactId(); + } + + return ":" + failedProject.getArtifactId(); + } + + @VisibleForTesting + Properties determineResumptionProperties( MavenExecutionResult result ) + { + Properties properties = new Properties(); + + List failedProjects = getFailedProjectsInOrder( result ); + if ( !failedProjects.isEmpty() ) + { + MavenProject resumeFromProject = failedProjects.get( 0 ); + Optional resumeFrom = getResumeFrom( result, resumeFromProject ); + Optional projectsToSkip = determineProjectsToSkip( result, failedProjects, resumeFromProject ); + + resumeFrom.ifPresent( value -> properties.setProperty( RESUME_FROM_PROPERTY, value ) ); + projectsToSkip.ifPresent( value -> properties.setProperty( EXCLUDED_PROJECTS_PROPERTY, value ) ); + } + else + { + logger.warn( "Could not create " + RESUME_PROPERTIES_FILENAME + " file: no failed projects found" ); + } + + return properties; + } + + private List getFailedProjectsInOrder( MavenExecutionResult result ) + { + List sortedProjects = result.getTopologicallySortedProjects(); + + return result.getExceptions().stream() + .filter( LifecycleExecutionException.class::isInstance ) + .map( LifecycleExecutionException.class::cast ) + .map( LifecycleExecutionException::getProject ) + .sorted( comparing( sortedProjects::indexOf ) ) + .collect( Collectors.toList() ); + } + + /** + * Determine the project where the next build can be resumed from. + * If the failed project is the first project of the build, + * it does not make sense to use --resume-from, so the result will be empty. + * @param result The result of the Maven build. + * @param failedProject The first failed project of the build. + * @return An optional containing the resume-from suggestion. + */ + private Optional getResumeFrom( MavenExecutionResult result, MavenProject failedProject ) + { + List allSortedProjects = result.getTopologicallySortedProjects(); + if ( !allSortedProjects.get( 0 ).equals( failedProject ) ) + { + return Optional.of( String.format( "%s:%s", failedProject.getGroupId(), failedProject.getArtifactId() ) ); + } + + return Optional.empty(); + } + + /** + * Projects after the first failed project could have succeeded by using -T or --fail-at-end. + * These projects can be skipped from later builds. + * 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. + * @return An optional containing a comma separated list of projects which can be skipped, + * or an empty optional if no projects can be skipped. + */ + private Optional determineProjectsToSkip( MavenExecutionResult result, List failedProjects, + MavenProject resumeFromProject ) + { + List allProjects = result.getTopologicallySortedProjects(); + int resumeFromProjectIndex = allProjects.indexOf( resumeFromProject ); + List remainingProjects = allProjects.subList( resumeFromProjectIndex + 1, allProjects.size() ); + + List failedProjectsGAList = failedProjects.stream() + .map( GroupArtifactPair::new ) + .collect( Collectors.toList() ); + + String projectsToSkip = remainingProjects.stream() + .filter( project -> result.getBuildSummary( project ) instanceof BuildSuccess ) + .filter( project -> hasNoDependencyOnProjects( project, failedProjectsGAList ) ) + .map( project -> String.format( "%s:%s", project.getGroupId(), project.getArtifactId() ) ) + .collect( Collectors.joining( PROPERTY_DELIMITER ) ); + + if ( !StringUtils.isEmpty( projectsToSkip ) ) + { + return Optional.of( projectsToSkip ); + } + + return Optional.empty(); + } + + private boolean hasNoDependencyOnProjects( MavenProject project, List projectsGAs ) + { + return project.getDependencies().stream() + .map( GroupArtifactPair::new ) + .noneMatch( projectsGAs::contains ); + } + + private boolean writeResumptionFile( MavenProject rootProject, Properties properties ) + { + Path resumeProperties = Paths.get( rootProject.getBuild().getDirectory(), RESUME_PROPERTIES_FILENAME ); + try + { + Files.createDirectories( resumeProperties.getParent() ); + try ( Writer writer = Files.newBufferedWriter( resumeProperties ) ) + { + properties.store( writer, null ); + } + } + catch ( IOException e ) + { + logger.warn( "Could not create " + RESUME_PROPERTIES_FILENAME + " file. ", e ); + return false; + } + + return true; + } + + private Properties loadResumptionFile( String rootBuildDirectory ) + { + Properties properties = new Properties(); + Path path = Paths.get( rootBuildDirectory, RESUME_PROPERTIES_FILENAME ); + if ( !Files.exists( path ) ) + { + logger.warn( "The " + path + " file does not exist. The --resume / -r feature will not work." ); + return properties; + } + + try ( Reader reader = Files.newBufferedReader( path ) ) + { + properties.load( reader ); + } + catch ( IOException e ) + { + logger.warn( "Unable to read " + path + ". The --resume / -r feature will not work." ); + } + + return properties; + } + + @VisibleForTesting + void applyResumptionProperties( MavenExecutionRequest request, Properties properties ) + { + if ( properties.containsKey( RESUME_FROM_PROPERTY ) && StringUtils.isEmpty( request.getResumeFrom() ) ) + { + String propertyValue = properties.getProperty( RESUME_FROM_PROPERTY ); + request.setResumeFrom( propertyValue ); + logger.info( "Resuming from " + propertyValue + " due to the --resume / -r feature." ); + } + + 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 '" + propertyValue + "' due to the --resume / -r feature." ); + } + } + + private static class GroupArtifactPair + { + private final String groupId; + private final String artifactId; + + GroupArtifactPair( MavenProject project ) + { + this.groupId = project.getGroupId(); + this.artifactId = project.getArtifactId(); + } + + GroupArtifactPair( Dependency dependency ) + { + this.groupId = dependency.getGroupId(); + this.artifactId = dependency.getArtifactId(); + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + GroupArtifactPair that = (GroupArtifactPair) o; + return Objects.equals( groupId, that.groupId ) && Objects.equals( artifactId, that.artifactId ); + } + + @Override + public int hashCode() + { + return Objects.hash( groupId, artifactId ); + } + } +} diff --git a/maven-core/src/test/java/org/apache/maven/execution/BuildResumptionManagerTest.java b/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumptionManagerTest.java similarity index 98% rename from maven-core/src/test/java/org/apache/maven/execution/BuildResumptionManagerTest.java rename to maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumptionManagerTest.java index 5c3129ba9485..a0c913b49384 100644 --- a/maven-core/src/test/java/org/apache/maven/execution/BuildResumptionManagerTest.java +++ b/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumptionManagerTest.java @@ -40,13 +40,13 @@ import static org.hamcrest.Matchers.*; @RunWith( MockitoJUnitRunner.class ) -public class BuildResumptionManagerTest +public class DefaultBuildResumptionManagerTest { @Mock private Logger logger; @InjectMocks - private BuildResumptionManager buildResumptionManager; + private DefaultBuildResumptionManager buildResumptionManager; private MavenExecutionResult result; From 7bc1e06aa5fa719ecf5882304eef1b2f42a31ef5 Mon Sep 17 00:00:00 2001 From: Maarten Mulders Date: Sat, 23 May 2020 20:30:46 +0200 Subject: [PATCH 13/31] [MNG-5760] Store information about resumption storage in execution result --- .../main/java/org/apache/maven/DefaultMaven.java | 13 +++++++------ .../execution/DefaultMavenExecutionResult.java | 14 ++++++++++++++ .../maven/execution/MavenExecutionResult.java | 14 ++++++++++++++ .../lifecycle/LifecycleExecutionException.java | 12 ------------ .../main/java/org/apache/maven/cli/MavenCli.java | 4 +--- 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java index 4907ac98ccef..a5e84f3fb110 100644 --- a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java +++ b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java @@ -366,20 +366,21 @@ private void afterSessionEnd( Collection projects, MavenSession se private void saveResumptionDataWhenApplicable( MavenExecutionResult result, MavenSession session ) { - List lifecycleExecutionExceptions = result.getExceptions().stream() + long lifecycleExecutionExceptionCount = result.getExceptions().stream() .filter( LifecycleExecutionException.class::isInstance ) - .map( LifecycleExecutionException.class::cast ) - .collect( Collectors.toList() ); + .count(); - if ( !lifecycleExecutionExceptions.isEmpty() ) + if ( lifecycleExecutionExceptionCount > 0 ) { session.getAllProjects().stream() .filter( MavenProject::isExecutionRoot ) .findFirst() .ifPresent( rootProject -> { - boolean persisted = buildResumptionManager.persistResumptionData( result, rootProject ); - lifecycleExecutionExceptions.forEach( e -> e.setBuildResumptionDataSaved( persisted ) ); + if ( buildResumptionManager.persistResumptionData( result, rootProject ) ) + { + result.setResumptionDataStored(); + } } ); } } diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionResult.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionResult.java index 6ab1daac800e..f346b7fb1ce6 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionResult.java +++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionResult.java @@ -43,6 +43,8 @@ public class DefaultMavenExecutionResult private final Map buildSummaries = Collections.synchronizedMap( new IdentityHashMap<>() ); + private boolean resumptionDataStored = false; + public MavenExecutionResult setProject( MavenProject project ) { this.project = project; @@ -108,4 +110,16 @@ public void addBuildSummary( BuildSummary summary ) { buildSummaries.put( summary.getProject(), summary ); } + + @Override + public boolean isResumptionDataStored() + { + return resumptionDataStored; + } + + @Override + public void setResumptionDataStored() + { + this.resumptionDataStored = true; + } } diff --git a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java index cb95fb1fa723..301d4590c640 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java +++ b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java @@ -67,4 +67,18 @@ public interface MavenExecutionResult * @param summary The build summary to add, must not be {@code null}. */ void addBuildSummary( BuildSummary summary ); + + /** + * Indicates whether or not resumption data has been stored. + * @see org.apache.maven.execution.BuildResumptionManager + * @return true when it is possible to resume the build, false otherwise. + */ + boolean isResumptionDataStored(); + + /** + * Indicate that resumption data has been stored. + * @see org.apache.maven.execution.BuildResumptionManager + * @see #isResumptionDataStored() + */ + void setResumptionDataStored(); } diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/LifecycleExecutionException.java b/maven-core/src/main/java/org/apache/maven/lifecycle/LifecycleExecutionException.java index af9821a24bc8..0831a4f9014d 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/LifecycleExecutionException.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/LifecycleExecutionException.java @@ -33,8 +33,6 @@ public class LifecycleExecutionException { private MavenProject project; - private boolean buildResumptionDataSaved = false; - public LifecycleExecutionException( String message ) { super( message ); @@ -78,16 +76,6 @@ public MavenProject getProject() return project; } - public boolean isBuildResumptionDataSaved() - { - return buildResumptionDataSaved; - } - - public void setBuildResumptionDataSaved( boolean isBuildResumptionDataSaved ) - { - this.buildResumptionDataSaved = isBuildResumptionDataSaved; - } - private static String createMessage( MojoExecution execution, MavenProject project, Throwable cause ) { MessageBuilder buffer = buffer( 256 ); 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 657dfe027706..66b1fc85c55d 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 @@ -992,7 +992,6 @@ private int execute( CliRequest cliRequest ) Map references = new LinkedHashMap<>(); MavenProject project = null; - boolean isResumptionDataSaved = false; for ( Throwable exception : result.getExceptions() ) { @@ -1004,7 +1003,6 @@ private int execute( CliRequest cliRequest ) { LifecycleExecutionException lifecycleExecutionException = (LifecycleExecutionException) exception; project = lifecycleExecutionException.getProject(); - isResumptionDataSaved = lifecycleExecutionException.isBuildResumptionDataSaved(); } } @@ -1034,7 +1032,7 @@ private int execute( CliRequest cliRequest ) } List sortedProjects = result.getTopologicallySortedProjects(); - if ( isResumptionDataSaved ) + if ( result.isResumptionDataStored() ) { logBuildResumeHint( "mvn -r " ); } From c7fee3f304826a9b74b9494dce670900f654a38c Mon Sep 17 00:00:00 2001 From: Maarten Mulders Date: Mon, 25 May 2020 15:39:50 +0200 Subject: [PATCH 14/31] [MNG-5760] Replace Stream#filter#count with Stream#anyMatch --- .../src/main/java/org/apache/maven/DefaultMaven.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java index a5e84f3fb110..9f6c56fa3abe 100644 --- a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java +++ b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java @@ -366,11 +366,10 @@ private void afterSessionEnd( Collection projects, MavenSession se private void saveResumptionDataWhenApplicable( MavenExecutionResult result, MavenSession session ) { - long lifecycleExecutionExceptionCount = result.getExceptions().stream() - .filter( LifecycleExecutionException.class::isInstance ) - .count(); + boolean hasLifecycleExecutionExceptions = result.getExceptions().stream() + .anyMatch( LifecycleExecutionException.class::isInstance ); - if ( lifecycleExecutionExceptionCount > 0 ) + if ( hasLifecycleExecutionExceptions ) { session.getAllProjects().stream() .filter( MavenProject::isExecutionRoot ) From b93dbfd582ae86c906b08c4886449feed5103e5c Mon Sep 17 00:00:00 2001 From: Maarten Mulders Date: Mon, 25 May 2020 15:45:57 +0200 Subject: [PATCH 15/31] [MNG-5760] Avoid stringly-typed method signatures --- .../maven/execution/DefaultBuildResumptionManager.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java index d08478fd0a7e..4b7831b88ff0 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java +++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java @@ -77,7 +77,7 @@ public boolean persistResumptionData( MavenExecutionResult result, MavenProject @Override public void applyResumptionData( MavenExecutionRequest request, MavenProject rootProject ) { - Properties properties = loadResumptionFile( rootProject.getBuild().getDirectory() ); + Properties properties = loadResumptionFile( Paths.get( rootProject.getBuild().getDirectory() ) ); applyResumptionProperties( request, properties ); } @@ -226,10 +226,10 @@ private boolean writeResumptionFile( MavenProject rootProject, Properties proper return true; } - private Properties loadResumptionFile( String rootBuildDirectory ) + private Properties loadResumptionFile( Path rootBuildDirectory ) { Properties properties = new Properties(); - Path path = Paths.get( rootBuildDirectory, RESUME_PROPERTIES_FILENAME ); + Path path = Paths.get( RESUME_PROPERTIES_FILENAME ).resolve( rootBuildDirectory ); if ( !Files.exists( path ) ) { logger.warn( "The " + path + " file does not exist. The --resume / -r feature will not work." ); From 8bcaa9f556094928797fc03307f8fbaf8e9f2959 Mon Sep 17 00:00:00 2001 From: Maarten Mulders Date: Mon, 25 May 2020 16:05:00 +0200 Subject: [PATCH 16/31] [MNG-5760] Rename resumptionDataStored to canResume --- .../src/main/java/org/apache/maven/DefaultMaven.java | 3 +-- .../maven/execution/DefaultMavenExecutionResult.java | 10 +++++----- .../apache/maven/execution/MavenExecutionResult.java | 10 +++++----- .../src/main/java/org/apache/maven/cli/MavenCli.java | 2 +- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java index 9f6c56fa3abe..ffda19d7b84d 100644 --- a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java +++ b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java @@ -30,7 +30,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Named; @@ -378,7 +377,7 @@ private void saveResumptionDataWhenApplicable( MavenExecutionResult result, Mave { if ( buildResumptionManager.persistResumptionData( result, rootProject ) ) { - result.setResumptionDataStored(); + result.setCanResume(); } } ); } diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionResult.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionResult.java index f346b7fb1ce6..ae87d5cb8dd1 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionResult.java +++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionResult.java @@ -43,7 +43,7 @@ public class DefaultMavenExecutionResult private final Map buildSummaries = Collections.synchronizedMap( new IdentityHashMap<>() ); - private boolean resumptionDataStored = false; + private boolean canResume = false; public MavenExecutionResult setProject( MavenProject project ) { @@ -112,14 +112,14 @@ public void addBuildSummary( BuildSummary summary ) } @Override - public boolean isResumptionDataStored() + public boolean canResume() { - return resumptionDataStored; + return canResume; } @Override - public void setResumptionDataStored() + public void setCanResume() { - this.resumptionDataStored = true; + this.canResume = true; } } diff --git a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java index 301d4590c640..07a1eb87264c 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java +++ b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java @@ -69,16 +69,16 @@ public interface MavenExecutionResult void addBuildSummary( BuildSummary summary ); /** - * Indicates whether or not resumption data has been stored. + * Indicates whether or not the build could be resumed by a second invocation of Maven. * @see org.apache.maven.execution.BuildResumptionManager * @return true when it is possible to resume the build, false otherwise. */ - boolean isResumptionDataStored(); + boolean canResume(); /** - * Indicate that resumption data has been stored. + * Indicate that the build could be resumed by a second invocation of Maven. * @see org.apache.maven.execution.BuildResumptionManager - * @see #isResumptionDataStored() + * @see #canResume() */ - void setResumptionDataStored(); + void setCanResume(); } 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 66b1fc85c55d..ec3d2ad2dbc3 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 @@ -1032,7 +1032,7 @@ private int execute( CliRequest cliRequest ) } List sortedProjects = result.getTopologicallySortedProjects(); - if ( result.isResumptionDataStored() ) + if ( result.canResume() ) { logBuildResumeHint( "mvn -r " ); } From 77e5d53050472ba9ee2eaa48bde3ac7be934d081 Mon Sep 17 00:00:00 2001 From: Maarten Mulders Date: Mon, 25 May 2020 17:26:30 +0200 Subject: [PATCH 17/31] [MNG-5760] Replace Plexus logger with SLF4J --- .../DefaultBuildResumptionManager.java | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java index 4b7831b88ff0..4a3f81944c06 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java +++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java @@ -24,9 +24,9 @@ import org.apache.maven.lifecycle.LifecycleExecutionException; import org.apache.maven.model.Dependency; import org.apache.maven.project.MavenProject; -import org.codehaus.plexus.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import java.io.IOException; @@ -56,9 +56,7 @@ public class DefaultBuildResumptionManager implements BuildResumptionManager private static final String RESUME_FROM_PROPERTY = "resumeFrom"; private static final String EXCLUDED_PROJECTS_PROPERTY = "excludedProjects"; private static final String PROPERTY_DELIMITER = ", "; - - @Inject - private Logger logger; + private static final Logger LOGGER = LoggerFactory.getLogger( DefaultBuildResumptionManager.class ); @Override public boolean persistResumptionData( MavenExecutionResult result, MavenProject rootProject ) @@ -67,7 +65,7 @@ public boolean persistResumptionData( MavenExecutionResult result, MavenProject if ( properties.isEmpty() ) { - logger.debug( "Will not create " + RESUME_PROPERTIES_FILENAME + " file: nothing to resume from" ); + LOGGER.debug( "Will not create {} file: nothing to resume from", RESUME_PROPERTIES_FILENAME ); return false; } @@ -91,7 +89,7 @@ public void removeResumptionData( MavenProject rootProject ) } catch ( IOException e ) { - logger.warn( "Could not delete " + RESUME_PROPERTIES_FILENAME + " file. ", e ); + LOGGER.warn( "Could not delete {} file. ", RESUME_PROPERTIES_FILENAME, e ); } } @@ -127,7 +125,7 @@ Properties determineResumptionProperties( MavenExecutionResult result ) } else { - logger.warn( "Could not create " + RESUME_PROPERTIES_FILENAME + " file: no failed projects found" ); + LOGGER.warn( "Could not create {} file: no failed projects found", RESUME_PROPERTIES_FILENAME ); } return properties; @@ -219,7 +217,7 @@ private boolean writeResumptionFile( MavenProject rootProject, Properties proper } catch ( IOException e ) { - logger.warn( "Could not create " + RESUME_PROPERTIES_FILENAME + " file. ", e ); + LOGGER.warn( "Could not create {} file. ", RESUME_PROPERTIES_FILENAME, e ); return false; } @@ -232,7 +230,7 @@ private Properties loadResumptionFile( Path rootBuildDirectory ) Path path = Paths.get( RESUME_PROPERTIES_FILENAME ).resolve( rootBuildDirectory ); if ( !Files.exists( path ) ) { - logger.warn( "The " + path + " file does not exist. The --resume / -r feature will not work." ); + LOGGER.warn( "The {} file does not exist. The --resume / -r feature will not work.", path ); return properties; } @@ -242,7 +240,7 @@ private Properties loadResumptionFile( Path rootBuildDirectory ) } catch ( IOException e ) { - logger.warn( "Unable to read " + path + ". The --resume / -r feature will not work." ); + LOGGER.warn( "Unable to read {}. The --resume / -r feature will not work.", path ); } return properties; @@ -255,7 +253,7 @@ void applyResumptionProperties( MavenExecutionRequest request, Properties proper { String propertyValue = properties.getProperty( RESUME_FROM_PROPERTY ); request.setResumeFrom( propertyValue ); - logger.info( "Resuming from " + propertyValue + " due to the --resume / -r feature." ); + LOGGER.info( "Resuming from {} due to the --resume / -r feature.", propertyValue ); } if ( properties.containsKey( EXCLUDED_PROJECTS_PROPERTY ) ) @@ -263,7 +261,7 @@ void applyResumptionProperties( MavenExecutionRequest request, Properties proper String propertyValue = properties.getProperty( EXCLUDED_PROJECTS_PROPERTY ); String[] excludedProjects = propertyValue.split( PROPERTY_DELIMITER ); request.getExcludedProjects().addAll( Arrays.asList( excludedProjects ) ); - logger.info( "Additionally excluding projects '" + propertyValue + "' due to the --resume / -r feature." ); + LOGGER.info( "Additionally excluding projects '{}' due to the --resume / -r feature.", propertyValue ); } } From d33702ded93d07a9b0e3a1651c8f1eb38709407c Mon Sep 17 00:00:00 2001 From: Maarten Mulders Date: Mon, 25 May 2020 17:29:50 +0200 Subject: [PATCH 18/31] [MNG-5760] Replace String#format with String concatenation --- .../apache/maven/execution/DefaultBuildResumptionManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java index 4a3f81944c06..9f7c9445cbc4 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java +++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java @@ -156,7 +156,7 @@ private Optional getResumeFrom( MavenExecutionResult result, MavenProjec List allSortedProjects = result.getTopologicallySortedProjects(); if ( !allSortedProjects.get( 0 ).equals( failedProject ) ) { - return Optional.of( String.format( "%s:%s", failedProject.getGroupId(), failedProject.getArtifactId() ) ); + return Optional.of( failedProject.getGroupId() + ":" + failedProject.getArtifactId() ); } return Optional.empty(); @@ -186,7 +186,7 @@ private Optional determineProjectsToSkip( MavenExecutionResult result, L String projectsToSkip = remainingProjects.stream() .filter( project -> result.getBuildSummary( project ) instanceof BuildSuccess ) .filter( project -> hasNoDependencyOnProjects( project, failedProjectsGAList ) ) - .map( project -> String.format( "%s:%s", project.getGroupId(), project.getArtifactId() ) ) + .map( project -> project.getGroupId() + ":" + project.getArtifactId() ) .collect( Collectors.joining( PROPERTY_DELIMITER ) ); if ( !StringUtils.isEmpty( projectsToSkip ) ) From 5a130ea767bf9993d879fcd02397700f9a91de63 Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Fri, 5 Jun 2020 20:44:15 +0200 Subject: [PATCH 19/31] [MNG-5760] Renamed BuildResumptionManager to BuildResumer --- .../java/org/apache/maven/DefaultMaven.java | 8 +++--- ...sumptionManager.java => BuildResumer.java} | 2 +- ...nManager.java => DefaultBuildResumer.java} | 6 ++--- .../maven/execution/MavenExecutionResult.java | 4 +-- .../maven/graph/DefaultGraphBuilder.java | 6 ++--- ...Test.java => DefaultBuildResumerTest.java} | 26 +++++++++---------- .../java/org/apache/maven/cli/MavenCli.java | 8 +++--- 7 files changed, 30 insertions(+), 30 deletions(-) rename maven-core/src/main/java/org/apache/maven/execution/{BuildResumptionManager.java => BuildResumer.java} (98%) rename maven-core/src/main/java/org/apache/maven/execution/{DefaultBuildResumptionManager.java => DefaultBuildResumer.java} (98%) rename maven-core/src/test/java/org/apache/maven/execution/{DefaultBuildResumptionManagerTest.java => DefaultBuildResumerTest.java} (87%) diff --git a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java index ffda19d7b84d..3eb75443813f 100644 --- a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java +++ b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java @@ -36,7 +36,7 @@ import javax.inject.Singleton; import org.apache.maven.artifact.ArtifactUtils; -import org.apache.maven.execution.BuildResumptionManager; +import org.apache.maven.execution.BuildResumer; import org.apache.maven.execution.DefaultMavenExecutionResult; import org.apache.maven.execution.ExecutionEvent; import org.apache.maven.execution.MavenExecutionRequest; @@ -102,7 +102,7 @@ public class DefaultMaven private GraphBuilder graphBuilder; @Inject - private BuildResumptionManager buildResumptionManager; + private BuildResumer buildResumer; @Override public MavenExecutionResult execute( MavenExecutionRequest request ) @@ -326,7 +326,7 @@ private MavenExecutionResult doExecute( MavenExecutionRequest request, MavenSess session.getAllProjects().stream() .filter( MavenProject::isExecutionRoot ) .findFirst() - .ifPresent( buildResumptionManager::removeResumptionData ); + .ifPresent( buildResumer::removeResumptionData ); } } finally @@ -375,7 +375,7 @@ private void saveResumptionDataWhenApplicable( MavenExecutionResult result, Mave .findFirst() .ifPresent( rootProject -> { - if ( buildResumptionManager.persistResumptionData( result, rootProject ) ) + if ( buildResumer.persistResumptionData( result, rootProject ) ) { result.setCanResume(); } diff --git a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java b/maven-core/src/main/java/org/apache/maven/execution/BuildResumer.java similarity index 98% rename from maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java rename to maven-core/src/main/java/org/apache/maven/execution/BuildResumer.java index f3d41aa3634e..be8537142a10 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java +++ b/maven-core/src/main/java/org/apache/maven/execution/BuildResumer.java @@ -28,7 +28,7 @@ * builds of the same project that have the -r command-line flag skip successfully built projects during earlier * invocations of Maven. */ -public interface BuildResumptionManager +public interface BuildResumer { /** * Persists any data needed to resume the build at a later point in time, using a new Maven invocation. This method diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java similarity index 98% rename from maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java rename to maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java index 9f7c9445cbc4..723236505b6f 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java +++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java @@ -45,18 +45,18 @@ import static java.util.Comparator.comparing; /** - * This implementation of {@link BuildResumptionManager} persists information in a properties file. The file is stored + * This implementation of {@link BuildResumer} persists information in a properties file. The file is stored * in the build output directory under the Maven execution root. */ @Named @Singleton -public class DefaultBuildResumptionManager implements BuildResumptionManager +public class DefaultBuildResumer implements BuildResumer { private static final String RESUME_PROPERTIES_FILENAME = "resume.properties"; private static final String RESUME_FROM_PROPERTY = "resumeFrom"; private static final String EXCLUDED_PROJECTS_PROPERTY = "excludedProjects"; private static final String PROPERTY_DELIMITER = ", "; - private static final Logger LOGGER = LoggerFactory.getLogger( DefaultBuildResumptionManager.class ); + private static final Logger LOGGER = LoggerFactory.getLogger( DefaultBuildResumer.class ); @Override public boolean persistResumptionData( MavenExecutionResult result, MavenProject rootProject ) diff --git a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java index 07a1eb87264c..7a86afbfaf34 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java +++ b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java @@ -70,14 +70,14 @@ public interface MavenExecutionResult /** * Indicates whether or not the build could be resumed by a second invocation of Maven. - * @see org.apache.maven.execution.BuildResumptionManager + * @see org.apache.maven.execution.BuildResumer * @return true when it is possible to resume the build, false otherwise. */ boolean canResume(); /** * Indicate that the build could be resumed by a second invocation of Maven. - * @see org.apache.maven.execution.BuildResumptionManager + * @see org.apache.maven.execution.BuildResumer * @see #canResume() */ void setCanResume(); diff --git a/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java b/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java index 091cbaff892b..1d19c036d24b 100644 --- a/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java +++ b/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java @@ -38,7 +38,7 @@ import org.apache.maven.MavenExecutionException; import org.apache.maven.ProjectCycleException; import org.apache.maven.artifact.ArtifactUtils; -import org.apache.maven.execution.BuildResumptionManager; +import org.apache.maven.execution.BuildResumer; import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.MavenSession; import org.apache.maven.execution.ProjectDependencyGraph; @@ -75,7 +75,7 @@ public class DefaultGraphBuilder protected ProjectBuilder projectBuilder; @Inject - private BuildResumptionManager buildResumptionManager; + private BuildResumer buildResumer; @Override public Result build( MavenSession session ) @@ -354,7 +354,7 @@ private void enrichRequestFromResumptionData( List projects, Maven .filter( MavenProject::isExecutionRoot ) .findFirst() .ifPresent( rootProject -> - buildResumptionManager.applyResumptionData( request, rootProject ) ); + buildResumer.applyResumptionData( request, rootProject ) ); } } diff --git a/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumptionManagerTest.java b/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumerTest.java similarity index 87% rename from maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumptionManagerTest.java rename to maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumerTest.java index a0c913b49384..fe1348e08ad8 100644 --- a/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumptionManagerTest.java +++ b/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumerTest.java @@ -40,13 +40,13 @@ import static org.hamcrest.Matchers.*; @RunWith( MockitoJUnitRunner.class ) -public class DefaultBuildResumptionManagerTest +public class DefaultBuildResumerTest { @Mock private Logger logger; @InjectMocks - private DefaultBuildResumptionManager buildResumptionManager; + private DefaultBuildResumer buildResumer; private MavenExecutionResult result; @@ -62,7 +62,7 @@ public void resumeFromGetsDetermined() MavenProject projectB = createFailedMavenProject( "B" ); result.setTopologicallySortedProjects( asList( projectA, projectB ) ); - Properties properties = buildResumptionManager.determineResumptionProperties( result ); + Properties properties = buildResumer.determineResumptionProperties( result ); assertThat( properties.get( "resumeFrom" ), is( "test:B" ) ); } @@ -74,7 +74,7 @@ public void resumeFromIsIgnoredWhenFirstProjectFails() MavenProject projectB = createMavenProject( "B" ); result.setTopologicallySortedProjects( asList( projectA, projectB ) ); - Properties properties = buildResumptionManager.determineResumptionProperties( result ); + Properties properties = buildResumer.determineResumptionProperties( result ); assertThat( properties.containsKey( "resumeFrom" ), is(false) ); } @@ -87,7 +87,7 @@ public void projectsSucceedingAfterFailedProjectsAreExcluded() MavenProject projectC = createSucceededMavenProject( "C" ); result.setTopologicallySortedProjects( asList( projectA, projectB, projectC ) ); - Properties properties = buildResumptionManager.determineResumptionProperties( result ); + Properties properties = buildResumer.determineResumptionProperties( result ); assertThat( properties.get( "excludedProjects" ), is("test:C") ); } @@ -101,7 +101,7 @@ public void projectsDependingOnFailedProjectsAreNotExcluded() projectC.setDependencies( singletonList( toDependency( projectB ) ) ); result.setTopologicallySortedProjects( asList( projectA, projectB, projectC ) ); - Properties properties = buildResumptionManager.determineResumptionProperties( result ); + Properties properties = buildResumer.determineResumptionProperties( result ); assertThat( properties.containsKey( "excludedProjects" ), is(false) ); } @@ -115,7 +115,7 @@ public void projectsFailingAfterAnotherFailedProjectAreNotExcluded() MavenProject projectD = createFailedMavenProject( "D" ); result.setTopologicallySortedProjects( asList( projectA, projectB, projectC, projectD ) ); - Properties properties = buildResumptionManager.determineResumptionProperties( result ); + Properties properties = buildResumer.determineResumptionProperties( result ); assertThat( properties.get( "resumeFrom" ), is("test:B") ); assertThat( properties.get( "excludedProjects" ), is("test:C") ); @@ -129,7 +129,7 @@ public void multipleExcludedProjectsAreCommaSeparated() MavenProject projectC = createSucceededMavenProject( "C" ); result.setTopologicallySortedProjects( asList( projectA, projectB, projectC ) ); - Properties properties = buildResumptionManager.determineResumptionProperties( result ); + Properties properties = buildResumer.determineResumptionProperties( result ); assertThat( properties.get( "excludedProjects" ), is( "test:B, test:C" ) ); } @@ -141,7 +141,7 @@ public void resumeFromPropertyGetsApplied() Properties properties = new Properties(); properties.setProperty( "resumeFrom", ":module-a" ); - buildResumptionManager.applyResumptionProperties( request, properties ); + buildResumer.applyResumptionProperties( request, properties ); assertThat( request.getResumeFrom(), is( ":module-a" ) ); } @@ -154,7 +154,7 @@ public void resumeFromPropertyDoesNotOverrideExistingRequestParameters() Properties properties = new Properties(); properties.setProperty( "resumeFrom", ":module-a" ); - buildResumptionManager.applyResumptionProperties( request, properties ); + buildResumer.applyResumptionProperties( request, properties ); assertThat( request.getResumeFrom(), is( ":module-b" ) ); } @@ -169,7 +169,7 @@ public void excludedProjectsFromPropertyGetsAddedToExistingRequestParameters() Properties properties = new Properties(); properties.setProperty( "excludedProjects", ":module-b, :module-c" ); - buildResumptionManager.applyResumptionProperties( request, properties ); + buildResumer.applyResumptionProperties( request, properties ); assertThat( request.getExcludedProjects(), contains( ":module-a", ":module-b", ":module-c" ) ); } @@ -182,7 +182,7 @@ public void resumeFromSelectorIsSuggestedWithoutGroupId() createMavenProject( "group", "module-b" ) ); MavenProject failedProject = allProjects.get( 0 ); - String selector = buildResumptionManager.getResumeFromSelector( allProjects, failedProject ); + String selector = buildResumer.getResumeFromSelector( allProjects, failedProject ); assertThat( selector, is( ":module-a" ) ); } @@ -195,7 +195,7 @@ public void resumeFromSelectorContainsGroupIdWhenArtifactIdIsNotUnique() createMavenProject( "group-b", "module" ) ); MavenProject failedProject = allProjects.get( 0 ); - String selector = buildResumptionManager.getResumeFromSelector( allProjects, failedProject ); + String selector = buildResumer.getResumeFromSelector( allProjects, failedProject ); assertThat( selector, is( "group-a:module" ) ); } 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 ec3d2ad2dbc3..7778a06fd0a6 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,7 @@ import org.apache.maven.exception.DefaultExceptionHandler; import org.apache.maven.exception.ExceptionHandler; import org.apache.maven.exception.ExceptionSummary; -import org.apache.maven.execution.BuildResumptionManager; +import org.apache.maven.execution.BuildResumer; import org.apache.maven.execution.DefaultMavenExecutionRequest; import org.apache.maven.execution.ExecutionListener; import org.apache.maven.execution.MavenExecutionRequest; @@ -169,7 +169,7 @@ public class MavenCli private Map configurationProcessors; - private BuildResumptionManager buildResumptionManager; + private BuildResumer buildResumer; public MavenCli() { @@ -708,7 +708,7 @@ protected void configure() dispatcher = (DefaultSecDispatcher) container.lookup( SecDispatcher.class, "maven" ); - buildResumptionManager = container.lookup( BuildResumptionManager.class ); + buildResumer = container.lookup( BuildResumer.class ); return container; } @@ -1038,7 +1038,7 @@ private int execute( CliRequest cliRequest ) } else if ( project != null && !project.equals( sortedProjects.get( 0 ) ) ) { - String resumeFromSelector = buildResumptionManager.getResumeFromSelector( sortedProjects, project ); + String resumeFromSelector = buildResumer.getResumeFromSelector( sortedProjects, project ); logBuildResumeHint( "mvn -rf " + resumeFromSelector ); } From 78019225f5088441e0032f9ae3978e0925796961 Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Fri, 5 Jun 2020 20:45:40 +0200 Subject: [PATCH 20/31] [MNG-5760] Removed an unused mock for the plexus Logger, which is a leftover after moving to slf4j. --- .../maven/execution/DefaultBuildResumerTest.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumerTest.java b/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumerTest.java index fe1348e08ad8..d2dd9ee2883e 100644 --- a/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumerTest.java +++ b/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumerTest.java @@ -22,12 +22,9 @@ import org.apache.maven.lifecycle.LifecycleExecutionException; import org.apache.maven.model.Dependency; import org.apache.maven.project.MavenProject; -import org.codehaus.plexus.logging.Logger; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.util.ArrayList; @@ -37,16 +34,13 @@ import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.is; @RunWith( MockitoJUnitRunner.class ) public class DefaultBuildResumerTest { - @Mock - private Logger logger; - - @InjectMocks - private DefaultBuildResumer buildResumer; + private final DefaultBuildResumer buildResumer = new DefaultBuildResumer(); private MavenExecutionResult result; From 5a4a9469b3fe90b776a9c3da54e7ea3341fa7044 Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Fri, 5 Jun 2020 20:47:31 +0200 Subject: [PATCH 21/31] [MNG-5760] Resolving review comment; clearing up persistResumptionData method name. --- maven-core/src/main/java/org/apache/maven/DefaultMaven.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java index 3eb75443813f..a78bf753f9df 100644 --- a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java +++ b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java @@ -318,7 +318,7 @@ private MavenExecutionResult doExecute( MavenExecutionRequest request, MavenSess if ( session.getResult().hasExceptions() ) { addExceptionToResult( result, session.getResult().getExceptions().get( 0 ) ); - saveResumptionDataWhenApplicable( result, session ); + persistResumptionData( result, session ); return result; } else @@ -363,7 +363,7 @@ private void afterSessionEnd( Collection projects, MavenSession se } } - private void saveResumptionDataWhenApplicable( MavenExecutionResult result, MavenSession session ) + private void persistResumptionData( MavenExecutionResult result, MavenSession session ) { boolean hasLifecycleExecutionExceptions = result.getExceptions().stream() .anyMatch( LifecycleExecutionException.class::isInstance ); From 557ec1cbf1366eae10e43492357fef0bd0663ae7 Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Fri, 5 Jun 2020 20:59:56 +0200 Subject: [PATCH 22/31] [MNG-5760] Review comment: Removed Guava's @VisibleForTesting --- .../java/org/apache/maven/execution/DefaultBuildResumer.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java index 723236505b6f..0e0e1658cd2e 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java +++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java @@ -19,7 +19,6 @@ * under the License. */ -import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.StringUtils; import org.apache.maven.lifecycle.LifecycleExecutionException; import org.apache.maven.model.Dependency; @@ -108,7 +107,7 @@ public String getResumeFromSelector( List mavenProjects, MavenProj return ":" + failedProject.getArtifactId(); } - @VisibleForTesting + // This method is made package-private for testing purposes Properties determineResumptionProperties( MavenExecutionResult result ) { Properties properties = new Properties(); @@ -246,7 +245,7 @@ private Properties loadResumptionFile( Path rootBuildDirectory ) return properties; } - @VisibleForTesting + // This method is made package-private for testing purposes void applyResumptionProperties( MavenExecutionRequest request, Properties properties ) { if ( properties.containsKey( RESUME_FROM_PROPERTY ) && StringUtils.isEmpty( request.getResumeFrom() ) ) From 08e3a61c7e5d8ec84d8ff3a85f1ffb50fefc0e2d Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Fri, 5 Jun 2020 21:21:14 +0200 Subject: [PATCH 23/31] [MNG-5760] When something fails while persisting resumption data, throw an exception instead of returning false. --- .../java/org/apache/maven/DefaultMaven.java | 12 +++++-- .../apache/maven/execution/BuildResumer.java | 4 ++- .../BuildResumptionPersistenceException.java | 34 +++++++++++++++++++ .../maven/execution/DefaultBuildResumer.java | 6 ++-- 4 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 maven-core/src/main/java/org/apache/maven/execution/BuildResumptionPersistenceException.java diff --git a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java index a78bf753f9df..15435913c517 100644 --- a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java +++ b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java @@ -37,6 +37,7 @@ import org.apache.maven.artifact.ArtifactUtils; import org.apache.maven.execution.BuildResumer; +import org.apache.maven.execution.BuildResumptionPersistenceException; import org.apache.maven.execution.DefaultMavenExecutionResult; import org.apache.maven.execution.ExecutionEvent; import org.apache.maven.execution.MavenExecutionRequest; @@ -375,9 +376,16 @@ private void persistResumptionData( MavenExecutionResult result, MavenSession se .findFirst() .ifPresent( rootProject -> { - if ( buildResumer.persistResumptionData( result, rootProject ) ) + try { - result.setCanResume(); + if ( buildResumer.persistResumptionData( result, rootProject ) ) + { + result.setCanResume(); + } + } + catch ( BuildResumptionPersistenceException e ) + { + logger.warn( "Could not persist build resumption data", e ); } } ); } diff --git a/maven-core/src/main/java/org/apache/maven/execution/BuildResumer.java b/maven-core/src/main/java/org/apache/maven/execution/BuildResumer.java index be8537142a10..fd6840e7b13c 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/BuildResumer.java +++ b/maven-core/src/main/java/org/apache/maven/execution/BuildResumer.java @@ -37,9 +37,11 @@ public interface BuildResumer * * @param result The result of the current Maven invocation. * @param rootProject The root project that is being built. + * @throws BuildResumptionPersistenceException When an error occurs while persisting data. * @return Whether any data was persisted. */ - boolean persistResumptionData( final MavenExecutionResult result, final MavenProject rootProject ); + boolean persistResumptionData( final MavenExecutionResult result, final MavenProject rootProject ) + throws BuildResumptionPersistenceException; /** * Uses previously stored resumption data to enrich an existing execution request. diff --git a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionPersistenceException.java b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionPersistenceException.java new file mode 100644 index 000000000000..fb9281b9a662 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionPersistenceException.java @@ -0,0 +1,34 @@ +package org.apache.maven.execution; + +/* + * 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.project.MavenProject; + +/** + * This exception will be thrown when something fails while persisting build resumption data. + * @see BuildResumer#persistResumptionData(MavenExecutionResult, MavenProject) + */ +public class BuildResumptionPersistenceException extends Exception +{ + public BuildResumptionPersistenceException( String message, Throwable cause ) + { + super( message, cause ); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java index 0e0e1658cd2e..21d99dec23f9 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java +++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java @@ -59,6 +59,7 @@ public class DefaultBuildResumer implements BuildResumer @Override public boolean persistResumptionData( MavenExecutionResult result, MavenProject rootProject ) + throws BuildResumptionPersistenceException { Properties properties = determineResumptionProperties( result ); @@ -204,6 +205,7 @@ private boolean hasNoDependencyOnProjects( MavenProject project, List Date: Fri, 5 Jun 2020 21:29:17 +0200 Subject: [PATCH 24/31] [MNG-5760] Add a boolean to the setCanResume method, which denotes whether a build can be resumed or not. --- maven-core/src/main/java/org/apache/maven/DefaultMaven.java | 6 ++---- .../apache/maven/execution/DefaultMavenExecutionResult.java | 4 ++-- .../org/apache/maven/execution/MavenExecutionResult.java | 5 +++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java index 15435913c517..5c7c9eaa23c9 100644 --- a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java +++ b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java @@ -378,10 +378,8 @@ private void persistResumptionData( MavenExecutionResult result, MavenSession se { try { - if ( buildResumer.persistResumptionData( result, rootProject ) ) - { - result.setCanResume(); - } + boolean persistenceResult = buildResumer.persistResumptionData( result, rootProject ); + result.setCanResume( persistenceResult ); } catch ( BuildResumptionPersistenceException e ) { diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionResult.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionResult.java index ae87d5cb8dd1..ecddd6608e2a 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionResult.java +++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionResult.java @@ -118,8 +118,8 @@ public boolean canResume() } @Override - public void setCanResume() + public void setCanResume( boolean canResume ) { - this.canResume = true; + this.canResume = canResume; } } diff --git a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java index 7a86afbfaf34..285aab086429 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java +++ b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java @@ -76,9 +76,10 @@ public interface MavenExecutionResult boolean canResume(); /** - * Indicate that the build could be resumed by a second invocation of Maven. + * Indicate that the build can or cannot be resumed by a second invocation of Maven. + * @param canResume true when it is possible to resume the build, false otherwise. * @see org.apache.maven.execution.BuildResumer * @see #canResume() */ - void setCanResume(); + void setCanResume( boolean canResume ); } From 2d5c3803f27ad86457f7edf79719df41feee274b Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Fri, 5 Jun 2020 21:38:02 +0200 Subject: [PATCH 25/31] [MNG-5760] Fixed checkstyle findings --- .../maven/execution/BuildResumptionPersistenceException.java | 4 +--- .../java/org/apache/maven/execution/DefaultBuildResumer.java | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionPersistenceException.java b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionPersistenceException.java index fb9281b9a662..2d2852f45cdd 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionPersistenceException.java +++ b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionPersistenceException.java @@ -19,11 +19,9 @@ * under the License. */ -import org.apache.maven.project.MavenProject; - /** * This exception will be thrown when something fails while persisting build resumption data. - * @see BuildResumer#persistResumptionData(MavenExecutionResult, MavenProject) + * @see BuildResumer#persistResumptionData */ public class BuildResumptionPersistenceException extends Exception { diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java index 21d99dec23f9..b5fb1b626b73 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java +++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java @@ -219,7 +219,7 @@ private boolean writeResumptionFile( MavenProject rootProject, Properties proper catch ( IOException e ) { String message = "Could not create " + RESUME_PROPERTIES_FILENAME + " file."; - throw new BuildResumptionPersistenceException( message, e); + throw new BuildResumptionPersistenceException( message, e ); } return true; From 41faa43e74727837afcad70507680ae14812d236 Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Sat, 6 Jun 2020 10:56:55 +0200 Subject: [PATCH 26/31] [MNG-5760] Moved the -rf helper method, including the tests, back to MavenCli. The BuildResumer is now only focused on the --resume feature. --- .../apache/maven/execution/BuildResumer.java | 19 ---------- .../maven/execution/DefaultBuildResumer.java | 15 -------- .../execution/DefaultBuildResumerTest.java | 33 +--------------- .../java/org/apache/maven/cli/MavenCli.java | 33 +++++++++++++++- .../org/apache/maven/cli/MavenCliTest.java | 38 +++++++++++++++++++ 5 files changed, 71 insertions(+), 67 deletions(-) diff --git a/maven-core/src/main/java/org/apache/maven/execution/BuildResumer.java b/maven-core/src/main/java/org/apache/maven/execution/BuildResumer.java index fd6840e7b13c..8de72cae3775 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/BuildResumer.java +++ b/maven-core/src/main/java/org/apache/maven/execution/BuildResumer.java @@ -21,8 +21,6 @@ import org.apache.maven.project.MavenProject; -import java.util.List; - /** * This class describes most of the logic needed for the --resume / -r feature. Its goal is to ensure newer * builds of the same project that have the -r command-line flag skip successfully built projects during earlier @@ -56,21 +54,4 @@ boolean persistResumptionData( final MavenExecutionResult result, final MavenPro */ void removeResumptionData( final MavenProject rootProject ); - /** - * A helper method to determine the value to resume the build with {@code -rf} taking into account the edge case - * where multiple modules in the reactor have the same artifactId. - *

- * {@code -rf :artifactId} will pick up the first module which matches, but when multiple modules in the reactor - * have the same artifactId, effective failed module might be later in build reactor. - * This means that developer will either have to type groupId or wait for build execution of all modules which - * were fine, but they are still before one which reported errors. - *

Then the returned value is {@code groupId:artifactId} when there is a name clash and - * {@code :artifactId} if there is no conflict. - * - * @param mavenProjects Maven projects which are part of build execution. - * @param failedProject 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( final List mavenProjects, final MavenProject failedProject ); } diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java index b5fb1b626b73..84a6dedf119d 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java +++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java @@ -93,21 +93,6 @@ public void removeResumptionData( MavenProject rootProject ) } } - @Override - public String getResumeFromSelector( List mavenProjects, MavenProject failedProject ) - { - boolean hasOverlappingArtifactId = mavenProjects.stream() - .filter( project -> failedProject.getArtifactId().equals( project.getArtifactId() ) ) - .count() > 1; - - if ( hasOverlappingArtifactId ) - { - return failedProject.getGroupId() + ":" + failedProject.getArtifactId(); - } - - return ":" + failedProject.getArtifactId(); - } - // This method is made package-private for testing purposes Properties determineResumptionProperties( MavenExecutionResult result ) { diff --git a/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumerTest.java b/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumerTest.java index d2dd9ee2883e..0664cb4bb1ad 100644 --- a/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumerTest.java +++ b/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumerTest.java @@ -168,41 +168,10 @@ public void excludedProjectsFromPropertyGetsAddedToExistingRequestParameters() assertThat( request.getExcludedProjects(), contains( ":module-a", ":module-b", ":module-c" ) ); } - @Test - public void resumeFromSelectorIsSuggestedWithoutGroupId() - { - List allProjects = asList( - createMavenProject( "group", "module-a" ), - createMavenProject( "group", "module-b" ) ); - MavenProject failedProject = allProjects.get( 0 ); - - String selector = buildResumer.getResumeFromSelector( allProjects, failedProject ); - - assertThat( selector, is( ":module-a" ) ); - } - - @Test - public void resumeFromSelectorContainsGroupIdWhenArtifactIdIsNotUnique() - { - List allProjects = asList( - createMavenProject( "group-a", "module" ), - createMavenProject( "group-b", "module" ) ); - MavenProject failedProject = allProjects.get( 0 ); - - String selector = buildResumer.getResumeFromSelector( allProjects, failedProject ); - - assertThat( selector, is( "group-a:module" ) ); - } - private MavenProject createMavenProject( String artifactId ) - { - return createMavenProject( "test", artifactId ); - } - - private MavenProject createMavenProject( String groupId, String artifactId ) { MavenProject project = new MavenProject(); - project.setGroupId( groupId ); + project.setGroupId( "test" ); project.setArtifactId( artifactId ); return project; } 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 7778a06fd0a6..5bf6b32bff6f 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 @@ -1038,7 +1038,7 @@ private int execute( CliRequest cliRequest ) } else if ( project != null && !project.equals( sortedProjects.get( 0 ) ) ) { - String resumeFromSelector = buildResumer.getResumeFromSelector( sortedProjects, project ); + String resumeFromSelector = getResumeFromSelector( sortedProjects, project ); logBuildResumeHint( "mvn -rf " + resumeFromSelector ); } @@ -1066,6 +1066,37 @@ private void logBuildResumeHint( String resumeBuildHint ) slf4jLogger.error( buffer().a( " " ).strong( resumeBuildHint ).toString() ); } + /** + * A helper method to determine the value to resume the build with {@code -rf} taking into account the edge case + * where multiple modules in the reactor have the same artifactId. + *

+ * {@code -rf :artifactId} will pick up the first module which matches, but when multiple modules in the reactor + * have the same artifactId, effective failed module might be later in build reactor. + * This means that developer will either have to type groupId or wait for build execution of all modules which + * were fine, but they are still before one which reported errors. + *

Then the returned value is {@code groupId:artifactId} when there is a name clash and + * {@code :artifactId} if there is no conflict. + * 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. + * @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 ) + { + boolean hasOverlappingArtifactId = mavenProjects.stream() + .filter( project -> failedProject.getArtifactId().equals( project.getArtifactId() ) ) + .count() > 1; + + if ( hasOverlappingArtifactId ) + { + return failedProject.getGroupId() + ":" + failedProject.getArtifactId(); + } + + return ":" + failedProject.getArtifactId(); + } + private void logSummary( ExceptionSummary summary, Map references, String indent, boolean showErrors ) { diff --git a/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java b/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java index 173b78c78a1c..b0e536f2db6c 100644 --- a/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java +++ b/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java @@ -19,6 +19,9 @@ * under the License. */ +import static java.util.Arrays.asList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -31,10 +34,12 @@ import java.io.File; import java.util.Collections; +import java.util.List; import org.apache.commons.cli.ParseException; import org.apache.maven.Maven; import org.apache.maven.eventspy.internal.EventSpyDispatcher; +import org.apache.maven.project.MavenProject; import org.apache.maven.shared.utils.logging.MessageUtils; import org.apache.maven.toolchain.building.ToolchainsBuildingRequest; import org.apache.maven.toolchain.building.ToolchainsBuildingResult; @@ -346,4 +351,37 @@ public void configure( final Binder binder ) orderdEventSpyDispatcherMock.verify(eventSpyDispatcherMock, times(1)).onEvent(any(ToolchainsBuildingResult.class)); } + @Test + public void resumeFromSelectorIsSuggestedWithoutGroupId() + { + List allProjects = asList( + createMavenProject( "group", "module-a" ), + createMavenProject( "group", "module-b" ) ); + MavenProject failedProject = allProjects.get( 0 ); + + String selector = cli.getResumeFromSelector( allProjects, failedProject ); + + assertThat( selector, is( ":module-a" ) ); + } + + @Test + public void resumeFromSelectorContainsGroupIdWhenArtifactIdIsNotUnique() + { + List allProjects = asList( + createMavenProject( "group-a", "module" ), + createMavenProject( "group-b", "module" ) ); + MavenProject failedProject = allProjects.get( 0 ); + + String selector = cli.getResumeFromSelector( allProjects, failedProject ); + + assertThat( selector, is( "group-a:module" ) ); + } + + private MavenProject createMavenProject( String groupId, String artifactId ) + { + MavenProject project = new MavenProject(); + project.setGroupId( groupId ); + project.setArtifactId( artifactId ); + return project; + } } From d87dcbf334a070fa1b51a2cde1126b87a21f78dc Mon Sep 17 00:00:00 2001 From: Maarten Mulders Date: Wed, 17 Jun 2020 14:43:41 +0200 Subject: [PATCH 27/31] [MNG-5760] Rename interface and implementation --- .../src/main/java/org/apache/maven/DefaultMaven.java | 8 ++++---- ...ildResumer.java => BuildResumptionDataRepository.java} | 6 +++--- .../execution/BuildResumptionPersistenceException.java | 2 +- ...mer.java => DefaultBuildResumptionDataRepository.java} | 6 +++--- .../org/apache/maven/execution/MavenExecutionResult.java | 4 ++-- .../java/org/apache/maven/graph/DefaultGraphBuilder.java | 6 +++--- ...java => DefaultBuildResumptionDataRepositoryTest.java} | 4 ++-- .../src/main/java/org/apache/maven/cli/MavenCli.java | 6 +++--- 8 files changed, 21 insertions(+), 21 deletions(-) rename maven-core/src/main/java/org/apache/maven/execution/{BuildResumer.java => BuildResumptionDataRepository.java} (88%) rename maven-core/src/main/java/org/apache/maven/execution/{DefaultBuildResumer.java => DefaultBuildResumptionDataRepository.java} (97%) rename maven-core/src/test/java/org/apache/maven/execution/{DefaultBuildResumerTest.java => DefaultBuildResumptionDataRepositoryTest.java} (97%) diff --git a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java index 5c7c9eaa23c9..ad5aad7f207d 100644 --- a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java +++ b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java @@ -36,7 +36,7 @@ import javax.inject.Singleton; import org.apache.maven.artifact.ArtifactUtils; -import org.apache.maven.execution.BuildResumer; +import org.apache.maven.execution.BuildResumptionDataRepository; import org.apache.maven.execution.BuildResumptionPersistenceException; import org.apache.maven.execution.DefaultMavenExecutionResult; import org.apache.maven.execution.ExecutionEvent; @@ -103,7 +103,7 @@ public class DefaultMaven private GraphBuilder graphBuilder; @Inject - private BuildResumer buildResumer; + private BuildResumptionDataRepository buildResumptionDataRepository; @Override public MavenExecutionResult execute( MavenExecutionRequest request ) @@ -327,7 +327,7 @@ private MavenExecutionResult doExecute( MavenExecutionRequest request, MavenSess session.getAllProjects().stream() .filter( MavenProject::isExecutionRoot ) .findFirst() - .ifPresent( buildResumer::removeResumptionData ); + .ifPresent( buildResumptionDataRepository::removeResumptionData ); } } finally @@ -378,7 +378,7 @@ private void persistResumptionData( MavenExecutionResult result, MavenSession se { try { - boolean persistenceResult = buildResumer.persistResumptionData( result, rootProject ); + boolean persistenceResult = buildResumptionDataRepository.persistResumptionData( result, rootProject ); result.setCanResume( persistenceResult ); } catch ( BuildResumptionPersistenceException e ) diff --git a/maven-core/src/main/java/org/apache/maven/execution/BuildResumer.java b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionDataRepository.java similarity index 88% rename from maven-core/src/main/java/org/apache/maven/execution/BuildResumer.java rename to maven-core/src/main/java/org/apache/maven/execution/BuildResumptionDataRepository.java index 8de72cae3775..8733819e91ee 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/BuildResumer.java +++ b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionDataRepository.java @@ -22,11 +22,11 @@ import org.apache.maven.project.MavenProject; /** - * This class describes most of the logic needed for the --resume / -r feature. Its goal is to ensure newer - * builds of the same project that have the -r command-line flag skip successfully built projects during earlier + * Instances of this interface retrieve and store data for the --resume / -r feature. This data is used to ensure newer + * builds of the same project, that have the -r command-line flag, skip successfully built projects during earlier * invocations of Maven. */ -public interface BuildResumer +public interface BuildResumptionDataRepository { /** * Persists any data needed to resume the build at a later point in time, using a new Maven invocation. This method diff --git a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionPersistenceException.java b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionPersistenceException.java index 2d2852f45cdd..1f9e8026a808 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionPersistenceException.java +++ b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionPersistenceException.java @@ -21,7 +21,7 @@ /** * This exception will be thrown when something fails while persisting build resumption data. - * @see BuildResumer#persistResumptionData + * @see BuildResumptionDataRepository#persistResumptionData */ public class BuildResumptionPersistenceException extends Exception { diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionDataRepository.java similarity index 97% rename from maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java rename to maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionDataRepository.java index 84a6dedf119d..e6815cec47a9 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumer.java +++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionDataRepository.java @@ -44,18 +44,18 @@ import static java.util.Comparator.comparing; /** - * This implementation of {@link BuildResumer} persists information in a properties file. The file is stored + * This implementation of {@link BuildResumptionDataRepository} persists information in a properties file. The file is stored * in the build output directory under the Maven execution root. */ @Named @Singleton -public class DefaultBuildResumer implements BuildResumer +public class DefaultBuildResumptionDataRepository implements BuildResumptionDataRepository { private static final String RESUME_PROPERTIES_FILENAME = "resume.properties"; private static final String RESUME_FROM_PROPERTY = "resumeFrom"; private static final String EXCLUDED_PROJECTS_PROPERTY = "excludedProjects"; private static final String PROPERTY_DELIMITER = ", "; - private static final Logger LOGGER = LoggerFactory.getLogger( DefaultBuildResumer.class ); + private static final Logger LOGGER = LoggerFactory.getLogger( DefaultBuildResumptionDataRepository.class ); @Override public boolean persistResumptionData( MavenExecutionResult result, MavenProject rootProject ) diff --git a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java index 285aab086429..8a099bb8b0c5 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java +++ b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionResult.java @@ -70,7 +70,7 @@ public interface MavenExecutionResult /** * Indicates whether or not the build could be resumed by a second invocation of Maven. - * @see org.apache.maven.execution.BuildResumer + * @see BuildResumptionDataRepository * @return true when it is possible to resume the build, false otherwise. */ boolean canResume(); @@ -78,7 +78,7 @@ public interface MavenExecutionResult /** * Indicate that the build can or cannot be resumed by a second invocation of Maven. * @param canResume true when it is possible to resume the build, false otherwise. - * @see org.apache.maven.execution.BuildResumer + * @see BuildResumptionDataRepository * @see #canResume() */ void setCanResume( boolean canResume ); diff --git a/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java b/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java index 1d19c036d24b..07cb80ee6fbb 100644 --- a/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java +++ b/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java @@ -38,7 +38,7 @@ import org.apache.maven.MavenExecutionException; import org.apache.maven.ProjectCycleException; import org.apache.maven.artifact.ArtifactUtils; -import org.apache.maven.execution.BuildResumer; +import org.apache.maven.execution.BuildResumptionDataRepository; import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.MavenSession; import org.apache.maven.execution.ProjectDependencyGraph; @@ -75,7 +75,7 @@ public class DefaultGraphBuilder protected ProjectBuilder projectBuilder; @Inject - private BuildResumer buildResumer; + private BuildResumptionDataRepository buildResumptionDataRepository; @Override public Result build( MavenSession session ) @@ -354,7 +354,7 @@ private void enrichRequestFromResumptionData( List projects, Maven .filter( MavenProject::isExecutionRoot ) .findFirst() .ifPresent( rootProject -> - buildResumer.applyResumptionData( request, rootProject ) ); + buildResumptionDataRepository.applyResumptionData( request, rootProject ) ); } } diff --git a/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumerTest.java b/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumptionDataRepositoryTest.java similarity index 97% rename from maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumerTest.java rename to maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumptionDataRepositoryTest.java index 0664cb4bb1ad..279fb1186afd 100644 --- a/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumerTest.java +++ b/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumptionDataRepositoryTest.java @@ -38,9 +38,9 @@ import static org.hamcrest.Matchers.is; @RunWith( MockitoJUnitRunner.class ) -public class DefaultBuildResumerTest +public class DefaultBuildResumptionDataRepositoryTest { - private final DefaultBuildResumer buildResumer = new DefaultBuildResumer(); + private final DefaultBuildResumptionDataRepository buildResumer = new DefaultBuildResumptionDataRepository(); private MavenExecutionResult result; 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 5bf6b32bff6f..4136a1ea5f81 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,7 @@ import org.apache.maven.exception.DefaultExceptionHandler; import org.apache.maven.exception.ExceptionHandler; import org.apache.maven.exception.ExceptionSummary; -import org.apache.maven.execution.BuildResumer; +import org.apache.maven.execution.BuildResumptionDataRepository; import org.apache.maven.execution.DefaultMavenExecutionRequest; import org.apache.maven.execution.ExecutionListener; import org.apache.maven.execution.MavenExecutionRequest; @@ -169,7 +169,7 @@ public class MavenCli private Map configurationProcessors; - private BuildResumer buildResumer; + private BuildResumptionDataRepository buildResumptionDataRepository; public MavenCli() { @@ -708,7 +708,7 @@ protected void configure() dispatcher = (DefaultSecDispatcher) container.lookup( SecDispatcher.class, "maven" ); - buildResumer = container.lookup( BuildResumer.class ); + buildResumptionDataRepository = container.lookup( BuildResumptionDataRepository.class ); return container; } From 4e35d21d023782015d6b889058ced29d45e2b259 Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Wed, 17 Jun 2020 15:00:22 +0200 Subject: [PATCH 28/31] [MNG-5760] Refactored `#determineProjectsToSkip` to return a list instead of an optional with a comma separated list --- .../DefaultBuildResumptionDataRepository.java | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) 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 e6815cec47a9..7e647d74bdb1 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 @@ -103,10 +103,13 @@ Properties determineResumptionProperties( MavenExecutionResult result ) { MavenProject resumeFromProject = failedProjects.get( 0 ); Optional resumeFrom = getResumeFrom( result, resumeFromProject ); - Optional projectsToSkip = determineProjectsToSkip( result, failedProjects, resumeFromProject ); + List projectsToSkip = determineProjectsToSkip( result, failedProjects, resumeFromProject ); resumeFrom.ifPresent( value -> properties.setProperty( RESUME_FROM_PROPERTY, value ) ); - projectsToSkip.ifPresent( value -> properties.setProperty( EXCLUDED_PROJECTS_PROPERTY, value ) ); + if ( !projectsToSkip.isEmpty() ) { + String excludedProjects = String.join( PROPERTY_DELIMITER, projectsToSkip ); + properties.setProperty( EXCLUDED_PROJECTS_PROPERTY, excludedProjects ); + } } else { @@ -154,11 +157,10 @@ private Optional getResumeFrom( MavenExecutionResult result, MavenProjec * @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. - * @return An optional containing a comma separated list of projects which can be skipped, - * or an empty optional if no projects can be skipped. + * @return A list of projects which can be skipped in a later build. */ - private Optional determineProjectsToSkip( MavenExecutionResult result, List failedProjects, - MavenProject resumeFromProject ) + private List determineProjectsToSkip( MavenExecutionResult result, List failedProjects, + MavenProject resumeFromProject ) { List allProjects = result.getTopologicallySortedProjects(); int resumeFromProjectIndex = allProjects.indexOf( resumeFromProject ); @@ -168,18 +170,11 @@ private Optional determineProjectsToSkip( MavenExecutionResult result, L .map( GroupArtifactPair::new ) .collect( Collectors.toList() ); - String projectsToSkip = remainingProjects.stream() + return remainingProjects.stream() .filter( project -> result.getBuildSummary( project ) instanceof BuildSuccess ) .filter( project -> hasNoDependencyOnProjects( project, failedProjectsGAList ) ) .map( project -> project.getGroupId() + ":" + project.getArtifactId() ) - .collect( Collectors.joining( PROPERTY_DELIMITER ) ); - - if ( !StringUtils.isEmpty( projectsToSkip ) ) - { - return Optional.of( projectsToSkip ); - } - - return Optional.empty(); + .collect( Collectors.toList() ); } private boolean hasNoDependencyOnProjects( MavenProject project, List projectsGAs ) From 9a43e1a10b3353e47df9446ae885856ca5cf49d0 Mon Sep 17 00:00:00 2001 From: Maarten Mulders Date: Wed, 17 Jun 2020 20:24:28 +0200 Subject: [PATCH 29/31] [MNG-5760] Split off BuildResumptionAnalyzer from BuildResumptionDataRepository --- .../java/org/apache/maven/DefaultMaven.java | 32 +-- .../execution/BuildResumptionAnalyzer.java | 36 ++++ .../maven/execution/BuildResumptionData.java | 55 +++++ .../BuildResumptionDataRepository.java | 4 +- .../DefaultBuildResumptionAnalyzer.java | 162 +++++++++++++++ .../DefaultBuildResumptionDataRepository.java | 190 +++--------------- .../DefaultBuildResumptionAnalyzerTest.java | 150 ++++++++++++++ ...aultBuildResumptionDataRepositoryTest.java | 132 +----------- 8 files changed, 453 insertions(+), 308 deletions(-) create mode 100644 maven-core/src/main/java/org/apache/maven/execution/BuildResumptionAnalyzer.java create mode 100644 maven-core/src/main/java/org/apache/maven/execution/BuildResumptionData.java create mode 100644 maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionAnalyzer.java create mode 100644 maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumptionAnalyzerTest.java diff --git a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java index ad5aad7f207d..167cb7b1f5f8 100644 --- a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java +++ b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java @@ -36,6 +36,7 @@ import javax.inject.Singleton; import org.apache.maven.artifact.ArtifactUtils; +import org.apache.maven.execution.BuildResumptionAnalyzer; import org.apache.maven.execution.BuildResumptionDataRepository; import org.apache.maven.execution.BuildResumptionPersistenceException; import org.apache.maven.execution.DefaultMavenExecutionResult; @@ -102,6 +103,9 @@ public class DefaultMaven @Named( GraphBuilder.HINT ) private GraphBuilder graphBuilder; + @Inject + private BuildResumptionAnalyzer buildResumptionAnalyzer; + @Inject private BuildResumptionDataRepository buildResumptionDataRepository; @@ -371,21 +375,23 @@ private void persistResumptionData( MavenExecutionResult result, MavenSession se if ( hasLifecycleExecutionExceptions ) { - session.getAllProjects().stream() + MavenProject rootProject = session.getAllProjects().stream() .filter( MavenProject::isExecutionRoot ) .findFirst() - .ifPresent( rootProject -> - { - try - { - boolean persistenceResult = buildResumptionDataRepository.persistResumptionData( result, rootProject ); - result.setCanResume( persistenceResult ); - } - catch ( BuildResumptionPersistenceException e ) - { - logger.warn( "Could not persist build resumption data", e ); - } - } ); + .orElseThrow( () -> new IllegalStateException( "No project in the session is execution root" ) ); + + buildResumptionAnalyzer.determineBuildResumptionData( result ).ifPresent( resumption -> + { + try + { + boolean canResume = buildResumptionDataRepository.persistResumptionData( rootProject, resumption ); + result.setCanResume( canResume ); + } + catch ( BuildResumptionPersistenceException e ) + { + logger.warn( "Could not persist build resumption data", e ); + } + } ); } } diff --git a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionAnalyzer.java b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionAnalyzer.java new file mode 100644 index 000000000000..1778946b001a --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionAnalyzer.java @@ -0,0 +1,36 @@ +package org.apache.maven.execution; + +/* + * 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.util.Optional; + +/** + * Instances of this class are responsible for determining whether it makes sense to "resume" a build (i.e., using + * the {@code --resume} flag. + */ +public interface BuildResumptionAnalyzer +{ + /** + * Construct an instance of {@link BuildResumptionData} based on the outcome of the current Maven build. + * @param result Outcome of the current Maven build. + * @return A {@link BuildResumptionData} instance or {@link Optional#empty()} if resuming the build is not possible. + */ + Optional determineBuildResumptionData( final MavenExecutionResult result ); +} 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 new file mode 100644 index 000000000000..9330929ee611 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionData.java @@ -0,0 +1,55 @@ +package org.apache.maven.execution; + +/* + * 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.util.Collections; +import java.util.List; + +/** + * This class holds the information required to enable resuming a Maven build with {@code --resume}. + */ +public class BuildResumptionData +{ + /** + * The project where the next build could resume from. + */ + private final String resumeFrom; + + /** + * List of projects to skip if the build would be resumed from {@link #resumeFrom}. + */ + private final List projectsToSkip; + + public BuildResumptionData ( final String resumeFrom, final List projectsToSkip ) + { + this.resumeFrom = resumeFrom; + this.projectsToSkip = projectsToSkip; + } + + public String getResumeFrom() + { + return this.resumeFrom; + } + + public List getProjectsToSkip() + { + return this.projectsToSkip; + } +} diff --git a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionDataRepository.java b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionDataRepository.java index 8733819e91ee..b8a3b222a6d4 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionDataRepository.java +++ b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionDataRepository.java @@ -33,12 +33,12 @@ public interface BuildResumptionDataRepository * may also decide it is not needed or meaningful to persist such data, and return false to indicate * so. * - * @param result The result of the current Maven invocation. * @param rootProject The root project that is being built. + * @param buildResumptionData Information needed to resume the build. * @throws BuildResumptionPersistenceException When an error occurs while persisting data. * @return Whether any data was persisted. */ - boolean persistResumptionData( final MavenExecutionResult result, final MavenProject rootProject ) + boolean persistResumptionData( final MavenProject rootProject, final BuildResumptionData buildResumptionData ) throws BuildResumptionPersistenceException; /** 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 new file mode 100644 index 000000000000..3d100dc89116 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionAnalyzer.java @@ -0,0 +1,162 @@ +package org.apache.maven.execution; + +/* + * 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.lifecycle.LifecycleExecutionException; +import org.apache.maven.model.Dependency; +import org.apache.maven.project.MavenProject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Named; +import javax.inject.Singleton; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import static java.util.Comparator.comparing; + +/** + * Default implementation of {@link BuildResumptionAnalyzer}. + */ +@Named +@Singleton +public class DefaultBuildResumptionAnalyzer implements BuildResumptionAnalyzer +{ + private static final Logger LOGGER = LoggerFactory.getLogger( DefaultBuildResumptionAnalyzer.class ); + + @Override + public Optional determineBuildResumptionData( final MavenExecutionResult result ) + { + final List failedProjects = getFailedProjectsInOrder( result ); + + if ( failedProjects.isEmpty() ) + { + LOGGER.info( "No failed projects found, resuming the build would not make sense." ); + return Optional.empty(); + } + + final MavenProject resumeFromProject = failedProjects.get( 0 ); + + if ( isFailedProjectFirstInBuild( result, resumeFromProject ) ) + { + LOGGER.info( "The first module in the build failed, resuming the build would not make sense." ); + return Optional.empty(); + } + + final String resumeFromSelector = resumeFromProject.getGroupId() + ":" + resumeFromProject.getArtifactId(); + final List projectsToSkip = determineProjectsToSkip( result, failedProjects, resumeFromProject ); + + return Optional.of( new BuildResumptionData( resumeFromSelector, projectsToSkip ) ); + } + + private boolean isFailedProjectFirstInBuild( final MavenExecutionResult result, final MavenProject failedProject ) + { + final List sortedProjects = result.getTopologicallySortedProjects(); + return sortedProjects.indexOf( failedProject ) == 0; + } + + private List getFailedProjectsInOrder( MavenExecutionResult result ) + { + List sortedProjects = result.getTopologicallySortedProjects(); + + return result.getExceptions().stream() + .filter( LifecycleExecutionException.class::isInstance ) + .map( LifecycleExecutionException.class::cast ) + .map( LifecycleExecutionException::getProject ) + .sorted( comparing( sortedProjects::indexOf ) ) + .collect( Collectors.toList() ); + } + + /** + * Projects after the first failed project could have succeeded by using -T or --fail-at-end. + * These projects can be skipped from later builds. + * 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. + * @return A list of projects which can be skipped in a later build. + */ + private List determineProjectsToSkip( MavenExecutionResult result, + List failedProjects, + MavenProject resumeFromProject ) + { + List allProjects = result.getTopologicallySortedProjects(); + int resumeFromProjectIndex = allProjects.indexOf( resumeFromProject ); + List remainingProjects = allProjects.subList( resumeFromProjectIndex + 1, allProjects.size() ); + + List failedProjectsGAList = failedProjects.stream() + .map( GroupArtifactPair::new ) + .collect( Collectors.toList() ); + + return remainingProjects.stream() + .filter( project -> result.getBuildSummary( project ) instanceof BuildSuccess ) + .filter( project -> hasNoDependencyOnProjects( project, failedProjectsGAList ) ) + .map( project -> project.getGroupId() + ":" + project.getArtifactId() ) + .collect( Collectors.toList() ); + } + + private boolean hasNoDependencyOnProjects( MavenProject project, List projectsGAs ) + { + return project.getDependencies().stream() + .map( GroupArtifactPair::new ) + .noneMatch( projectsGAs::contains ); + } + + private static class GroupArtifactPair + { + private final String groupId; + private final String artifactId; + + GroupArtifactPair( MavenProject project ) + { + this.groupId = project.getGroupId(); + this.artifactId = project.getArtifactId(); + } + + GroupArtifactPair( Dependency dependency ) + { + this.groupId = dependency.getGroupId(); + this.artifactId = dependency.getArtifactId(); + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + GroupArtifactPair that = (GroupArtifactPair) o; + return Objects.equals( groupId, that.groupId ) && Objects.equals( artifactId, that.artifactId ); + } + + @Override + public int hashCode() + { + return Objects.hash( groupId, artifactId ); + } + } +} 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 7e647d74bdb1..e7965b3c45d9 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 @@ -20,8 +20,6 @@ */ import org.apache.commons.lang3.StringUtils; -import org.apache.maven.lifecycle.LifecycleExecutionException; -import org.apache.maven.model.Dependency; import org.apache.maven.project.MavenProject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,17 +33,11 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.Optional; import java.util.Properties; -import java.util.stream.Collectors; - -import static java.util.Comparator.comparing; /** - * This implementation of {@link BuildResumptionDataRepository} persists information in a properties file. The file is stored - * in the build output directory under the Maven execution root. + * This implementation of {@link BuildResumptionDataRepository} persists information in a properties file. The file is + * stored in the build output directory under the Maven execution root. */ @Named @Singleton @@ -58,151 +50,58 @@ public class DefaultBuildResumptionDataRepository implements BuildResumptionData private static final Logger LOGGER = LoggerFactory.getLogger( DefaultBuildResumptionDataRepository.class ); @Override - public boolean persistResumptionData( MavenExecutionResult result, MavenProject rootProject ) + public boolean persistResumptionData( MavenProject rootProject, BuildResumptionData buildResumptionData ) throws BuildResumptionPersistenceException { - Properties properties = determineResumptionProperties( result ); - - if ( properties.isEmpty() ) - { - LOGGER.debug( "Will not create {} file: nothing to resume from", RESUME_PROPERTIES_FILENAME ); - return false; - } - - return writeResumptionFile( rootProject, properties ); - } - - @Override - public void applyResumptionData( MavenExecutionRequest request, MavenProject rootProject ) - { - Properties properties = loadResumptionFile( Paths.get( rootProject.getBuild().getDirectory() ) ); - applyResumptionProperties( request, properties ); - } + Properties properties = convertToProperties( buildResumptionData ); - @Override - public void removeResumptionData( MavenProject rootProject ) - { Path resumeProperties = Paths.get( rootProject.getBuild().getDirectory(), RESUME_PROPERTIES_FILENAME ); try { - Files.deleteIfExists( resumeProperties ); + Files.createDirectories( resumeProperties.getParent() ); + try ( Writer writer = Files.newBufferedWriter( resumeProperties ) ) + { + properties.store( writer, null ); + } } catch ( IOException e ) { - LOGGER.warn( "Could not delete {} file. ", RESUME_PROPERTIES_FILENAME, e ); + String message = "Could not create " + RESUME_PROPERTIES_FILENAME + " file."; + throw new BuildResumptionPersistenceException( message, e ); } + + return true; } - // This method is made package-private for testing purposes - Properties determineResumptionProperties( MavenExecutionResult result ) + private Properties convertToProperties( final BuildResumptionData buildResumptionData ) { Properties properties = new Properties(); - - List failedProjects = getFailedProjectsInOrder( result ); - if ( !failedProjects.isEmpty() ) - { - MavenProject resumeFromProject = failedProjects.get( 0 ); - Optional resumeFrom = getResumeFrom( result, resumeFromProject ); - List projectsToSkip = determineProjectsToSkip( result, failedProjects, resumeFromProject ); - - resumeFrom.ifPresent( value -> properties.setProperty( RESUME_FROM_PROPERTY, value ) ); - if ( !projectsToSkip.isEmpty() ) { - String excludedProjects = String.join( PROPERTY_DELIMITER, projectsToSkip ); - properties.setProperty( EXCLUDED_PROJECTS_PROPERTY, excludedProjects ); - } - } - else - { - LOGGER.warn( "Could not create {} file: no failed projects found", RESUME_PROPERTIES_FILENAME ); - } + properties.setProperty( RESUME_FROM_PROPERTY, buildResumptionData.getResumeFrom() ); + String excludedProjects = String.join( PROPERTY_DELIMITER, buildResumptionData.getProjectsToSkip() ); + properties.setProperty( EXCLUDED_PROJECTS_PROPERTY, excludedProjects ); return properties; } - private List getFailedProjectsInOrder( MavenExecutionResult result ) - { - List sortedProjects = result.getTopologicallySortedProjects(); - - return result.getExceptions().stream() - .filter( LifecycleExecutionException.class::isInstance ) - .map( LifecycleExecutionException.class::cast ) - .map( LifecycleExecutionException::getProject ) - .sorted( comparing( sortedProjects::indexOf ) ) - .collect( Collectors.toList() ); - } - - /** - * Determine the project where the next build can be resumed from. - * If the failed project is the first project of the build, - * it does not make sense to use --resume-from, so the result will be empty. - * @param result The result of the Maven build. - * @param failedProject The first failed project of the build. - * @return An optional containing the resume-from suggestion. - */ - private Optional getResumeFrom( MavenExecutionResult result, MavenProject failedProject ) - { - List allSortedProjects = result.getTopologicallySortedProjects(); - if ( !allSortedProjects.get( 0 ).equals( failedProject ) ) - { - return Optional.of( failedProject.getGroupId() + ":" + failedProject.getArtifactId() ); - } - - return Optional.empty(); - } - - /** - * Projects after the first failed project could have succeeded by using -T or --fail-at-end. - * These projects can be skipped from later builds. - * 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. - * @return A list of projects which can be skipped in a later build. - */ - private List determineProjectsToSkip( MavenExecutionResult result, List failedProjects, - MavenProject resumeFromProject ) - { - List allProjects = result.getTopologicallySortedProjects(); - int resumeFromProjectIndex = allProjects.indexOf( resumeFromProject ); - List remainingProjects = allProjects.subList( resumeFromProjectIndex + 1, allProjects.size() ); - - List failedProjectsGAList = failedProjects.stream() - .map( GroupArtifactPair::new ) - .collect( Collectors.toList() ); - - return remainingProjects.stream() - .filter( project -> result.getBuildSummary( project ) instanceof BuildSuccess ) - .filter( project -> hasNoDependencyOnProjects( project, failedProjectsGAList ) ) - .map( project -> project.getGroupId() + ":" + project.getArtifactId() ) - .collect( Collectors.toList() ); - } - - private boolean hasNoDependencyOnProjects( MavenProject project, List projectsGAs ) + @Override + public void applyResumptionData( MavenExecutionRequest request, MavenProject rootProject ) { - return project.getDependencies().stream() - .map( GroupArtifactPair::new ) - .noneMatch( projectsGAs::contains ); + Properties properties = loadResumptionFile( Paths.get( rootProject.getBuild().getDirectory() ) ); + applyResumptionProperties( request, properties ); } - private boolean writeResumptionFile( MavenProject rootProject, Properties properties ) - throws BuildResumptionPersistenceException + @Override + public void removeResumptionData( MavenProject rootProject ) { Path resumeProperties = Paths.get( rootProject.getBuild().getDirectory(), RESUME_PROPERTIES_FILENAME ); try { - Files.createDirectories( resumeProperties.getParent() ); - try ( Writer writer = Files.newBufferedWriter( resumeProperties ) ) - { - properties.store( writer, null ); - } + Files.deleteIfExists( resumeProperties ); } catch ( IOException e ) { - String message = "Could not create " + RESUME_PROPERTIES_FILENAME + " file."; - throw new BuildResumptionPersistenceException( message, e ); + LOGGER.warn( "Could not delete {} file. ", RESUME_PROPERTIES_FILENAME, e ); } - - return true; } private Properties loadResumptionFile( Path rootBuildDirectory ) @@ -245,43 +144,4 @@ void applyResumptionProperties( MavenExecutionRequest request, Properties proper LOGGER.info( "Additionally excluding projects '{}' due to the --resume / -r feature.", propertyValue ); } } - - private static class GroupArtifactPair - { - private final String groupId; - private final String artifactId; - - GroupArtifactPair( MavenProject project ) - { - this.groupId = project.getGroupId(); - this.artifactId = project.getArtifactId(); - } - - GroupArtifactPair( Dependency dependency ) - { - this.groupId = dependency.getGroupId(); - this.artifactId = dependency.getArtifactId(); - } - - @Override - public boolean equals( Object o ) - { - if ( this == o ) - { - return true; - } - if ( o == null || getClass() != o.getClass() ) - { - return false; - } - GroupArtifactPair that = (GroupArtifactPair) o; - return Objects.equals( groupId, that.groupId ) && Objects.equals( artifactId, that.artifactId ); - } - - @Override - public int hashCode() - { - return Objects.hash( groupId, artifactId ); - } - } } 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 new file mode 100644 index 000000000000..59a8e63c1403 --- /dev/null +++ b/maven-core/src/test/java/org/apache/maven/execution/DefaultBuildResumptionAnalyzerTest.java @@ -0,0 +1,150 @@ +package org.apache.maven.execution; + +/* + * 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.lifecycle.LifecycleExecutionException; +import org.apache.maven.model.Dependency; +import org.apache.maven.project.MavenProject; +import org.junit.Before; +import org.junit.Test; + +import java.util.Optional; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.is; + +public class DefaultBuildResumptionAnalyzerTest +{ + private final DefaultBuildResumptionAnalyzer analyzer = new DefaultBuildResumptionAnalyzer(); + + private MavenExecutionResult executionResult; + + @Before + public void before() { + executionResult = new DefaultMavenExecutionResult(); + } + + @Test + public void resumeFromGetsDetermined() + { + MavenProject projectA = createSucceededMavenProject( "A" ); + MavenProject projectB = createFailedMavenProject( "B" ); + executionResult.setTopologicallySortedProjects( asList( projectA, projectB ) ); + + Optional result = analyzer.determineBuildResumptionData( executionResult ); + + assertThat( result.isPresent(), is( true ) ); + assertThat( result.get().getResumeFrom(), is( "test:B" ) ); + } + + @Test + public void resumeFromIsIgnoredWhenFirstProjectFails() + { + MavenProject projectA = createFailedMavenProject( "A" ); + MavenProject projectB = createMavenProject( "B" ); + executionResult.setTopologicallySortedProjects( asList( projectA, projectB ) ); + + Optional result = analyzer.determineBuildResumptionData( executionResult ); + + assertThat( result.isPresent(), is( false ) ); + } + + @Test + public void projectsSucceedingAfterFailedProjectsAreExcluded() + { + MavenProject projectA = createSucceededMavenProject( "A" ); + MavenProject projectB = createFailedMavenProject( "B" ); + MavenProject projectC = createSucceededMavenProject( "C" ); + executionResult.setTopologicallySortedProjects( asList( projectA, projectB, projectC ) ); + + Optional result = analyzer.determineBuildResumptionData( executionResult ); + + assertThat( result.isPresent(), is( true ) ); + assertThat( result.get().getProjectsToSkip(), contains( "test:C" ) ); + } + + @Test + public void projectsDependingOnFailedProjectsAreNotExcluded() + { + MavenProject projectA = createSucceededMavenProject( "A" ); + MavenProject projectB = createFailedMavenProject( "B" ); + MavenProject projectC = createSucceededMavenProject( "C" ); + projectC.setDependencies( singletonList( toDependency( projectB ) ) ); + executionResult.setTopologicallySortedProjects( asList( projectA, projectB, projectC ) ); + + Optional result = analyzer.determineBuildResumptionData( executionResult ); + + assertThat( result.isPresent(), is( true ) ); + assertThat( result.get().getProjectsToSkip().isEmpty(), is( true ) ); + } + + @Test + public void projectsFailingAfterAnotherFailedProjectAreNotExcluded() + { + MavenProject projectA = createSucceededMavenProject( "A" ); + MavenProject projectB = createFailedMavenProject( "B" ); + MavenProject projectC = createSucceededMavenProject( "C" ); + MavenProject projectD = createFailedMavenProject( "D" ); + executionResult.setTopologicallySortedProjects( asList( projectA, projectB, projectC, projectD ) ); + + Optional result = analyzer.determineBuildResumptionData( executionResult ); + + assertThat( result.isPresent(), is( true ) ); + assertThat( result.get().getResumeFrom(), is( "test:B" ) ); + assertThat( result.get().getProjectsToSkip(), contains( "test:C" ) ); + assertThat( result.get().getProjectsToSkip(), not( contains( "test:D" ) ) ); + } + + private MavenProject createMavenProject( String artifactId ) + { + MavenProject project = new MavenProject(); + project.setGroupId( "test" ); + project.setArtifactId( artifactId ); + return project; + } + + private Dependency toDependency(MavenProject mavenProject ) + { + Dependency dependency = new Dependency(); + dependency.setGroupId( mavenProject.getGroupId() ); + dependency.setArtifactId( mavenProject.getArtifactId() ); + dependency.setVersion( mavenProject.getVersion() ); + return dependency; + } + + private MavenProject createSucceededMavenProject( String artifactId ) + { + MavenProject project = createMavenProject( artifactId ); + executionResult.addBuildSummary( new BuildSuccess( project, 0 ) ); + return project; + } + + private MavenProject createFailedMavenProject( String artifactId ) + { + MavenProject project = createMavenProject( artifactId ); + executionResult.addBuildSummary( new BuildFailure( project, 0, new Exception() ) ); + executionResult.addException( new LifecycleExecutionException( "", project ) ); + return project; + } +} \ No newline at end of file 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 279fb1186afd..415b94698a39 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,10 +19,6 @@ * under the License. */ -import org.apache.maven.lifecycle.LifecycleExecutionException; -import org.apache.maven.model.Dependency; -import org.apache.maven.project.MavenProject; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; @@ -31,8 +27,6 @@ import java.util.List; import java.util.Properties; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; @@ -40,93 +34,7 @@ @RunWith( MockitoJUnitRunner.class ) public class DefaultBuildResumptionDataRepositoryTest { - private final DefaultBuildResumptionDataRepository buildResumer = new DefaultBuildResumptionDataRepository(); - - private MavenExecutionResult result; - - @Before - public void before() { - result = new DefaultMavenExecutionResult(); - } - - @Test - public void resumeFromGetsDetermined() - { - MavenProject projectA = createSucceededMavenProject( "A" ); - MavenProject projectB = createFailedMavenProject( "B" ); - result.setTopologicallySortedProjects( asList( projectA, projectB ) ); - - Properties properties = buildResumer.determineResumptionProperties( result ); - - assertThat( properties.get( "resumeFrom" ), is( "test:B" ) ); - } - - @Test - public void resumeFromIsIgnoredWhenFirstProjectFails() - { - MavenProject projectA = createFailedMavenProject( "A" ); - MavenProject projectB = createMavenProject( "B" ); - result.setTopologicallySortedProjects( asList( projectA, projectB ) ); - - Properties properties = buildResumer.determineResumptionProperties( result ); - - assertThat( properties.containsKey( "resumeFrom" ), is(false) ); - } - - @Test - public void projectsSucceedingAfterFailedProjectsAreExcluded() - { - MavenProject projectA = createSucceededMavenProject( "A" ); - MavenProject projectB = createFailedMavenProject( "B" ); - MavenProject projectC = createSucceededMavenProject( "C" ); - result.setTopologicallySortedProjects( asList( projectA, projectB, projectC ) ); - - Properties properties = buildResumer.determineResumptionProperties( result ); - - assertThat( properties.get( "excludedProjects" ), is("test:C") ); - } - - @Test - public void projectsDependingOnFailedProjectsAreNotExcluded() - { - MavenProject projectA = createSucceededMavenProject( "A" ); - MavenProject projectB = createFailedMavenProject( "B" ); - MavenProject projectC = createSucceededMavenProject( "C" ); - projectC.setDependencies( singletonList( toDependency( projectB ) ) ); - result.setTopologicallySortedProjects( asList( projectA, projectB, projectC ) ); - - Properties properties = buildResumer.determineResumptionProperties( result ); - - assertThat( properties.containsKey( "excludedProjects" ), is(false) ); - } - - @Test - public void projectsFailingAfterAnotherFailedProjectAreNotExcluded() - { - MavenProject projectA = createSucceededMavenProject( "A" ); - MavenProject projectB = createFailedMavenProject( "B" ); - MavenProject projectC = createSucceededMavenProject( "C" ); - MavenProject projectD = createFailedMavenProject( "D" ); - result.setTopologicallySortedProjects( asList( projectA, projectB, projectC, projectD ) ); - - Properties properties = buildResumer.determineResumptionProperties( result ); - - assertThat( properties.get( "resumeFrom" ), is("test:B") ); - assertThat( properties.get( "excludedProjects" ), is("test:C") ); - } - - @Test - public void multipleExcludedProjectsAreCommaSeparated() - { - MavenProject projectA = createFailedMavenProject( "A" ); - MavenProject projectB = createSucceededMavenProject( "B" ); - MavenProject projectC = createSucceededMavenProject( "C" ); - result.setTopologicallySortedProjects( asList( projectA, projectB, projectC ) ); - - Properties properties = buildResumer.determineResumptionProperties( result ); - - assertThat( properties.get( "excludedProjects" ), is( "test:B, test:C" ) ); - } + private final DefaultBuildResumptionDataRepository repository = new DefaultBuildResumptionDataRepository(); @Test public void resumeFromPropertyGetsApplied() @@ -135,7 +43,7 @@ public void resumeFromPropertyGetsApplied() Properties properties = new Properties(); properties.setProperty( "resumeFrom", ":module-a" ); - buildResumer.applyResumptionProperties( request, properties ); + repository.applyResumptionProperties( request, properties ); assertThat( request.getResumeFrom(), is( ":module-a" ) ); } @@ -148,7 +56,7 @@ public void resumeFromPropertyDoesNotOverrideExistingRequestParameters() Properties properties = new Properties(); properties.setProperty( "resumeFrom", ":module-a" ); - buildResumer.applyResumptionProperties( request, properties ); + repository.applyResumptionProperties( request, properties ); assertThat( request.getResumeFrom(), is( ":module-b" ) ); } @@ -163,40 +71,8 @@ public void excludedProjectsFromPropertyGetsAddedToExistingRequestParameters() Properties properties = new Properties(); properties.setProperty( "excludedProjects", ":module-b, :module-c" ); - buildResumer.applyResumptionProperties( request, properties ); + repository.applyResumptionProperties( request, properties ); assertThat( request.getExcludedProjects(), contains( ":module-a", ":module-b", ":module-c" ) ); } - - private MavenProject createMavenProject( String artifactId ) - { - MavenProject project = new MavenProject(); - project.setGroupId( "test" ); - project.setArtifactId( artifactId ); - return project; - } - - private Dependency toDependency( MavenProject mavenProject ) - { - Dependency dependency = new Dependency(); - dependency.setGroupId( mavenProject.getGroupId() ); - dependency.setArtifactId( mavenProject.getArtifactId() ); - dependency.setVersion( mavenProject.getVersion() ); - return dependency; - } - - private MavenProject createSucceededMavenProject( String artifactId ) - { - MavenProject project = createMavenProject( artifactId ); - result.addBuildSummary( new BuildSuccess( project, 0 ) ); - return project; - } - - private MavenProject createFailedMavenProject( String artifactId ) - { - MavenProject project = createMavenProject( artifactId ); - result.addBuildSummary( new BuildFailure( project, 0, new Exception() ) ); - result.addException( new LifecycleExecutionException( "", project ) ); - return project; - } } From adf8c6faecb16208ebdf61c8d9ee691409491f7a Mon Sep 17 00:00:00 2001 From: Maarten Mulders Date: Thu, 18 Jun 2020 09:27:45 +0200 Subject: [PATCH 30/31] [MNG-5760] Persist method either succeeds or throws Exception, so returns void --- maven-core/src/main/java/org/apache/maven/DefaultMaven.java | 4 ++-- .../apache/maven/execution/BuildResumptionDataRepository.java | 3 +-- .../maven/execution/DefaultBuildResumptionDataRepository.java | 4 +--- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java index 167cb7b1f5f8..51d7752fbfd6 100644 --- a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java +++ b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java @@ -384,8 +384,8 @@ private void persistResumptionData( MavenExecutionResult result, MavenSession se { try { - boolean canResume = buildResumptionDataRepository.persistResumptionData( rootProject, resumption ); - result.setCanResume( canResume ); + buildResumptionDataRepository.persistResumptionData( rootProject, resumption ); + result.setCanResume( true ); } catch ( BuildResumptionPersistenceException e ) { diff --git a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionDataRepository.java b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionDataRepository.java index b8a3b222a6d4..3d0be6f6076f 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionDataRepository.java +++ b/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionDataRepository.java @@ -36,9 +36,8 @@ public interface BuildResumptionDataRepository * @param rootProject The root project that is being built. * @param buildResumptionData Information needed to resume the build. * @throws BuildResumptionPersistenceException When an error occurs while persisting data. - * @return Whether any data was persisted. */ - boolean persistResumptionData( final MavenProject rootProject, final BuildResumptionData buildResumptionData ) + void persistResumptionData( final MavenProject rootProject, final BuildResumptionData buildResumptionData ) throws BuildResumptionPersistenceException; /** 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 e7965b3c45d9..eea097f99349 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 @@ -50,7 +50,7 @@ public class DefaultBuildResumptionDataRepository implements BuildResumptionData private static final Logger LOGGER = LoggerFactory.getLogger( DefaultBuildResumptionDataRepository.class ); @Override - public boolean persistResumptionData( MavenProject rootProject, BuildResumptionData buildResumptionData ) + public void persistResumptionData( MavenProject rootProject, BuildResumptionData buildResumptionData ) throws BuildResumptionPersistenceException { Properties properties = convertToProperties( buildResumptionData ); @@ -69,8 +69,6 @@ public boolean persistResumptionData( MavenProject rootProject, BuildResumptionD String message = "Could not create " + RESUME_PROPERTIES_FILENAME + " file."; throw new BuildResumptionPersistenceException( message, e ); } - - return true; } private Properties convertToProperties( final BuildResumptionData buildResumptionData ) From 058fa9e97cfb40861e9f3c69cbc096e355f77572 Mon Sep 17 00:00:00 2001 From: Martin Kanters Date: Sat, 20 Jun 2020 11:27:50 +0200 Subject: [PATCH 31/31] [MNG-5760] Removed unused import --- .../java/org/apache/maven/execution/BuildResumptionData.java | 1 - 1 file changed, 1 deletion(-) 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 9330929ee611..8889b8312a63 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 @@ -19,7 +19,6 @@ * under the License. */ -import java.util.Collections; import java.util.List; /**