blob: 43ebf06c1f3c321fcdbca7b3fa85937ab26ee319 [file] [log] [blame]
package org.apache.maven.plugin.dependency;
/*
* 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.io.File;
import java.io.IOException;
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.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.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.FileUtils;
/**
* Remove the project dependencies from the local repository, and optionally
* re-resolve them.
*
* @author jdcasey
* @since 2.0
* @goal purge-local-repository
* @aggregator
*
*/
public class PurgeLocalRepositoryMojo
extends AbstractMojo
{
public static final String FILE_FUZZINESS = "file";
public static final String VERSION_FUZZINESS = "version";
public static final String ARTIFACT_ID_FUZZINESS = "artifactId";
public static final String GROUP_ID_FUZZINESS = "groupId";
/**
* The projects in the current build. Each of these is subject to
* refreshing.
*
* @parameter default-value="${reactorProjects}"
* @required
* @readonly
*/
private List projects;
/**
* The list of dependencies in the form of groupId:artifactId which should
* NOT be deleted/refreshed. This is useful for third-party artifacts.
*
* @parameter
*/
private List excludes;
/**
* Comma-separated list of groupId:artifactId entries, which should be used
* to exclude artifacts from deletion/refresh. This is a command-line
* alternative to the <code>excludes</code> parameter, since List
* parameters are not currently compatible with CLI specification.
*
* @parameter expression="${exclude}"
*/
private String exclude;
/**
* Whether to re-resolve the artifacts once they have been deleted from the
* local repository. If you are running this mojo from the command-line, you
* may want to disable this. By default, artifacts will be re-resolved.
*
* @parameter expression="${reResolve}" default-value="true"
*/
private boolean reResolve;
/**
* The local repository, from which to delete artifacts.
*
* @parameter default-value="${localRepository}"
* @required
* @readonly
*/
private ArtifactRepository localRepository;
/**
* The artifact resolver used to re-resolve dependencies, if that option is
* enabled.
*
* @component
*/
private ArtifactResolver resolver;
/**
* The artifact metadata source used to resolve dependencies
*
* @component
*/
private ArtifactMetadataSource source;
/**
* Determines how liberally the plugin will delete an artifact from the
* local repository. Values are: <br/>
* <ul>
* <li><b>file</b> <i>(default)</i> - Eliminate only the artifact's file.</li>
* <li><b>version</b> - Eliminate all files associated with the artifact's
* version.</li>
* <li><b>artifactId</b> - Eliminate all files associated with the
* artifact's artifactId.</li>
* <li><b>groupId</b> - Eliminate all files associated with the artifact's
* groupId.</li>
* </ul>
*
* @parameter expression="${resolutionFuzziness}" default-value="file"
*/
private String resolutionFuzziness;
/**
* Whether this mojo should act on all transitive dependencies. Default
* value is true.
*
* @parameter expression="${actTransitively}" default-value="true"
*/
private boolean actTransitively;
/**
* Used to construct artifacts for deletion/resolution...
*
* @component
*/
private ArtifactFactory factory;
/**
* Whether this plugin should output verbose messages. Default is false.
*
* @parameter expression="${verbose}" default-value="false"
*/
private boolean verbose;
public void execute()
throws MojoExecutionException, MojoFailureException
{
List exclusionPatterns = buildExclusionPatternsList();
for ( Iterator it = projects.iterator(); it.hasNext(); )
{
MavenProject project = (MavenProject) it.next();
try
{
refreshDependenciesForProject( project, exclusionPatterns );
}
catch ( ArtifactResolutionException e )
{
MojoFailureException failure = new MojoFailureException( this,
"Failed to refresh project dependencies for: "
+ project.getId(),
"Artifact resolution failed for project: "
+ project.getId() );
failure.initCause( e );
throw failure;
}
}
}
private List buildExclusionPatternsList()
{
List patterns = new ArrayList();
if ( exclude != null )
{
String[] elements = exclude.split( " ?, ?" );
patterns.addAll( Arrays.asList( elements ) );
}
else if ( excludes != null && !excludes.isEmpty() )
{
patterns.addAll( excludes );
}
return patterns;
}
private Map createArtifactMap( MavenProject project )
{
Map artifactMap = Collections.EMPTY_MAP;
List dependencies = project.getDependencies();
List remoteRepositories = Collections.EMPTY_LIST;
Set dependencyArtifacts = new HashSet();
for ( Iterator it = dependencies.iterator(); it.hasNext(); )
{
Dependency dependency = (Dependency) it.next();
VersionRange vr = VersionRange.createFromVersion( dependency.getVersion() );
Artifact artifact = factory.createDependencyArtifact( dependency.getGroupId(), dependency.getArtifactId(),
vr, dependency.getType(), dependency.getClassifier(),
dependency.getScope() );
dependencyArtifacts.add( artifact );
}
if ( actTransitively )
{
try
{
ArtifactResolutionResult result = resolver.resolveTransitively( dependencyArtifacts, project
.getArtifact(), remoteRepositories, localRepository, source );
artifactMap = ArtifactUtils.artifactMapByVersionlessId( result.getArtifacts() );
}
catch ( ArtifactResolutionException e )
{
verbose( "Skipping: " + e.getArtifactId() + ". It cannot be resolved." );
}
catch ( ArtifactNotFoundException e )
{
verbose( "Skipping: " + e.getArtifactId() + ". It cannot be resolved." );
}
}
else
{
artifactMap = new HashMap();
for ( Iterator it = dependencyArtifacts.iterator(); it.hasNext(); )
{
Artifact artifact = (Artifact) it.next();
try
{
resolver.resolve( artifact, remoteRepositories, localRepository );
artifactMap.put( ArtifactUtils.versionlessKey( artifact ), artifact );
}
catch ( ArtifactResolutionException e )
{
verbose( "Skipping: " + e.getArtifactId() + ". It cannot be resolved." );
}
catch ( ArtifactNotFoundException e )
{
verbose( "Skipping: " + e.getArtifactId() + ". It cannot be resolved." );
}
}
}
return artifactMap;
}
private void verbose( String message )
{
if ( verbose || getLog().isDebugEnabled() )
{
getLog().info( message );
}
}
private void refreshDependenciesForProject( MavenProject project, List exclusionPatterns )
throws ArtifactResolutionException, MojoFailureException
{
Map deps = createArtifactMap( project );
if ( deps.isEmpty() )
{
getLog().info( "Nothing to do for project: " + project.getId() );
return;
}
if ( !exclusionPatterns.isEmpty() )
{
for ( Iterator it = exclusionPatterns.iterator(); it.hasNext(); )
{
String excludedKey = (String) it.next();
verbose( "Excluding: " + excludedKey + " from refresh operation for project: " + project.getId() );
deps.remove( excludedKey );
}
}
verbose( "Processing dependencies for project: " + project.getId() );
List missingArtifacts = new ArrayList();
for ( Iterator it = deps.entrySet().iterator(); it.hasNext(); )
{
Map.Entry entry = (Map.Entry) it.next();
Artifact artifact = (Artifact) entry.getValue();
verbose( "Processing artifact: " + artifact.getId() );
File deleteTarget = findDeleteTarget( artifact );
verbose( "Deleting: " + deleteTarget );
if ( deleteTarget.isDirectory() )
{
try
{
FileUtils.deleteDirectory( deleteTarget );
}
catch ( IOException e )
{
throw new MojoFailureException( this, "Cannot delete dependency from the local repository: "
+ artifact.getId(), "Failed to delete: " + deleteTarget );
}
}
else
{
deleteTarget.delete();
}
if ( reResolve )
{
verbose( "Re-resolving." );
artifact.setResolved( false );
try
{
resolver.resolveAlways( artifact, project.getRemoteArtifactRepositories(), localRepository );
}
catch ( ArtifactResolutionException e )
{
getLog().debug( e.getMessage() );
missingArtifacts.add( artifact );
}
catch ( ArtifactNotFoundException e )
{
getLog().debug( e.getMessage() );
missingArtifacts.add( artifact );
}
}
}
if ( missingArtifacts.size() > 0 )
{
String message = "required artifacts missing:\n";
for ( Iterator i = missingArtifacts.iterator(); i.hasNext(); )
{
Artifact missingArtifact = (Artifact) i.next();
message += " " + missingArtifact.getId() + "\n";
}
message += "\nfor the artifact:";
throw new ArtifactResolutionException( message, project.getArtifact(), project
.getRemoteArtifactRepositories() );
}
}
private File findDeleteTarget( Artifact artifact )
{
File deleteTarget = artifact.getFile();
if ( GROUP_ID_FUZZINESS.equals( resolutionFuzziness ) )
{
// get the artifactId dir.
deleteTarget = deleteTarget.getParentFile().getParentFile();
// get the first groupId dir.
deleteTarget = deleteTarget.getParentFile();
String[] path = localRepository.pathOf( artifact ).split( "\\/" );
// subtract the artifact filename, version dir, artifactId dir, and
// the first groupId
// dir, since we've accounted for those above.
int groupParts = path.length - 4;
File parent = deleteTarget.getParentFile();
int count = 0;
while ( count++ < groupParts )
{
// prune empty dirs back to the beginning of the groupId, if
// possible.
// if the parent dir only has the one child file, then it's okay
// to prune.
if ( parent.list().length < 2 )
{
deleteTarget = parent;
// check the parent of this newly checked dir
parent = deleteTarget.getParentFile();
}
else
{
// if there are more files than the one that we're
// interested in killing, stop.
break;
}
}
}
else if ( ARTIFACT_ID_FUZZINESS.equals( resolutionFuzziness ) )
{
// get the artifactId dir.
deleteTarget = deleteTarget.getParentFile().getParentFile();
}
else if ( VERSION_FUZZINESS.equals( resolutionFuzziness ) )
{
// get the version dir.
deleteTarget = deleteTarget.getParentFile();
}
// else it's file fuzziness.
return deleteTarget;
}
}