mirror of https://github.com/apache/maven.git
[MNG-5760] Add `-r/--resume` to automatically resume from the last failure point
Author: Martin Kanters <mkanters93@gmail.com>
This commit is contained in:
parent
c7aa002c74
commit
658ad90b38
|
@ -36,6 +36,9 @@ import javax.inject.Named;
|
|||
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;
|
||||
import org.apache.maven.execution.ExecutionEvent;
|
||||
import org.apache.maven.execution.MavenExecutionRequest;
|
||||
|
@ -44,6 +47,7 @@ import org.apache.maven.execution.MavenSession;
|
|||
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 +103,12 @@ public class DefaultMaven
|
|||
@Named( GraphBuilder.HINT )
|
||||
private GraphBuilder graphBuilder;
|
||||
|
||||
@Inject
|
||||
private BuildResumptionAnalyzer buildResumptionAnalyzer;
|
||||
|
||||
@Inject
|
||||
private BuildResumptionDataRepository buildResumptionDataRepository;
|
||||
|
||||
@Override
|
||||
public MavenExecutionResult execute( MavenExecutionRequest request )
|
||||
{
|
||||
|
@ -312,7 +322,16 @@ public class DefaultMaven
|
|||
|
||||
if ( session.getResult().hasExceptions() )
|
||||
{
|
||||
return addExceptionToResult( result, session.getResult().getExceptions().get( 0 ) );
|
||||
addExceptionToResult( result, session.getResult().getExceptions().get( 0 ) );
|
||||
persistResumptionData( result, session );
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
session.getAllProjects().stream()
|
||||
.filter( MavenProject::isExecutionRoot )
|
||||
.findFirst()
|
||||
.ifPresent( buildResumptionDataRepository::removeResumptionData );
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
@ -349,6 +368,33 @@ public class DefaultMaven
|
|||
}
|
||||
}
|
||||
|
||||
private void persistResumptionData( MavenExecutionResult result, MavenSession session )
|
||||
{
|
||||
boolean hasLifecycleExecutionExceptions = result.getExceptions().stream()
|
||||
.anyMatch( LifecycleExecutionException.class::isInstance );
|
||||
|
||||
if ( hasLifecycleExecutionExceptions )
|
||||
{
|
||||
MavenProject rootProject = session.getAllProjects().stream()
|
||||
.filter( MavenProject::isExecutionRoot )
|
||||
.findFirst()
|
||||
.orElseThrow( () -> new IllegalStateException( "No project in the session is execution root" ) );
|
||||
|
||||
buildResumptionAnalyzer.determineBuildResumptionData( result ).ifPresent( resumption ->
|
||||
{
|
||||
try
|
||||
{
|
||||
buildResumptionDataRepository.persistResumptionData( rootProject, resumption );
|
||||
result.setCanResume( true );
|
||||
}
|
||||
catch ( BuildResumptionPersistenceException e )
|
||||
{
|
||||
logger.warn( "Could not persist build resumption data", e );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
public RepositorySystemSession newRepositorySession( MavenExecutionRequest request )
|
||||
{
|
||||
return repositorySessionFactory.newRepositorySession( request );
|
||||
|
|
|
@ -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<BuildResumptionData> determineBuildResumptionData( final MavenExecutionResult result );
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
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.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<String> projectsToSkip;
|
||||
|
||||
public BuildResumptionData ( final String resumeFrom, final List<String> projectsToSkip )
|
||||
{
|
||||
this.resumeFrom = resumeFrom;
|
||||
this.projectsToSkip = projectsToSkip;
|
||||
}
|
||||
|
||||
public String getResumeFrom()
|
||||
{
|
||||
return this.resumeFrom;
|
||||
}
|
||||
|
||||
public List<String> getProjectsToSkip()
|
||||
{
|
||||
return this.projectsToSkip;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
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;
|
||||
|
||||
/**
|
||||
* 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 BuildResumptionDataRepository
|
||||
{
|
||||
/**
|
||||
* 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 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.
|
||||
*/
|
||||
void persistResumptionData( final MavenProject rootProject, final BuildResumptionData buildResumptionData )
|
||||
throws BuildResumptionPersistenceException;
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
|
||||
/**
|
||||
* Removes previously stored resumption data.
|
||||
* @param rootProject The root project that is being built.
|
||||
*/
|
||||
void removeResumptionData( final MavenProject rootProject );
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This exception will be thrown when something fails while persisting build resumption data.
|
||||
* @see BuildResumptionDataRepository#persistResumptionData
|
||||
*/
|
||||
public class BuildResumptionPersistenceException extends Exception
|
||||
{
|
||||
public BuildResumptionPersistenceException( String message, Throwable cause )
|
||||
{
|
||||
super( message, cause );
|
||||
}
|
||||
}
|
|
@ -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<BuildResumptionData> determineBuildResumptionData( final MavenExecutionResult result )
|
||||
{
|
||||
final List<MavenProject> 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<String> projectsToSkip = determineProjectsToSkip( result, failedProjects, resumeFromProject );
|
||||
|
||||
return Optional.of( new BuildResumptionData( resumeFromSelector, projectsToSkip ) );
|
||||
}
|
||||
|
||||
private boolean isFailedProjectFirstInBuild( final MavenExecutionResult result, final MavenProject failedProject )
|
||||
{
|
||||
final List<MavenProject> sortedProjects = result.getTopologicallySortedProjects();
|
||||
return sortedProjects.indexOf( failedProject ) == 0;
|
||||
}
|
||||
|
||||
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() );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<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() );
|
||||
|
||||
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<GroupArtifactPair> 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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
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.commons.lang3.StringUtils;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
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.Properties;
|
||||
|
||||
/**
|
||||
* 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 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( DefaultBuildResumptionDataRepository.class );
|
||||
|
||||
@Override
|
||||
public void persistResumptionData( MavenProject rootProject, BuildResumptionData buildResumptionData )
|
||||
throws BuildResumptionPersistenceException
|
||||
{
|
||||
Properties properties = convertToProperties( buildResumptionData );
|
||||
|
||||
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 )
|
||||
{
|
||||
String message = "Could not create " + RESUME_PROPERTIES_FILENAME + " file.";
|
||||
throw new BuildResumptionPersistenceException( message, e );
|
||||
}
|
||||
}
|
||||
|
||||
private Properties convertToProperties( final BuildResumptionData buildResumptionData )
|
||||
{
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty( RESUME_FROM_PROPERTY, buildResumptionData.getResumeFrom() );
|
||||
String excludedProjects = String.join( PROPERTY_DELIMITER, buildResumptionData.getProjectsToSkip() );
|
||||
properties.setProperty( EXCLUDED_PROJECTS_PROPERTY, excludedProjects );
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyResumptionData( MavenExecutionRequest request, MavenProject rootProject )
|
||||
{
|
||||
Properties properties = loadResumptionFile( Paths.get( 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 {} file. ", RESUME_PROPERTIES_FILENAME, e );
|
||||
}
|
||||
}
|
||||
|
||||
private Properties loadResumptionFile( Path rootBuildDirectory )
|
||||
{
|
||||
Properties properties = new Properties();
|
||||
Path path = Paths.get( RESUME_PROPERTIES_FILENAME ).resolve( rootBuildDirectory );
|
||||
if ( !Files.exists( path ) )
|
||||
{
|
||||
LOGGER.warn( "The {} file does not exist. The --resume / -r feature will not work.", path );
|
||||
return properties;
|
||||
}
|
||||
|
||||
try ( Reader reader = Files.newBufferedReader( path ) )
|
||||
{
|
||||
properties.load( reader );
|
||||
}
|
||||
catch ( IOException e )
|
||||
{
|
||||
LOGGER.warn( "Unable to read {}. The --resume / -r feature will not work.", path );
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
// 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() ) )
|
||||
{
|
||||
String propertyValue = properties.getProperty( RESUME_FROM_PROPERTY );
|
||||
request.setResumeFrom( propertyValue );
|
||||
LOGGER.info( "Resuming from {} due to the --resume / -r feature.", propertyValue );
|
||||
}
|
||||
|
||||
if ( properties.containsKey( EXCLUDED_PROJECTS_PROPERTY ) )
|
||||
{
|
||||
String propertyValue = properties.getProperty( EXCLUDED_PROJECTS_PROPERTY );
|
||||
String[] excludedProjects = propertyValue.split( PROPERTY_DELIMITER );
|
||||
request.getExcludedProjects().addAll( Arrays.asList( excludedProjects ) );
|
||||
LOGGER.info( "Additionally excluding projects '{}' due to the --resume / -r feature.", propertyValue );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -116,6 +116,8 @@ public class DefaultMavenExecutionRequest
|
|||
|
||||
private List<String> excludedProjects;
|
||||
|
||||
private boolean resume = false;
|
||||
|
||||
private String resumeFrom;
|
||||
|
||||
private String makeBehavior;
|
||||
|
@ -300,6 +302,12 @@ public class DefaultMavenExecutionRequest
|
|||
return excludedProjects;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResume()
|
||||
{
|
||||
return resume;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResumeFrom()
|
||||
{
|
||||
|
@ -598,6 +606,14 @@ public class DefaultMavenExecutionRequest
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MavenExecutionRequest setResume()
|
||||
{
|
||||
resume = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MavenExecutionRequest setResumeFrom( String project )
|
||||
{
|
||||
|
|
|
@ -43,6 +43,8 @@ public class DefaultMavenExecutionResult
|
|||
private final Map<MavenProject, BuildSummary> buildSummaries =
|
||||
Collections.synchronizedMap( new IdentityHashMap<>() );
|
||||
|
||||
private boolean canResume = false;
|
||||
|
||||
public MavenExecutionResult setProject( MavenProject project )
|
||||
{
|
||||
this.project = project;
|
||||
|
@ -108,4 +110,16 @@ public class DefaultMavenExecutionResult
|
|||
{
|
||||
buildSummaries.put( summary.getProject(), summary );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canResume()
|
||||
{
|
||||
return canResume;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCanResume( boolean canResume )
|
||||
{
|
||||
this.canResume = canResume;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -171,6 +171,17 @@ public interface MavenExecutionRequest
|
|||
*/
|
||||
List<String> 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();
|
||||
|
|
|
@ -67,4 +67,19 @@ public interface MavenExecutionResult
|
|||
* @param summary The build summary to add, must not be {@code null}.
|
||||
*/
|
||||
void addBuildSummary( BuildSummary summary );
|
||||
|
||||
/**
|
||||
* Indicates whether or not the build could be resumed by a second invocation of Maven.
|
||||
* @see BuildResumptionDataRepository
|
||||
* @return <code>true</code> when it is possible to resume the build, <code>false</code> otherwise.
|
||||
*/
|
||||
boolean canResume();
|
||||
|
||||
/**
|
||||
* Indicate that the build can or cannot be resumed by a second invocation of Maven.
|
||||
* @param canResume <code>true</code> when it is possible to resume the build, <code>false</code> otherwise.
|
||||
* @see BuildResumptionDataRepository
|
||||
* @see #canResume()
|
||||
*/
|
||||
void setCanResume( boolean canResume );
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.apache.maven.DefaultMaven;
|
|||
import org.apache.maven.MavenExecutionException;
|
||||
import org.apache.maven.ProjectCycleException;
|
||||
import org.apache.maven.artifact.ArtifactUtils;
|
||||
import org.apache.maven.execution.BuildResumptionDataRepository;
|
||||
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 BuildResumptionDataRepository buildResumptionDataRepository;
|
||||
|
||||
@Override
|
||||
public Result<ProjectDependencyGraph> build( MavenSession session )
|
||||
{
|
||||
|
@ -84,6 +88,7 @@ public class DefaultGraphBuilder
|
|||
{
|
||||
final List<MavenProject> projects = getProjectsForMavenReactor( session );
|
||||
validateProjects( projects );
|
||||
enrichRequestFromResumptionData( projects, session.getRequest() );
|
||||
result = reactorDependencyGraph( session, projects );
|
||||
}
|
||||
|
||||
|
@ -341,6 +346,18 @@ public class DefaultGraphBuilder
|
|||
return result;
|
||||
}
|
||||
|
||||
private void enrichRequestFromResumptionData( List<MavenProject> projects, MavenExecutionRequest request )
|
||||
{
|
||||
if ( request.isResume() )
|
||||
{
|
||||
projects.stream()
|
||||
.filter( MavenProject::isExecutionRoot )
|
||||
.findFirst()
|
||||
.ifPresent( rootProject ->
|
||||
buildResumptionDataRepository.applyResumptionData( request, rootProject ) );
|
||||
}
|
||||
}
|
||||
|
||||
private String formatProjects( List<MavenProject> projects )
|
||||
{
|
||||
StringBuilder projectNames = new StringBuilder();
|
||||
|
|
|
@ -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<BuildResumptionData> 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<BuildResumptionData> 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<BuildResumptionData> 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<BuildResumptionData> 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<BuildResumptionData> 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
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.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
@RunWith( MockitoJUnitRunner.class )
|
||||
public class DefaultBuildResumptionDataRepositoryTest
|
||||
{
|
||||
private final DefaultBuildResumptionDataRepository repository = new DefaultBuildResumptionDataRepository();
|
||||
|
||||
@Test
|
||||
public void resumeFromPropertyGetsApplied()
|
||||
{
|
||||
MavenExecutionRequest request = new DefaultMavenExecutionRequest();
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty( "resumeFrom", ":module-a" );
|
||||
|
||||
repository.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" );
|
||||
|
||||
repository.applyResumptionProperties( request, properties );
|
||||
|
||||
assertThat( request.getResumeFrom(), is( ":module-b" ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludedProjectsFromPropertyGetsAddedToExistingRequestParameters()
|
||||
{
|
||||
MavenExecutionRequest request = new DefaultMavenExecutionRequest();
|
||||
List<String> excludedProjects = new ArrayList<>();
|
||||
excludedProjects.add( ":module-a" );
|
||||
request.setExcludedProjects( excludedProjects );
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty( "excludedProjects", ":module-b, :module-c" );
|
||||
|
||||
repository.applyResumptionProperties( request, properties );
|
||||
|
||||
assertThat( request.getExcludedProjects(), contains( ":module-a", ":module-b", ":module-c" ) );
|
||||
}
|
||||
}
|
|
@ -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 class 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() );
|
||||
|
|
|
@ -48,6 +48,7 @@ import org.apache.maven.eventspy.internal.EventSpyDispatcher;
|
|||
import org.apache.maven.exception.DefaultExceptionHandler;
|
||||
import org.apache.maven.exception.ExceptionHandler;
|
||||
import org.apache.maven.exception.ExceptionSummary;
|
||||
import org.apache.maven.execution.BuildResumptionDataRepository;
|
||||
import org.apache.maven.execution.DefaultMavenExecutionRequest;
|
||||
import org.apache.maven.execution.ExecutionListener;
|
||||
import org.apache.maven.execution.MavenExecutionRequest;
|
||||
|
@ -168,6 +169,8 @@ public class MavenCli
|
|||
|
||||
private Map<String, ConfigurationProcessor> configurationProcessors;
|
||||
|
||||
private BuildResumptionDataRepository buildResumptionDataRepository;
|
||||
|
||||
public MavenCli()
|
||||
{
|
||||
this( null );
|
||||
|
@ -705,6 +708,8 @@ public class MavenCli
|
|||
|
||||
dispatcher = (DefaultSecDispatcher) container.lookup( SecDispatcher.class, "maven" );
|
||||
|
||||
buildResumptionDataRepository = container.lookup( BuildResumptionDataRepository.class );
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
|
@ -996,7 +1001,8 @@ public class MavenCli
|
|||
|
||||
if ( project == null && exception instanceof LifecycleExecutionException )
|
||||
{
|
||||
project = ( (LifecycleExecutionException) exception ).getProject();
|
||||
LifecycleExecutionException lifecycleExecutionException = (LifecycleExecutionException) exception;
|
||||
project = lifecycleExecutionException.getProject();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1025,12 +1031,15 @@ public class MavenCli
|
|||
}
|
||||
}
|
||||
|
||||
if ( project != null && !project.equals( result.getTopologicallySortedProjects().get( 0 ) ) )
|
||||
List<MavenProject> sortedProjects = result.getTopologicallySortedProjects();
|
||||
if ( result.canResume() )
|
||||
{
|
||||
slf4jLogger.error( "" );
|
||||
slf4jLogger.error( "After correcting the problems, you can resume the build with the command" );
|
||||
slf4jLogger.error( buffer().a( " " ).strong( "mvn <args> -rf "
|
||||
+ getResumeFrom( result.getTopologicallySortedProjects(), project ) ).toString() );
|
||||
logBuildResumeHint( "mvn <args> -r " );
|
||||
}
|
||||
else if ( project != null && !project.equals( sortedProjects.get( 0 ) ) )
|
||||
{
|
||||
String resumeFromSelector = getResumeFromSelector( sortedProjects, project );
|
||||
logBuildResumeHint( "mvn <args> -rf " + resumeFromSelector );
|
||||
}
|
||||
|
||||
if ( MavenExecutionRequest.REACTOR_FAIL_NEVER.equals( cliRequest.request.getReactorFailureBehavior() ) )
|
||||
|
@ -1050,32 +1059,41 @@ public class MavenCli
|
|||
}
|
||||
}
|
||||
|
||||
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() );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
* {@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.
|
||||
* 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).
|
||||
* @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<MavenProject> mavenProjects, MavenProject failedProject )
|
||||
String getResumeFromSelector( List<MavenProject> mavenProjects, MavenProject failedProject )
|
||||
{
|
||||
for ( MavenProject buildProject : mavenProjects )
|
||||
{
|
||||
if ( failedProject.getArtifactId().equals( buildProject.getArtifactId() ) && !failedProject.equals(
|
||||
buildProject ) )
|
||||
boolean hasOverlappingArtifactId = mavenProjects.stream()
|
||||
.filter( project -> failedProject.getArtifactId().equals( project.getArtifactId() ) )
|
||||
.count() > 1;
|
||||
|
||||
if ( hasOverlappingArtifactId )
|
||||
{
|
||||
return failedProject.getGroupId() + ":" + failedProject.getArtifactId();
|
||||
}
|
||||
}
|
||||
|
||||
return ":" + failedProject.getArtifactId();
|
||||
}
|
||||
|
||||
|
@ -1516,6 +1534,11 @@ public class MavenCli
|
|||
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 ) );
|
||||
|
|
|
@ -19,6 +19,9 @@ package org.apache.maven.cli;
|
|||
* 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 static org.mockito.Mockito.times;
|
|||
|
||||
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 class MavenCliTest
|
|||
orderdEventSpyDispatcherMock.verify(eventSpyDispatcherMock, times(1)).onEvent(any(ToolchainsBuildingResult.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resumeFromSelectorIsSuggestedWithoutGroupId()
|
||||
{
|
||||
List<MavenProject> 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<MavenProject> 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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue