This is an automated email from the ASF dual-hosted git repository. rfscholte pushed a commit to branch MNG-5760 in repository https://gitbox.apache.org/repos/asf/maven.git
commit 504a0efdb9906eae99cdc2b7e21bb1a7c24cbb8c Author: Maarten Mulders <[email protected]> AuthorDate: Sat May 23 19:56:34 2020 +0200 Extract public interface --- .../maven/execution/BuildResumptionManager.java | 299 ++------------------- ...ger.java => DefaultBuildResumptionManager.java} | 29 +- ...java => DefaultBuildResumptionManagerTest.java} | 4 +- 3 files changed, 36 insertions(+), 296 deletions(-) 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 e0ab008..f3d41aa 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 @@ package org.apache.maven.execution; * 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 <code>false</code> 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 class BuildResumptionManager * @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<MavenProject> 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<MavenProject> failedProjects = getFailedProjectsInOrder( result ); - if ( !failedProjects.isEmpty() ) - { - MavenProject resumeFromProject = failedProjects.get( 0 ); - Optional<String> resumeFrom = getResumeFrom( result, resumeFromProject ); - Optional<String> 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<MavenProject> getFailedProjectsInOrder( MavenExecutionResult result ) - { - List<MavenProject> 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<String> getResumeFrom( MavenExecutionResult result, MavenProject failedProject ) - { - List<MavenProject> 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<String> determineProjectsToSkip( MavenExecutionResult result, List<MavenProject> failedProjects, - MavenProject resumeFromProject ) - { - List<MavenProject> allProjects = result.getTopologicallySortedProjects(); - int resumeFromProjectIndex = allProjects.indexOf( resumeFromProject ); - List<MavenProject> remainingProjects = allProjects.subList( resumeFromProjectIndex + 1, allProjects.size() ); - - List<GroupArtifactPair> 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<GroupArtifactPair> 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<MavenProject> mavenProjects, final MavenProject failedProject ); } diff --git a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java similarity index 89% copy from maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java copy to maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java index e0ab008..d08478f 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/BuildResumptionManager.java +++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultBuildResumptionManager.java @@ -45,13 +45,12 @@ 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 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 BuildResumptionManager +public class DefaultBuildResumptionManager implements BuildResumptionManager { private static final String RESUME_PROPERTIES_FILENAME = "resume.properties"; private static final String RESUME_FROM_PROPERTY = "resumeFrom"; @@ -60,7 +59,8 @@ public class BuildResumptionManager @Inject private Logger logger; - + + @Override public boolean persistResumptionData( MavenExecutionResult result, MavenProject rootProject ) { Properties properties = determineResumptionProperties( result ); @@ -74,12 +74,14 @@ public class BuildResumptionManager 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 ); @@ -93,22 +95,7 @@ public class BuildResumptionManager } } - /** - * 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. - * <p> - * {@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. - * <p>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). - */ + @Override public String getResumeFromSelector( List<MavenProject> mavenProjects, MavenProject failedProject ) { boolean hasOverlappingArtifactId = mavenProjects.stream() 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 5c3129b..a0c913b 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.MatcherAssert.assertThat; 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;
