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