1 package org.apache.maven.plugins.dependency;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.HashSet;
27 import java.util.LinkedHashSet;
28 import java.util.List;
29 import java.util.Set;
30
31 import org.apache.maven.artifact.Artifact;
32 import org.apache.maven.artifact.ArtifactUtils;
33 import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
34 import org.apache.maven.artifact.repository.ArtifactRepository;
35 import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
36 import org.apache.maven.artifact.resolver.ArtifactResolutionException;
37 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
38 import org.apache.maven.execution.MavenSession;
39 import org.apache.maven.model.Dependency;
40 import org.apache.maven.plugin.AbstractMojo;
41 import org.apache.maven.plugin.MojoExecution;
42 import org.apache.maven.plugin.MojoExecution.Source;
43 import org.apache.maven.plugin.MojoExecutionException;
44 import org.apache.maven.plugin.MojoFailureException;
45 import org.apache.maven.plugins.annotations.Component;
46 import org.apache.maven.plugins.annotations.Mojo;
47 import org.apache.maven.plugins.annotations.Parameter;
48 import org.apache.maven.project.MavenProject;
49 import org.apache.maven.shared.artifact.filter.resolve.AbstractFilter;
50 import org.apache.maven.shared.artifact.filter.resolve.AndFilter;
51 import org.apache.maven.shared.artifact.filter.resolve.Node;
52 import org.apache.maven.shared.artifact.filter.resolve.PatternExclusionsFilter;
53 import org.apache.maven.shared.artifact.filter.resolve.PatternInclusionsFilter;
54 import org.apache.maven.shared.artifact.filter.resolve.ScopeFilter;
55 import org.apache.maven.shared.artifact.filter.resolve.TransformableFilter;
56 import org.apache.maven.shared.artifact.filter.resolve.transform.ArtifactIncludeFilterTransformer;
57 import org.apache.maven.shared.transfer.artifact.DefaultArtifactCoordinate;
58 import org.apache.maven.shared.transfer.artifact.TransferUtils;
59 import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver;
60 import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolverException;
61 import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult;
62 import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver;
63 import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolverException;
64 import org.apache.maven.shared.utils.logging.MessageBuilder;
65 import org.apache.maven.shared.utils.logging.MessageUtils;
66 import org.codehaus.plexus.util.FileUtils;
67 import org.codehaus.plexus.util.StringUtils;
68
69
70
71
72
73
74
75
76 @Mojo( name = "purge-local-repository", threadSafe = true, requiresProject = false )
77 public class PurgeLocalRepositoryMojo
78 extends AbstractMojo
79 {
80
81 private static final String VERSION_FUZZINESS = "version";
82
83 private static final String ARTIFACT_ID_FUZZINESS = "artifactId";
84
85 private static final String GROUP_ID_FUZZINESS = "groupId";
86
87
88
89
90 @Parameter( defaultValue = "${reactorProjects}", readonly = true, required = true )
91 private List<MavenProject> reactorProjects;
92
93
94
95
96 @Parameter( defaultValue = "${project}", readonly = true, required = true )
97 private MavenProject project;
98
99 @Parameter( defaultValue = "${session}", readonly = true, required = true )
100 private MavenSession session;
101
102
103
104
105 @Parameter( defaultValue = "${mojo}", required = true, readonly = true )
106 private MojoExecution mojoExecution;
107
108
109
110
111 @Component
112 private ArtifactHandlerManager artifactHandlerManager;
113
114
115
116
117
118
119
120
121
122 @Parameter
123 private List<String> manualIncludes;
124
125
126
127
128
129
130
131
132 @Parameter( property = "manualInclude" )
133 private String manualInclude;
134
135
136
137
138
139
140 @Parameter
141 private List<String> includes;
142
143
144
145
146
147
148
149
150 @Parameter( property = "include" )
151 private String include;
152
153
154
155
156 @Parameter
157 private List<String> excludes;
158
159
160
161
162
163
164 @Parameter( property = "exclude" )
165 private String exclude;
166
167
168
169
170
171 @Parameter( property = "reResolve", defaultValue = "true" )
172 private boolean reResolve;
173
174
175
176
177 @Parameter( defaultValue = "${localRepository}", readonly = true, required = true )
178 private ArtifactRepository localRepository;
179
180
181
182
183 @Component
184 private DependencyResolver dependencyResolver;
185
186
187
188
189 @Component
190 private ArtifactResolver artifactResolver;
191
192
193
194
195
196
197
198
199
200
201 @Parameter( property = "resolutionFuzziness", defaultValue = "version" )
202 private String resolutionFuzziness;
203
204
205
206
207 @Parameter( property = "actTransitively", defaultValue = "true" )
208 private boolean actTransitively;
209
210
211
212
213 @Parameter( property = "verbose", defaultValue = "false" )
214 private boolean verbose;
215
216
217
218
219
220
221 @Parameter( property = "snapshotsOnly", defaultValue = "false" )
222 private boolean snapshotsOnly;
223
224
225
226
227
228
229 @Parameter( property = "skip", defaultValue = "false" )
230 private boolean skip;
231
232
233
234
235 private class DirectDependencyFilter
236 extends AbstractFilter
237 {
238 private Artifact projectArtifact;
239
240 private List<Dependency> directDependencies;
241
242
243
244
245
246
247 DirectDependencyFilter( Artifact projectArtifact, List<Dependency> directDependencies )
248 {
249 this.projectArtifact = projectArtifact;
250 this.directDependencies = directDependencies;
251 }
252
253 @Override
254 public boolean accept( Node node, List<Node> parents )
255 {
256
257 if ( artifactsGAMatch( node, projectArtifact.getGroupId(), projectArtifact.getArtifactId() ) )
258 {
259 return true;
260 }
261 for ( Dependency dep : directDependencies )
262 {
263 if ( this.artifactsGAMatch( node, dep.getGroupId(), dep.getArtifactId() ) )
264 {
265 return true;
266 }
267 }
268 return false;
269 }
270
271
272
273
274 private boolean artifactsGAMatch( Node node, String groupId, String artifactId )
275 {
276 if ( node.getDependency() == null )
277 {
278 return false;
279 }
280
281 if ( !node.getDependency().getGroupId().equals( groupId ) )
282 {
283 getLog().debug( "Different groupId: " + node.getDependency() + " " + groupId );
284 return false;
285 }
286 if ( !node.getDependency().getArtifactId().equals( artifactId ) )
287 {
288 getLog().debug( "Different artifactId: " + node.getDependency() + " " + artifactId );
289 return false;
290 }
291 return true;
292 }
293 }
294
295
296
297
298 private static class SnapshotsFilter
299 extends AbstractFilter
300 {
301 @Override
302 public boolean accept( Node node, List<Node> parents )
303 {
304 if ( node.getDependency() == null )
305 {
306 return false;
307 }
308 else
309 {
310 return ArtifactUtils.isSnapshot( node.getDependency().getVersion() );
311 }
312 }
313 }
314
315 @Override
316 public void execute()
317 throws MojoExecutionException, MojoFailureException
318 {
319 if ( isSkip() )
320 {
321 getLog().info( "Skipping plugin execution" );
322 return;
323 }
324
325 if ( !StringUtils.isEmpty( manualInclude ) )
326 {
327 manualIncludes = this.parseIncludes( manualInclude );
328 }
329
330 if ( manualIncludes != null && manualIncludes.size() > 0 )
331 {
332 manualPurge( manualIncludes );
333 return;
334 }
335
336 Set<Artifact> purgedArtifacts = new HashSet<>();
337 if ( shouldPurgeAllProjectsInReactor() )
338 {
339 for ( MavenProject reactorProject : reactorProjects )
340 {
341 purgeLocalRepository( reactorProject, purgedArtifacts );
342 }
343 }
344 else
345 {
346 purgeLocalRepository( project, purgedArtifacts );
347 }
348 }
349
350
351
352
353
354
355
356 private boolean shouldPurgeAllProjectsInReactor()
357 {
358 Source source = mojoExecution.getSource();
359 return reactorProjects.size() > 1 && source == Source.CLI;
360 }
361
362
363
364
365
366
367
368
369 private void purgeLocalRepository( MavenProject theProject, Set<Artifact> purgedArtifacts )
370 throws MojoFailureException
371 {
372 List<Dependency> dependencies = theProject.getDependencies();
373
374 TransformableFilter dependencyFilter = createPurgeArtifactsFilter( theProject, dependencies, purgedArtifacts );
375
376 Set<Artifact> resolvedArtifactsToPurge =
377 getFilteredResolvedArtifacts( theProject, dependencies, dependencyFilter );
378
379 if ( resolvedArtifactsToPurge.isEmpty() )
380 {
381 getLog().info( "No artifacts included for purge for project: " + getProjectKey( theProject ) );
382 return;
383 }
384
385 purgeArtifacts( theProject, resolvedArtifactsToPurge );
386 purgedArtifacts.addAll( resolvedArtifactsToPurge );
387
388 if ( reResolve )
389 {
390 getLog().info( "Re-resolving dependencies" );
391 ArtifactFilter artifactFilter = dependencyFilter.transform( new ArtifactIncludeFilterTransformer() );
392 try
393 {
394 reResolveArtifacts( theProject, resolvedArtifactsToPurge, artifactFilter );
395 }
396 catch ( ArtifactResolutionException | ArtifactNotFoundException e )
397 {
398 String failureMessage = "Failed to refresh project dependencies for: " + theProject.getId();
399 throw new MojoFailureException( failureMessage, e );
400 }
401 }
402 }
403
404
405
406
407
408
409
410 private void manualPurge( List<String> theIncludes )
411 throws MojoExecutionException
412 {
413 MessageBuilder messageBuilder = MessageUtils.buffer();
414
415 getLog().info( messageBuilder.a( "Deleting " ).strong( theIncludes.size() )
416 .a( " manual " )
417 .a( theIncludes.size() != 1 ? "dependencies" : "dependency" )
418 .a( " from " )
419 .strong( localRepository.getBasedir() )
420 .toString() );
421
422 for ( String gavPattern : theIncludes )
423 {
424 if ( StringUtils.isEmpty( gavPattern ) )
425 {
426 getLog().debug( "Skipping empty gav pattern" );
427 continue;
428 }
429
430 String relativePath = gavToPath( gavPattern );
431 if ( StringUtils.isEmpty( relativePath ) )
432 {
433 getLog().debug( "Skipping empty relative path for gav pattern: " + gavPattern );
434 continue;
435 }
436
437 File purgeDir = new File( localRepository.getBasedir(), relativePath );
438 if ( purgeDir.exists() )
439 {
440 getLog().debug( "Deleting directory: " + purgeDir );
441 try
442 {
443 FileUtils.deleteDirectory( purgeDir );
444 }
445 catch ( IOException e )
446 {
447 throw new MojoExecutionException( "Unable to purge directory: " + purgeDir );
448 }
449 }
450 else
451 {
452 getLog().debug( "Directory: " + purgeDir + " doesn't exist" );
453 }
454 }
455 }
456
457
458
459
460
461
462
463 private String gavToPath( String gav )
464 {
465 if ( StringUtils.isEmpty( gav ) )
466 {
467 return null;
468 }
469
470 String[] pathComponents = gav.split( ":" );
471
472 StringBuilder path = new StringBuilder( pathComponents[0].replace( '.', '/' ) );
473
474 for ( int i = 1; i < pathComponents.length; ++i )
475 {
476 path.append( "/" ).append( pathComponents[i] );
477 }
478
479 return path.toString();
480 }
481
482
483
484
485
486
487
488
489
490
491 private TransformableFilter createPurgeArtifactsFilter( MavenProject theProject, List<Dependency> dependencies,
492 Set<Artifact> purgedArtifacts )
493 {
494 List<TransformableFilter> subFilters = new ArrayList<>();
495
496
497 subFilters.add( ScopeFilter.excluding( Artifact.SCOPE_SYSTEM ) );
498
499 if ( this.snapshotsOnly )
500 {
501 subFilters.add( new SnapshotsFilter() );
502 }
503
504
505 if ( !StringUtils.isEmpty( this.include ) )
506 {
507 this.includes = parseIncludes( this.include );
508 }
509 if ( this.includes != null )
510 {
511 subFilters.add( new PatternInclusionsFilter( includes ) );
512 }
513
514 if ( !StringUtils.isEmpty( this.exclude ) )
515 {
516 this.excludes = parseIncludes( this.exclude );
517 }
518 if ( this.excludes != null )
519 {
520 subFilters.add( new PatternExclusionsFilter( excludes ) );
521 }
522
523 if ( !actTransitively )
524 {
525 subFilters.add( new DirectDependencyFilter( theProject.getArtifact(), dependencies ) );
526 }
527
528 List<String> exclusions = new ArrayList<>( reactorProjects.size() );
529
530 for ( MavenProject reactorProject : reactorProjects )
531 {
532 exclusions.add( toPatternExcludes( reactorProject.getArtifact() ) );
533 }
534
535 for ( Artifact purgedArtifact : purgedArtifacts )
536 {
537 exclusions.add( toPatternExcludes( purgedArtifact ) );
538 }
539 subFilters.add( new PatternExclusionsFilter( exclusions ) );
540
541 return new AndFilter( subFilters );
542 }
543
544
545
546
547
548
549
550 private String toPatternExcludes( Artifact artifact )
551 {
552 return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":"
553 + artifact.getArtifactHandler().getExtension() + ":" + artifact.getVersion();
554 }
555
556
557
558
559
560
561
562 private List<String> parseIncludes( String theInclude )
563 {
564 List<String> theIncludes = new ArrayList<>();
565
566 if ( theInclude != null )
567 {
568 String[] elements = theInclude.split( "," );
569 theIncludes.addAll( Arrays.asList( elements ) );
570 }
571
572 return theIncludes;
573 }
574
575 private Set<Artifact> getFilteredResolvedArtifacts( MavenProject theProject, List<Dependency> dependencies,
576 TransformableFilter filter )
577 {
578 try
579 {
580 Iterable<ArtifactResult> results =
581 dependencyResolver.resolveDependencies( session.getProjectBuildingRequest(), theProject.getModel(),
582 filter );
583
584 Set<Artifact> resolvedArtifacts = new LinkedHashSet<>();
585
586 for ( ArtifactResult artResult : results )
587 {
588 resolvedArtifacts.add( artResult.getArtifact() );
589 }
590
591 return resolvedArtifacts;
592 }
593 catch ( DependencyResolverException e )
594 {
595 getLog().info( "Unable to resolve all dependencies for: " + getProjectKey( theProject )
596 + ". Falling back to non-transitive mode for initial artifact resolution." );
597 }
598
599 Set<Artifact> resolvedArtifacts = new LinkedHashSet<>();
600
601 ArtifactFilter artifactFilter = filter.transform( new ArtifactIncludeFilterTransformer() );
602
603 for ( Dependency dependency : dependencies )
604 {
605 DefaultArtifactCoordinate coordinate = new DefaultArtifactCoordinate();
606 coordinate.setGroupId( dependency.getGroupId() );
607 coordinate.setArtifactId( dependency.getArtifactId() );
608 coordinate.setVersion( dependency.getVersion() );
609 coordinate.setExtension( artifactHandlerManager.getArtifactHandler( dependency.getType() ).getExtension() );
610 try
611 {
612 Artifact artifact =
613 artifactResolver.resolveArtifact( session.getProjectBuildingRequest(), coordinate ).getArtifact();
614 if ( artifactFilter.include( artifact ) )
615 {
616 resolvedArtifacts.add( artifact );
617 }
618 }
619 catch ( ArtifactResolverException e )
620 {
621 getLog().debug( "Unable to resolve artifact: " + coordinate );
622 }
623 }
624 return resolvedArtifacts;
625 }
626
627 private void purgeArtifacts( MavenProject theProject, Set<Artifact> artifacts )
628 throws MojoFailureException
629 {
630 MessageBuilder messageBuilder = MessageUtils.buffer();
631
632 getLog().info( messageBuilder.a( "Deleting " ).strong( artifacts.size() )
633 .a( " " ).strong( actTransitively ? "transitive" : "direct" )
634 .a( artifacts.size() != 1 ? " dependencies" : " dependency" )
635 .a( " for project " ).strong( getProjectKey( theProject ) )
636 .a( " from " ).strong( localRepository.getBasedir() )
637 .a( " with artifact " ).strong( resolutionFuzziness ).a( " resolution fuzziness" )
638 .toString() );
639
640 for ( Artifact artifact : artifacts )
641 {
642 verbose( "Purging artifact: " + artifact.getId() );
643
644 File deleteTarget = findDeleteTarget( artifact );
645
646 verbose( "Deleting: " + deleteTarget );
647
648 if ( deleteTarget.isDirectory() )
649 {
650 try
651 {
652 FileUtils.deleteDirectory( deleteTarget );
653 }
654 catch ( IOException e )
655 {
656 getLog().warn( "Unable to purge local repository location: " + deleteTarget, e );
657 }
658 }
659 else
660 {
661 if ( !deleteTarget.delete() )
662 {
663 deleteTarget.deleteOnExit();
664 getLog().warn( "Unable to purge local repository location immediately: " + deleteTarget );
665 }
666 }
667 artifact.setResolved( false );
668 }
669 }
670
671 private void reResolveArtifacts( MavenProject theProject, Set<Artifact> artifacts, ArtifactFilter filter )
672 throws ArtifactResolutionException, ArtifactNotFoundException
673 {
674
675
676 for ( Artifact artifact : artifacts )
677 {
678 verbose( "Resolving artifact: " + artifact.getId() );
679
680 try
681 {
682
683 artifactResolver.resolveArtifact( session.getProjectBuildingRequest(),
684 TransferUtils.toArtifactCoordinate( artifact ) );
685
686 }
687 catch ( ArtifactResolverException e )
688 {
689 verbose( e.getMessage() );
690 }
691 }
692
693 List<Artifact> missingArtifacts = new ArrayList<>();
694
695 for ( Artifact artifact : artifacts )
696 {
697 try
698 {
699 artifactResolver.resolveArtifact( session.getProjectBuildingRequest(), artifact );
700 }
701 catch ( ArtifactResolverException e )
702 {
703 verbose( e.getMessage() );
704 missingArtifacts.add( artifact );
705 }
706 }
707
708 if ( missingArtifacts.size() > 0 )
709 {
710 StringBuilder message = new StringBuilder( "required artifacts missing:" );
711 message.append( System.lineSeparator() );
712 for ( Artifact missingArtifact : missingArtifacts )
713 {
714 message.append( " " ).append( missingArtifact.getId() ).append( System.lineSeparator() );
715 }
716 message.append( System.lineSeparator() );
717 message.append( "for the artifact:" );
718
719 throw new ArtifactResolutionException( message.toString(), theProject.getArtifact(),
720 theProject.getRemoteArtifactRepositories() );
721 }
722 }
723
724 private File findDeleteTarget( Artifact artifact )
725 {
726
727 File deleteTarget = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) );
728
729 if ( GROUP_ID_FUZZINESS.equals( resolutionFuzziness ) )
730 {
731
732 deleteTarget = deleteTarget.getParentFile().getParentFile().getParentFile();
733 }
734 else if ( ARTIFACT_ID_FUZZINESS.equals( resolutionFuzziness ) )
735 {
736
737 deleteTarget = deleteTarget.getParentFile().getParentFile();
738 }
739 else if ( VERSION_FUZZINESS.equals( resolutionFuzziness ) )
740 {
741
742 deleteTarget = deleteTarget.getParentFile();
743 }
744
745 return deleteTarget;
746 }
747
748 private void verbose( String message )
749 {
750 if ( verbose || getLog().isDebugEnabled() )
751 {
752 getLog().info( message );
753 }
754 }
755
756 private String getProjectKey( MavenProject project )
757 {
758 return project.getArtifactId();
759 }
760
761
762
763
764 public boolean isSkip()
765 {
766 return skip;
767 }
768
769
770
771
772 public void setSkip( boolean skip )
773 {
774 this.skip = skip;
775 }
776 }