View Javadoc

1   package org.apache.maven.plugin.dependency;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   * http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.    
20   */
21  
22  import org.apache.maven.artifact.Artifact;
23  import org.apache.maven.artifact.ArtifactUtils;
24  import org.apache.maven.artifact.factory.ArtifactFactory;
25  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
26  import org.apache.maven.artifact.repository.ArtifactRepository;
27  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
28  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
29  import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
30  import org.apache.maven.artifact.resolver.ArtifactResolver;
31  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
32  import org.apache.maven.artifact.versioning.VersionRange;
33  import org.apache.maven.model.Dependency;
34  import org.apache.maven.plugin.AbstractMojo;
35  import org.apache.maven.plugin.MojoExecutionException;
36  import org.apache.maven.plugin.MojoFailureException;
37  import org.apache.maven.plugins.annotations.Component;
38  import org.apache.maven.plugins.annotations.Mojo;
39  import org.apache.maven.plugins.annotations.Parameter;
40  import org.apache.maven.project.MavenProject;
41  import org.codehaus.plexus.util.FileUtils;
42  
43  import java.io.File;
44  import java.io.IOException;
45  import java.util.ArrayList;
46  import java.util.Arrays;
47  import java.util.Collections;
48  import java.util.HashMap;
49  import java.util.HashSet;
50  import java.util.Iterator;
51  import java.util.List;
52  import java.util.Map;
53  import java.util.Set;
54  
55  /**
56   * Remove the project dependencies from the local repository, and optionally
57   * re-resolve them.
58   *
59   * @author jdcasey
60   * @version $Id: PurgeLocalRepositoryMojo.java 1357251 2012-07-04 13:28:33Z olamy $
61   * @since 2.0
62   */
63  @Mojo( name = "purge-local-repository", aggregator = true )
64  public class PurgeLocalRepositoryMojo
65      extends AbstractMojo
66  {
67  
68      public static final String FILE_FUZZINESS = "file";
69  
70      public static final String VERSION_FUZZINESS = "version";
71  
72      public static final String ARTIFACT_ID_FUZZINESS = "artifactId";
73  
74      public static final String GROUP_ID_FUZZINESS = "groupId";
75  
76      /**
77       * The projects in the current build. Each of these is subject to
78       * refreshing.
79       */
80      @Parameter( defaultValue = "${reactorProjects}", readonly = true, required = true )
81      private List<MavenProject> projects;
82  
83      /**
84       * The list of dependencies in the form of groupId:artifactId which should
85       * NOT be deleted/refreshed. This is useful for third-party artifacts.
86       */
87      @Parameter
88      private List<String> excludes;
89  
90      /**
91       * Comma-separated list of groupId:artifactId entries, which should be used
92       * to exclude artifacts from deletion/refresh. This is a command-line
93       * alternative to the <code>excludes</code> parameter, since List
94       * parameters are not currently compatible with CLI specification.
95       */
96      @Parameter( property = "exclude" )
97      private String exclude;
98  
99      /**
100      * Whether to re-resolve the artifacts once they have been deleted from the
101      * local repository. If you are running this mojo from the command-line, you
102      * may want to disable this. By default, artifacts will be re-resolved.
103      */
104     @Parameter( property = "reResolve", defaultValue = "true" )
105     private boolean reResolve;
106 
107     /**
108      * The local repository, from which to delete artifacts.
109      */
110     @Parameter( defaultValue = "${localRepository}", readonly = true, required = true )
111     private ArtifactRepository localRepository;
112 
113     /**
114      * The artifact resolver used to re-resolve dependencies, if that option is
115      * enabled.
116      */
117     @Component
118     private ArtifactResolver resolver;
119 
120     /**
121      * The artifact metadata source used to resolve dependencies
122      */
123     @Component
124     private ArtifactMetadataSource source;
125 
126     /**
127      * Determines how liberally the plugin will delete an artifact from the
128      * local repository. Values are: <br/>
129      * <ul>
130      * <li><b>file</b> <i>(default)</i> - Eliminate only the artifact's file.</li>
131      * <li><b>version</b> - Eliminate all files associated with the artifact's
132      * version.</li>
133      * <li><b>artifactId</b> - Eliminate all files associated with the
134      * artifact's artifactId.</li>
135      * <li><b>groupId</b> - Eliminate all files associated with the artifact's
136      * groupId.</li>
137      * </ul>
138      */
139     @Parameter( property = "resolutionFuzziness", defaultValue = "file" )
140     private String resolutionFuzziness;
141 
142     /**
143      * Whether this mojo should act on all transitive dependencies. Default
144      * value is true.
145      */
146     @Parameter( property = "actTransitively", defaultValue = "true" )
147     private boolean actTransitively;
148 
149     /**
150      * Used to construct artifacts for deletion/resolution...
151      */
152     @Component
153     private ArtifactFactory factory;
154 
155     /**
156      * Whether this plugin should output verbose messages. Default is false.
157      */
158     @Parameter( property = "verbose", defaultValue = "false" )
159     private boolean verbose;
160 
161     /**
162      * Whether to purge only snapshot artifacts.
163      *
164      * @since 2.4
165      */
166     @Parameter( property = "snapshotsOnly", defaultValue = "false" )
167     private boolean snapshotsOnly;
168 
169 
170     public void execute()
171         throws MojoExecutionException, MojoFailureException
172     {
173         List<String> exclusionPatterns = buildExclusionPatternsList();
174 
175         for ( MavenProject project : projects )
176         {
177             try
178             {
179                 refreshDependenciesForProject( project, exclusionPatterns );
180             }
181             catch ( ArtifactResolutionException e )
182             {
183                 MojoFailureException failure =
184                     new MojoFailureException( this, "Failed to refresh project dependencies for: " + project.getId(),
185                                               "Artifact resolution failed for project: " + project.getId() );
186                 failure.initCause( e );
187 
188                 throw failure;
189             }
190         }
191     }
192 
193     private List<String> buildExclusionPatternsList()
194     {
195         List<String> patterns = new ArrayList<String>();
196 
197         if ( exclude != null )
198         {
199             String[] elements = exclude.split( " ?, ?" );
200 
201             patterns.addAll( Arrays.asList( elements ) );
202         }
203         else if ( excludes != null && !excludes.isEmpty() )
204         {
205             patterns.addAll( excludes );
206         }
207 
208         return patterns;
209     }
210 
211     private Map<String, Artifact> createArtifactMap( MavenProject project )
212     {
213         Map<String, Artifact> artifactMap = Collections.emptyMap();
214 
215         @SuppressWarnings( "unchecked" ) List<Dependency> dependencies = project.getDependencies();
216 
217         List<ArtifactRepository> remoteRepositories = Collections.emptyList();
218 
219         Set<Artifact> dependencyArtifacts = new HashSet<Artifact>();
220 
221         for ( Dependency dependency : dependencies )
222         {
223             VersionRange vr = VersionRange.createFromVersion( dependency.getVersion() );
224 
225             Artifact artifact =
226                 factory.createDependencyArtifact( dependency.getGroupId(), dependency.getArtifactId(), vr,
227                                                   dependency.getType(), dependency.getClassifier(),
228                                                   dependency.getScope() );
229             if ( snapshotsOnly && !artifact.isSnapshot() )
230             {
231                 continue;
232             }
233             dependencyArtifacts.add( artifact );
234         }
235 
236         if ( actTransitively )
237         {
238             try
239             {
240                 ArtifactResolutionResult result;
241 
242                 if ( snapshotsOnly )
243                 {
244                     result = resolver.resolveTransitively( dependencyArtifacts, project.getArtifact(), localRepository,
245                                                            remoteRepositories, source, new ArtifactFilter()
246                     {
247                         public boolean include( Artifact artifact )
248                         {
249                             return artifact.isSnapshot();
250                         }
251                     } );
252                 }
253                 else
254                 {
255                     result =
256                         resolver.resolveTransitively( dependencyArtifacts, project.getArtifact(), remoteRepositories,
257                                                       localRepository, source );
258                 }
259 
260                 artifactMap = ArtifactUtils.artifactMapByVersionlessId( result.getArtifacts() );
261             }
262             catch ( ArtifactResolutionException e )
263             {
264                 verbose( "Skipping: " + e.getArtifactId() + ". It cannot be resolved." );
265             }
266             catch ( ArtifactNotFoundException e )
267             {
268                 verbose( "Skipping: " + e.getArtifactId() + ". It cannot be resolved." );
269             }
270         }
271         else
272         {
273             artifactMap = new HashMap<String, Artifact>();
274             for ( Artifact artifact : dependencyArtifacts )
275             {
276                 try
277                 {
278                     resolver.resolve( artifact, remoteRepositories, localRepository );
279 
280                     artifactMap.put( ArtifactUtils.versionlessKey( artifact ), artifact );
281                 }
282                 catch ( ArtifactResolutionException e )
283                 {
284                     verbose( "Skipping: " + e.getArtifactId() + ". It cannot be resolved." );
285                 }
286                 catch ( ArtifactNotFoundException e )
287                 {
288                     verbose( "Skipping: " + e.getArtifactId() + ". It cannot be resolved." );
289                 }
290             }
291         }
292 
293         return artifactMap;
294     }
295 
296     private void verbose( String message )
297     {
298         if ( verbose || getLog().isDebugEnabled() )
299         {
300             getLog().info( message );
301         }
302     }
303 
304     private void refreshDependenciesForProject( MavenProject project, List<String> exclusionPatterns )
305         throws ArtifactResolutionException, MojoFailureException
306     {
307         Map<String, Artifact> deps = createArtifactMap( project );
308 
309         if ( deps.isEmpty() )
310         {
311             getLog().info( "Nothing to do for project: " + project.getId() );
312             return;
313         }
314 
315         if ( !exclusionPatterns.isEmpty() )
316         {
317             for ( String excludedKey : exclusionPatterns )
318             {
319                 if ( GROUP_ID_FUZZINESS.equals( resolutionFuzziness ) )
320                 {
321                     verbose( "Excluding groupId: " + excludedKey + " from refresh operation for project: "
322                                  + project.getId() );
323 
324                     for ( Iterator<Map.Entry<String, Artifact>> deps_it = deps.entrySet().iterator();
325                           deps_it.hasNext(); )
326                     {
327                         Map.Entry<String, Artifact> dependency = deps_it.next();
328 
329                         Artifact artifact = dependency.getValue();
330 
331                         if ( artifact.getGroupId().equals( excludedKey ) )
332                         {
333                             deps_it.remove();
334                         }
335                     }
336                 }
337                 else
338                 {
339                     verbose( "Excluding: " + excludedKey + " from refresh operation for project: " + project.getId() );
340 
341                     deps.remove( excludedKey );
342                 }
343             }
344         }
345 
346         verbose( "Processing dependencies for project: " + project.getId() );
347 
348         List<Artifact> missingArtifacts = new ArrayList<Artifact>();
349         for ( Map.Entry<String, Artifact> entry : deps.entrySet() )
350         {
351             Artifact artifact = entry.getValue();
352 
353             verbose( "Processing artifact: " + artifact.getId() );
354 
355             File deleteTarget = findDeleteTarget( artifact );
356 
357             verbose( "Deleting: " + deleteTarget );
358 
359             if ( deleteTarget.isDirectory() )
360             {
361                 try
362                 {
363                     FileUtils.deleteDirectory( deleteTarget );
364                 }
365                 catch ( IOException e )
366                 {
367                     throw new MojoFailureException( this, "Cannot delete dependency from the local repository: "
368                         + artifact.getId(), "Failed to delete: " + deleteTarget );
369                 }
370             }
371             else
372             {
373                 deleteTarget.delete();
374             }
375 
376             if ( reResolve )
377             {
378                 verbose( "Re-resolving." );
379 
380                 artifact.setResolved( false );
381 
382                 try
383                 {
384                     resolver.resolveAlways( artifact, project.getRemoteArtifactRepositories(), localRepository );
385                 }
386                 catch ( ArtifactResolutionException e )
387                 {
388                     getLog().debug( e.getMessage() );
389                     missingArtifacts.add( artifact );
390                 }
391                 catch ( ArtifactNotFoundException e )
392                 {
393                     getLog().debug( e.getMessage() );
394                     missingArtifacts.add( artifact );
395                 }
396             }
397         }
398 
399         if ( missingArtifacts.size() > 0 )
400         {
401             String message = "required artifacts missing:\n";
402             for ( Artifact missingArtifact : missingArtifacts )
403             {
404                 message += "  " + missingArtifact.getId() + "\n";
405             }
406             message += "\nfor the artifact:";
407 
408             throw new ArtifactResolutionException( message, project.getArtifact(),
409                                                    project.getRemoteArtifactRepositories() );
410         }
411 
412     }
413 
414     private File findDeleteTarget( Artifact artifact )
415     {
416         File deleteTarget = artifact.getFile();
417 
418         if ( GROUP_ID_FUZZINESS.equals( resolutionFuzziness ) )
419         {
420             // get the artifactId dir.
421             deleteTarget = deleteTarget.getParentFile().getParentFile();
422 
423             // get the first groupId dir.
424             deleteTarget = deleteTarget.getParentFile();
425 
426             String[] path = localRepository.pathOf( artifact ).split( "\\/" );
427 
428             // subtract the artifact filename, version dir, artifactId dir, and
429             // the first groupId
430             // dir, since we've accounted for those above.
431             int groupParts = path.length - 4;
432 
433             File parent = deleteTarget.getParentFile();
434             int count = 0;
435             while ( count++ < groupParts )
436             {
437                 // prune empty dirs back to the beginning of the groupId, if
438                 // possible.
439 
440                 // if the parent dir only has the one child file, then it's okay
441                 // to prune.
442                 if ( parent.list().length < 2 )
443                 {
444                     deleteTarget = parent;
445 
446                     // check the parent of this newly checked dir
447                     parent = deleteTarget.getParentFile();
448                 }
449                 else
450                 {
451                     // if there are more files than the one that we're
452                     // interested in killing, stop.
453                     break;
454                 }
455             }
456 
457         }
458         else if ( ARTIFACT_ID_FUZZINESS.equals( resolutionFuzziness ) )
459         {
460             // get the artifactId dir.
461             deleteTarget = deleteTarget.getParentFile().getParentFile();
462         }
463         else if ( VERSION_FUZZINESS.equals( resolutionFuzziness ) )
464         {
465             // get the version dir.
466             deleteTarget = deleteTarget.getParentFile();
467         }
468         // else it's file fuzziness.
469 
470         return deleteTarget;
471     }
472 
473 }