blob: 633208354689d1c6a2f7c5c2525e0cb958574d2d [file] [log] [blame]
package org.apache.maven.shared.release.phase;
/*
* 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.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.release.ReleaseExecutionException;
import org.apache.maven.shared.release.ReleaseFailureException;
import org.apache.maven.shared.release.ReleaseResult;
import org.apache.maven.shared.release.config.ReleaseDescriptor;
import org.apache.maven.shared.release.env.ReleaseEnvironment;
import org.apache.maven.shared.release.versions.DefaultVersionInfo;
import org.apache.maven.shared.release.versions.VersionInfo;
import org.apache.maven.shared.release.versions.VersionParseException;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.components.interactivity.Prompter;
import org.codehaus.plexus.components.interactivity.PrompterException;
/**
* Check the dependencies of all projects being released to see if there are any unreleased snapshots.
*
* @author <a href="mailto:brett@apache.org">Brett Porter</a>
* @todo plugins with no version will be resolved to RELEASE which is not a snapshot, but remains unresolved to this point. This is a potential hole in the check, and should be revisited after the release pom writing is done and resolving versions to verify whether it is.
* @todo plugins injected by the lifecycle are not tested here. They will be injected with a RELEASE version so are covered under the above point.
*/
@Component( role = ReleasePhase.class, hint = "check-dependency-snapshots" )
public class CheckDependencySnapshotsPhase
extends AbstractReleasePhase
{
public static final String RESOLVE_SNAPSHOT_MESSAGE = "There are still some remaining snapshot dependencies.\n";
public static final String RESOLVE_SNAPSHOT_PROMPT = "Do you want to resolve them now?";
public static final String RESOLVE_SNAPSHOT_TYPE_MESSAGE = "Dependency type to resolve,";
public static final String RESOLVE_SNAPSHOT_TYPE_PROMPT =
"specify the selection number ( 0:All 1:Project Dependencies 2:Plugins 3:Reports 4:Extensions ):";
/**
* Component used to prompt for input.
*/
@Requirement
private Prompter prompter;
/**
* Component used to create artifacts
*/
@Requirement
private ArtifactFactory artifactFactory;
// Be aware of the difference between usedSnapshots and specifiedSnapshots:
// UsedSnapshots end up on the classpath.
// SpecifiedSnapshots are defined anywhere in the pom.
// We'll probably need to introduce specifiedSnapshots as well.
// @TODO MRELEASE-378: verify custom dependencies in plugins. Be aware of deprecated/removed Components in M3, such as PluginCollector
// @TODO MRELEASE-763: verify all dependencies in inactive profiles
private Set<Artifact> usedSnapshotDependencies = new HashSet<Artifact>();
private Set<Artifact> usedSnapshotReports = new HashSet<Artifact>();
private Set<Artifact> usedSnapshotExtensions = new HashSet<Artifact>();
private Set<Artifact> usedSnapshotPlugins = new HashSet<Artifact>();
public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
List<MavenProject> reactorProjects )
throws ReleaseExecutionException, ReleaseFailureException
{
ReleaseResult result = new ReleaseResult();
if ( !releaseDescriptor.isAllowTimestampedSnapshots() )
{
logInfo( result, "Checking dependencies and plugins for snapshots ..." );
Map<String, String> originalVersions = releaseDescriptor.getOriginalVersions( reactorProjects );
for ( MavenProject project : reactorProjects )
{
checkProject( project, originalVersions, releaseDescriptor );
}
}
else
{
logInfo( result, "Ignoring SNAPSHOT depenedencies and plugins ..." );
}
result.setResultCode( ReleaseResult.SUCCESS );
return result;
}
private void checkProject( MavenProject project, Map<String, String> originalVersions,
ReleaseDescriptor releaseDescriptor )
throws ReleaseFailureException, ReleaseExecutionException
{
@SuppressWarnings( "unchecked" )
Map<String, Artifact> artifactMap = ArtifactUtils.artifactMapByVersionlessId( project.getArtifacts() );
if ( project.getParentArtifact() != null )
{
if ( checkArtifact( project.getParentArtifact(), originalVersions, artifactMap, releaseDescriptor ) )
{
usedSnapshotDependencies.add( project.getParentArtifact() );
}
}
Set<Artifact> dependencyArtifacts = project.getArtifacts();
checkDependencies( originalVersions, releaseDescriptor, artifactMap, dependencyArtifacts );
//@todo check dependencyManagement
@SuppressWarnings( "unchecked" )
Set<Artifact> pluginArtifacts = project.getPluginArtifacts();
checkPlugins( originalVersions, releaseDescriptor, artifactMap, pluginArtifacts );
//@todo check pluginManagement
@SuppressWarnings( "unchecked" )
Set<Artifact> reportArtifacts = project.getReportArtifacts();
checkReports( originalVersions, releaseDescriptor, artifactMap, reportArtifacts );
@SuppressWarnings( "unchecked" )
Set<Artifact> extensionArtifacts = project.getExtensionArtifacts();
checkExtensions( originalVersions, releaseDescriptor, artifactMap, extensionArtifacts );
//@todo check profiles
if ( !usedSnapshotDependencies.isEmpty() || !usedSnapshotReports.isEmpty()
|| !usedSnapshotExtensions.isEmpty() || !usedSnapshotPlugins.isEmpty() )
{
if ( releaseDescriptor.isInteractive() )
{
resolveSnapshots( usedSnapshotDependencies, usedSnapshotReports, usedSnapshotExtensions,
usedSnapshotPlugins, releaseDescriptor );
}
if ( !usedSnapshotDependencies.isEmpty() || !usedSnapshotReports.isEmpty()
|| !usedSnapshotExtensions.isEmpty() || !usedSnapshotPlugins.isEmpty() )
{
StringBuilder message = new StringBuilder();
printSnapshotDependencies( usedSnapshotDependencies, message );
printSnapshotDependencies( usedSnapshotReports, message );
printSnapshotDependencies( usedSnapshotExtensions, message );
printSnapshotDependencies( usedSnapshotPlugins, message );
message.append( "in project '" + project.getName() + "' (" + project.getId() + ")" );
throw new ReleaseFailureException(
"Can't release project due to non released dependencies :\n" + message );
}
}
}
private void checkPlugins( Map<String, String> originalVersions, ReleaseDescriptor releaseDescriptor,
Map<String, Artifact> artifactMap, Set<Artifact> pluginArtifacts )
throws ReleaseExecutionException
{
for ( Artifact artifact : pluginArtifacts )
{
if ( checkArtifact( artifact, originalVersions, artifactMap, releaseDescriptor ) )
{
boolean addToFailures;
if ( "org.apache.maven.plugins".equals( artifact.getGroupId() ) && "maven-release-plugin".equals(
artifact.getArtifactId() ) )
{
// It's a snapshot of the release plugin. Maybe just testing - ask
// By default, we fail as for any other plugin
if ( releaseDescriptor.isSnapshotReleasePluginAllowed() )
{
addToFailures = false;
}
else if ( releaseDescriptor.isInteractive() )
{
try
{
String result;
if ( !releaseDescriptor.isSnapshotReleasePluginAllowed() )
{
prompter.showMessage( "This project relies on a SNAPSHOT of the release plugin. "
+ "This may be necessary during testing.\n" );
result = prompter.prompt( "Do you want to continue with the release?",
Arrays.asList( "yes", "no" ), "no" );
}
else
{
result = "yes";
}
if ( result.toLowerCase( Locale.ENGLISH ).startsWith( "y" ) )
{
addToFailures = false;
releaseDescriptor.setSnapshotReleasePluginAllowed( true );
}
else
{
addToFailures = true;
}
}
catch ( PrompterException e )
{
throw new ReleaseExecutionException( e.getMessage(), e );
}
}
else
{
addToFailures = true;
}
}
else
{
addToFailures = true;
}
if ( addToFailures )
{
usedSnapshotPlugins.add( artifact );
}
}
}
}
private void checkDependencies( Map<String, String> originalVersions, ReleaseDescriptor releaseDescriptor,
Map<String, Artifact> artifactMap, Set<Artifact> dependencyArtifacts )
{
for ( Artifact artifact : dependencyArtifacts )
{
if ( checkArtifact( artifact, originalVersions, artifactMap, releaseDescriptor ) )
{
usedSnapshotDependencies.add( getArtifactFromMap( artifact, artifactMap ) );
}
}
}
private void checkReports( Map<String, String> originalVersions, ReleaseDescriptor releaseDescriptor,
Map<String, Artifact> artifactMap, Set<Artifact> reportArtifacts )
{
for ( Artifact artifact : reportArtifacts )
{
if ( checkArtifact( artifact, originalVersions, artifactMap, releaseDescriptor ) )
{
//snapshotDependencies.add( artifact );
usedSnapshotReports.add( artifact );
}
}
}
private void checkExtensions( Map<String, String> originalVersions, ReleaseDescriptor releaseDescriptor,
Map<String, Artifact> artifactMap, Set<Artifact> extensionArtifacts )
{
for ( Artifact artifact : extensionArtifacts )
{
if ( checkArtifact( artifact, originalVersions, artifactMap, releaseDescriptor ) )
{
usedSnapshotExtensions.add( artifact );
}
}
}
private static boolean checkArtifact( Artifact artifact, Map<String, String> originalVersions,
Map<String, Artifact> artifactMapByVersionlessId,
ReleaseDescriptor releaseDescriptor )
{
Artifact checkArtifact = getArtifactFromMap( artifact, artifactMapByVersionlessId );
return checkArtifact( checkArtifact, originalVersions, releaseDescriptor );
}
private static Artifact getArtifactFromMap( Artifact artifact, Map<String, Artifact> artifactMapByVersionlessId )
{
String versionlessId = ArtifactUtils.versionlessKey( artifact );
Artifact checkArtifact = artifactMapByVersionlessId.get( versionlessId );
if ( checkArtifact == null )
{
checkArtifact = artifact;
}
return checkArtifact;
}
private static boolean checkArtifact( Artifact artifact, Map<String, String> originalVersions,
ReleaseDescriptor releaseDescriptor )
{
String versionlessArtifactKey = ArtifactUtils.versionlessKey( artifact.getGroupId(), artifact.getArtifactId() );
// We are only looking at dependencies external to the project - ignore anything found in the reactor as
// it's version will be updated
boolean result =
artifact.isSnapshot()
&& !artifact.getBaseVersion().equals( originalVersions.get( versionlessArtifactKey ) );
// If we have a snapshot but allowTimestampedSnapshots is true, accept the artifact if the version
// indicates that it is a timestamped snapshot.
if ( result && releaseDescriptor.isAllowTimestampedSnapshots() )
{
result = artifact.getVersion().indexOf( Artifact.SNAPSHOT_VERSION ) >= 0;
}
return result;
}
public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
List<MavenProject> reactorProjects )
throws ReleaseExecutionException, ReleaseFailureException
{
// It makes no modifications, so simulate is the same as execute
return execute( releaseDescriptor, releaseEnvironment, reactorProjects );
}
public void setPrompter( Prompter prompter )
{
this.prompter = prompter;
}
private StringBuilder printSnapshotDependencies( Set<Artifact> snapshotsSet, StringBuilder message )
{
List<Artifact> snapshotsList = new ArrayList<Artifact>( snapshotsSet );
Collections.sort( snapshotsList );
for ( Artifact artifact : snapshotsList )
{
message.append( " " );
message.append( artifact );
message.append( "\n" );
}
return message;
}
private void resolveSnapshots( Set<Artifact> projectDependencies, Set<Artifact> reportDependencies,
Set<Artifact> extensionDependencies, Set<Artifact> pluginDependencies,
ReleaseDescriptor releaseDescriptor )
throws ReleaseExecutionException
{
try
{
prompter.showMessage( RESOLVE_SNAPSHOT_MESSAGE );
String result =
prompter.prompt( RESOLVE_SNAPSHOT_PROMPT, Arrays.asList( "yes", "no" ), "no" );
if ( result.toLowerCase( Locale.ENGLISH ).startsWith( "y" ) )
{
Map<String, Map<String, String>> resolvedSnapshots = null;
prompter.showMessage( RESOLVE_SNAPSHOT_TYPE_MESSAGE );
result = prompter.prompt( RESOLVE_SNAPSHOT_TYPE_PROMPT,
Arrays.asList( "0", "1", "2", "3" ), "1" );
switch ( Integer.parseInt( result.toLowerCase( Locale.ENGLISH ) ) )
{
// all
case 0:
resolvedSnapshots = processSnapshot( projectDependencies );
resolvedSnapshots.putAll( processSnapshot( pluginDependencies ) );
resolvedSnapshots.putAll( processSnapshot( reportDependencies ) );
resolvedSnapshots.putAll( processSnapshot( extensionDependencies ) );
break;
// project dependencies
case 1:
resolvedSnapshots = processSnapshot( projectDependencies );
break;
// plugins
case 2:
resolvedSnapshots = processSnapshot( pluginDependencies );
break;
// reports
case 3:
resolvedSnapshots = processSnapshot( reportDependencies );
break;
// extensions
case 4:
resolvedSnapshots = processSnapshot( extensionDependencies );
break;
default:
}
releaseDescriptor.getResolvedSnapshotDependencies().putAll( resolvedSnapshots );
}
}
catch ( PrompterException e )
{
throw new ReleaseExecutionException( e.getMessage(), e );
}
catch ( VersionParseException e )
{
throw new ReleaseExecutionException( e.getMessage(), e );
}
}
private Map<String, Map<String, String>> processSnapshot( Set<Artifact> snapshotSet )
throws PrompterException, VersionParseException
{
Map<String, Map<String, String>> resolvedSnapshots = new HashMap<String, Map<String, String>>();
Iterator<Artifact> iterator = snapshotSet.iterator();
while ( iterator.hasNext() )
{
Artifact currentArtifact = iterator.next();
String versionlessKey = ArtifactUtils.versionlessKey( currentArtifact );
Map<String, String> versionMap = new HashMap<String, String>();
VersionInfo versionInfo = new DefaultVersionInfo( currentArtifact.getBaseVersion() );
versionMap.put( ReleaseDescriptor.ORIGINAL_VERSION, versionInfo.toString() );
prompter.showMessage(
"Dependency '" + versionlessKey + "' is a snapshot (" + currentArtifact.getVersion() + ")\n" );
String result = prompter.prompt( "Which release version should it be set to?",
versionInfo.getReleaseVersionString() );
versionMap.put( ReleaseDescriptor.RELEASE_KEY, result );
iterator.remove();
// by default, keep the same version for the dependency after release, unless it was previously newer
// the user may opt to type in something different
VersionInfo nextVersionInfo = new DefaultVersionInfo( result );
String nextVersion;
if ( nextVersionInfo.compareTo( versionInfo ) > 0 )
{
nextVersion = nextVersionInfo.toString();
}
else
{
nextVersion = versionInfo.toString();
}
result = prompter.prompt( "What version should the dependency be reset to for development?", nextVersion );
versionMap.put( ReleaseDescriptor.DEVELOPMENT_KEY, result );
resolvedSnapshots.put( versionlessKey, versionMap );
}
return resolvedSnapshots;
}
}