All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.maven.plugins.dependency.PurgeLocalRepositoryMojo Maven / Gradle / Ivy

There is a newer version: 3.8.1
Show newest version
package org.apache.maven.plugins.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.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecution.Source;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.artifact.filter.resolve.AbstractFilter;
import org.apache.maven.shared.artifact.filter.resolve.AndFilter;
import org.apache.maven.shared.artifact.filter.resolve.Node;
import org.apache.maven.shared.artifact.filter.resolve.PatternExclusionsFilter;
import org.apache.maven.shared.artifact.filter.resolve.PatternInclusionsFilter;
import org.apache.maven.shared.artifact.filter.resolve.ScopeFilter;
import org.apache.maven.shared.artifact.filter.resolve.TransformableFilter;
import org.apache.maven.shared.artifact.filter.resolve.transform.ArtifactIncludeFilterTransformer;
import org.apache.maven.shared.transfer.artifact.DefaultArtifactCoordinate;
import org.apache.maven.shared.transfer.artifact.TransferUtils;
import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver;
import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolverException;
import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult;
import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver;
import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolverException;
import org.apache.maven.shared.utils.logging.MessageBuilder;
import org.apache.maven.shared.utils.logging.MessageUtils;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.StringUtils;

/**
 * When run on a project, remove the project dependencies from the local repository, and optionally re-resolve them.
 * Outside of a project, remove the manually given dependencies.
 *
 * @author jdcasey
 * @since 2.0
 */
@Mojo( name = "purge-local-repository", threadSafe = true, requiresProject = false )
public class PurgeLocalRepositoryMojo
    extends AbstractMojo
{

    private static final String VERSION_FUZZINESS = "version";

    private static final String ARTIFACT_ID_FUZZINESS = "artifactId";

    private static final String GROUP_ID_FUZZINESS = "groupId";

    /**
     * The Maven projects in the reactor.
     */
    @Parameter( defaultValue = "${reactorProjects}", readonly = true, required = true )
    private List reactorProjects;

    /**
     * The current Maven project.
     */
    @Parameter( defaultValue = "${project}", readonly = true, required = true )
    private MavenProject project;

    @Parameter( defaultValue = "${session}", readonly = true, required = true )
    private MavenSession session;

    /**
     * This mojo execution, used to determine if it was launched from the lifecycle or the command-line.
     */
    @Parameter( defaultValue = "${mojo}", required = true, readonly = true )
    private MojoExecution mojoExecution;

    /**
     * Artifact handler manager.
     */
    @Component
    private ArtifactHandlerManager artifactHandlerManager;

    /**
     * The list of dependencies in the form of groupId:artifactId which should BE deleted/purged from the local
     * repository. Note that using this parameter will deactivate the normal process for purging the current project
     * dependency tree. If this parameter is used, only the included artifacts will be purged. The manualIncludes
     * parameter should not be used in combination with the includes/excludes parameters.
     *
     * @since 2.6
     */
    @Parameter
    private List manualIncludes;

    /**
     * Comma-separated list of groupId:artifactId entries, which should be used to manually include artifacts for
     * deletion. This is a command-line alternative to the manualIncludes parameter, since List parameters
     * are not currently compatible with CLI specification.
     *
     * @since 2.6
     */
    @Parameter( property = "manualInclude" )
    private String manualInclude;

    /**
     * The list of dependencies in the form of groupId:artifactId which should BE deleted/refreshed.
     *
     * @since 2.6
     */
    @Parameter
    private List includes;

    /**
     * Comma-separated list of groupId:artifactId entries, which should be used to include artifacts for
     * deletion/refresh. This is a command-line alternative to the includes parameter, since List
     * parameters are not currently compatible with CLI specification.
     *
     * @since 2.6
     */
    @Parameter( property = "include" )
    private String include;

    /**
     * The list of dependencies in the form of groupId:artifactId which should NOT be deleted/refreshed.
     */
    @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 excludes parameter, since List
     * parameters are not currently compatible with CLI specification.
     */
    @Parameter( property = "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( property = "reResolve", defaultValue = "true" )
    private boolean reResolve;

    /**
     * The local repository, from which to delete artifacts.
     */
    @Parameter( defaultValue = "${localRepository}", readonly = true, required = true )
    private ArtifactRepository localRepository;

    /**
     * The dependency resolver
     */
    @Component
    private DependencyResolver dependencyResolver;

    /**
     * The artifact resolver used to re-resolve dependencies, if that option is enabled.
     */
    @Component
    private ArtifactResolver artifactResolver;

    /**
     * Determines how liberally the plugin will delete an artifact from the local repository. Values are: 
*
    *
  • file - Eliminate only the artifact's file.
  • *
  • version (default) - Eliminate all files associated with the version of the artifact.
  • *
  • artifactId - Eliminate all files associated with the artifact's artifactId.
  • *
  • groupId - Eliminate all files associated with the artifact's groupId.
  • *
*/ @Parameter( property = "resolutionFuzziness", defaultValue = "version" ) private String resolutionFuzziness; /** * Whether this mojo should act on all transitive dependencies. Default value is true. */ @Parameter( property = "actTransitively", defaultValue = "true" ) private boolean actTransitively; /** * Whether this plugin should output verbose messages. Default is false. */ @Parameter( property = "verbose", defaultValue = "false" ) private boolean verbose; /** * Whether to purge only snapshot artifacts. * * @since 2.4 */ @Parameter( property = "snapshotsOnly", defaultValue = "false" ) private boolean snapshotsOnly; /** * Skip plugin execution completely. * * @since 2.7 */ @Parameter( property = "skip", defaultValue = "false" ) private boolean skip; /** * Includes only direct project dependencies. */ private class DirectDependencyFilter extends AbstractFilter { private final Artifact projectArtifact; private final List directDependencies; /** * Default constructor * * @param directDependencies Set of dependencies objects which represent the direct dependencies of the project */ DirectDependencyFilter( Artifact projectArtifact, List directDependencies ) { this.projectArtifact = projectArtifact; this.directDependencies = directDependencies; } @Override public boolean accept( Node node, List parents ) { if ( artifactsGAMatch( node, projectArtifact.getGroupId(), projectArtifact.getArtifactId() ) ) { return true; } for ( Dependency dep : directDependencies ) { if ( this.artifactsGAMatch( node, dep.getGroupId(), dep.getArtifactId() ) ) { return true; } } return false; } /* * Compare the groupId:artifactId of two artifacts. */ private boolean artifactsGAMatch( Node node, String groupId, String artifactId ) { if ( node.getDependency() == null ) { return false; } if ( !node.getDependency().getGroupId().equals( groupId ) ) { getLog().debug( "Different groupId: " + node.getDependency() + " " + groupId ); return false; } if ( !node.getDependency().getArtifactId().equals( artifactId ) ) { getLog().debug( "Different artifactId: " + node.getDependency() + " " + artifactId ); return false; } return true; } } /** * Includes only snapshot artifacts */ private static class SnapshotsFilter extends AbstractFilter { @Override public boolean accept( Node node, List parents ) { if ( node.getDependency() == null ) { return false; } else { return ArtifactUtils.isSnapshot( node.getDependency().getVersion() ); } } } @Override public void execute() throws MojoExecutionException, MojoFailureException { if ( isSkip() ) { getLog().info( "Skipping plugin execution" ); return; } if ( !StringUtils.isEmpty( manualInclude ) ) { manualIncludes = this.parseIncludes( manualInclude ); } // If it's a manual purge, the only step is to delete from the local repo if ( manualIncludes != null && manualIncludes.size() > 0 ) { manualPurge( manualIncludes ); return; } Set purgedArtifacts = new HashSet<>(); if ( shouldPurgeAllProjectsInReactor() ) { for ( MavenProject reactorProject : reactorProjects ) { purgeLocalRepository( reactorProject, purgedArtifacts ); } } else { purgeLocalRepository( project, purgedArtifacts ); } } /** * Determines if all projects in the reactor should be purged from their dependencies. When this goal is started on * the command-line, it is always the case. When it is bound to a phase in the lifecycle, it is never the case. * * @return true if all projects in the reactor should be purged, false otherwise. */ private boolean shouldPurgeAllProjectsInReactor() { Source source = mojoExecution.getSource(); return reactorProjects.size() > 1 && source == Source.CLI; } /** * Purges the local repository for the dependencies in the given Maven project. * * @param theProject Maven project. * @param purgedArtifacts The artifacts that were already purged. * @throws MojoFailureException in case of errors during the purge. */ private void purgeLocalRepository( MavenProject theProject, Set purgedArtifacts ) throws MojoFailureException { List dependencies = theProject.getDependencies(); TransformableFilter dependencyFilter = createPurgeArtifactsFilter( theProject, dependencies, purgedArtifacts ); Set resolvedArtifactsToPurge = getFilteredResolvedArtifacts( theProject, dependencies, dependencyFilter ); if ( resolvedArtifactsToPurge.isEmpty() ) { getLog().info( "No artifacts included for purge for project: " + getProjectKey( theProject ) ); return; } purgeArtifacts( theProject, resolvedArtifactsToPurge ); purgedArtifacts.addAll( resolvedArtifactsToPurge ); if ( reResolve ) { getLog().info( "Re-resolving dependencies" ); try { reResolveArtifacts( theProject, resolvedArtifactsToPurge ); } catch ( ArtifactResolutionException e ) { String failureMessage = "Failed to refresh project dependencies for: " + theProject.getId(); throw new MojoFailureException( failureMessage, e ); } } } /** * Purge/Delete artifacts from the local repository according to the given patterns. * * @param theIncludes The includes. * @throws MojoExecutionException in case of an error. */ private void manualPurge( List theIncludes ) throws MojoExecutionException { MessageBuilder messageBuilder = MessageUtils.buffer(); getLog().info( messageBuilder.a( "Deleting " ).strong( theIncludes.size() ) .a( " manual " ) .a( theIncludes.size() != 1 ? "dependencies" : "dependency" ) .a( " from " ) .strong( localRepository.getBasedir() ) .toString() ); for ( String gavPattern : theIncludes ) { if ( StringUtils.isEmpty( gavPattern ) ) { getLog().debug( "Skipping empty gav pattern" ); continue; } String relativePath = gavToPath( gavPattern ); if ( StringUtils.isEmpty( relativePath ) ) { getLog().debug( "Skipping empty relative path for gav pattern: " + gavPattern ); continue; } File purgeDir = new File( localRepository.getBasedir(), relativePath ); if ( purgeDir.exists() ) { getLog().debug( "Deleting directory: " + purgeDir ); try { FileUtils.deleteDirectory( purgeDir ); } catch ( IOException e ) { throw new MojoExecutionException( "Unable to purge directory: " + purgeDir ); } } else { getLog().debug( "Directory: " + purgeDir + " doesn't exist" ); } } } /** * Convert a groupId:artifactId:version to a file system path * * @param gav the groupId:artifactId:version string * @return the corresponding path */ private String gavToPath( String gav ) { if ( StringUtils.isEmpty( gav ) ) { return null; } String[] pathComponents = gav.split( ":" ); StringBuilder path = new StringBuilder( pathComponents[0].replace( '.', '/' ) ); for ( int i = 1; i < pathComponents.length; ++i ) { path.append( "/" ).append( pathComponents[i] ); } return path.toString(); } /** * Create the includes exclude filter to use when resolving and purging dependencies Also excludes any "system" * scope dependencies * * @param theProject The Maven project. * @param dependencies The dependencies to use as a reference if we're excluding transitive dependencies * @param purgedArtifacts The artifacts already purged. * @return the created filter */ private TransformableFilter createPurgeArtifactsFilter( MavenProject theProject, List dependencies, Set purgedArtifacts ) { List subFilters = new ArrayList<>(); // System dependencies should never be purged subFilters.add( ScopeFilter.excluding( Artifact.SCOPE_SYSTEM ) ); if ( this.snapshotsOnly ) { subFilters.add( new SnapshotsFilter() ); } // The CLI includes/excludes overrides configuration in the pom if ( !StringUtils.isEmpty( this.include ) ) { this.includes = parseIncludes( this.include ); } if ( this.includes != null ) { subFilters.add( new PatternInclusionsFilter( includes ) ); } if ( !StringUtils.isEmpty( this.exclude ) ) { this.excludes = parseIncludes( this.exclude ); } if ( this.excludes != null ) { subFilters.add( new PatternExclusionsFilter( excludes ) ); } if ( !actTransitively ) { subFilters.add( new DirectDependencyFilter( theProject.getArtifact(), dependencies ) ); } List exclusions = new ArrayList<>( reactorProjects.size() ); // It doesn't make sense to include projects from the reactor here since they're likely not able to be resolved for ( MavenProject reactorProject : reactorProjects ) { exclusions.add( toPatternExcludes( reactorProject.getArtifact() ) ); } // There is no need to consider a second time artifacts that were already purged (re-resolved or not) for ( Artifact purgedArtifact : purgedArtifacts ) { exclusions.add( toPatternExcludes( purgedArtifact ) ); } subFilters.add( new PatternExclusionsFilter( exclusions ) ); return new AndFilter( subFilters ); } /** * Returns a string that represents a pattern for an exclude filter for the given artifact. * * @param artifact Artifact. * @return String representation of a pattern for an exclude filter for the given artifact. */ private String toPatternExcludes( Artifact artifact ) { return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getArtifactHandler().getExtension() + ":" + artifact.getVersion(); } /** * Convert comma separated list of includes to List object * * @param theInclude The list of includes * @return the includes list */ private List parseIncludes( String theInclude ) { List theIncludes = new ArrayList<>(); if ( theInclude != null ) { String[] elements = theInclude.split( "," ); theIncludes.addAll( Arrays.asList( elements ) ); } return theIncludes; } private Set getFilteredResolvedArtifacts( MavenProject theProject, List dependencies, TransformableFilter filter ) { try { Iterable results = dependencyResolver.resolveDependencies( session.getProjectBuildingRequest(), theProject.getModel(), filter ); Set resolvedArtifacts = new LinkedHashSet<>(); for ( ArtifactResult artResult : results ) { resolvedArtifacts.add( artResult.getArtifact() ); } return resolvedArtifacts; } catch ( DependencyResolverException e ) { getLog().info( "Unable to resolve all dependencies for: " + getProjectKey( theProject ) + ". Falling back to non-transitive mode for initial artifact resolution." ); } Set resolvedArtifacts = new LinkedHashSet<>(); ArtifactFilter artifactFilter = filter.transform( new ArtifactIncludeFilterTransformer() ); for ( Dependency dependency : dependencies ) { DefaultArtifactCoordinate coordinate = new DefaultArtifactCoordinate(); coordinate.setGroupId( dependency.getGroupId() ); coordinate.setArtifactId( dependency.getArtifactId() ); coordinate.setVersion( dependency.getVersion() ); coordinate.setExtension( artifactHandlerManager.getArtifactHandler( dependency.getType() ).getExtension() ); try { Artifact artifact = artifactResolver.resolveArtifact( session.getProjectBuildingRequest(), coordinate ).getArtifact(); if ( artifactFilter.include( artifact ) ) { resolvedArtifacts.add( artifact ); } } catch ( ArtifactResolverException e ) { getLog().debug( "Unable to resolve artifact: " + coordinate ); } } return resolvedArtifacts; } private void purgeArtifacts( MavenProject theProject, Set artifacts ) { MessageBuilder messageBuilder = MessageUtils.buffer(); getLog().info( messageBuilder.a( "Deleting " ).strong( artifacts.size() ) .a( " " ).strong( actTransitively ? "transitive" : "direct" ) .a( artifacts.size() != 1 ? " dependencies" : " dependency" ) .a( " for project " ).strong( getProjectKey( theProject ) ) .a( " from " ).strong( localRepository.getBasedir() ) .a( " with artifact " ).strong( resolutionFuzziness ).a( " resolution fuzziness" ) .toString() ); for ( Artifact artifact : artifacts ) { verbose( "Purging artifact: " + artifact.getId() ); File deleteTarget = findDeleteTarget( artifact ); verbose( "Deleting: " + deleteTarget ); if ( deleteTarget.isDirectory() ) { try { FileUtils.deleteDirectory( deleteTarget ); } catch ( IOException e ) { getLog().warn( "Unable to purge local repository location: " + deleteTarget, e ); } } else { if ( !deleteTarget.delete() ) { deleteTarget.deleteOnExit(); getLog().warn( "Unable to purge local repository location immediately: " + deleteTarget ); } } artifact.setResolved( false ); } } private void reResolveArtifacts( MavenProject theProject, Set artifacts ) throws ArtifactResolutionException { // Always need to re-resolve the poms in case they were purged along with the artifact // because Maven 2 will not automatically re-resolve them when resolving the artifact for ( Artifact artifact : artifacts ) { verbose( "Resolving artifact: " + artifact.getId() ); try { //CHECKSTYLE_OFF: LineLength artifactResolver.resolveArtifact( session.getProjectBuildingRequest(), TransferUtils.toArtifactCoordinate( artifact ) ); //CHECKSTYLE_ON: LineLength } catch ( ArtifactResolverException e ) { verbose( e.getMessage() ); } } List missingArtifacts = new ArrayList<>(); for ( Artifact artifact : artifacts ) { try { artifactResolver.resolveArtifact( session.getProjectBuildingRequest(), artifact ); } catch ( ArtifactResolverException e ) { verbose( e.getMessage() ); missingArtifacts.add( artifact ); } } if ( missingArtifacts.size() > 0 ) { StringBuilder message = new StringBuilder( "required artifacts missing:" ); message.append( System.lineSeparator() ); for ( Artifact missingArtifact : missingArtifacts ) { message.append( " " ).append( missingArtifact.getId() ).append( System.lineSeparator() ); } message.append( System.lineSeparator() ); message.append( "for the artifact:" ); throw new ArtifactResolutionException( message.toString(), theProject.getArtifact(), theProject.getRemoteArtifactRepositories() ); } } private File findDeleteTarget( Artifact artifact ) { // Use localRepository.pathOf() in case artifact.getFile() is not set File deleteTarget = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); if ( GROUP_ID_FUZZINESS.equals( resolutionFuzziness ) ) { // get the groupId dir. deleteTarget = deleteTarget.getParentFile().getParentFile().getParentFile(); } 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; } private void verbose( String message ) { if ( verbose || getLog().isDebugEnabled() ) { getLog().info( message ); } } private String getProjectKey( MavenProject project ) { return project.getArtifactId(); } /** * @return {@link #skip} */ public boolean isSkip() { return skip; } /** * @param skip {@link #skip} */ public void setSkip( boolean skip ) { this.skip = skip; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy