View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.dependency;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.HashSet;
26  import java.util.LinkedHashSet;
27  import java.util.List;
28  import java.util.Set;
29  
30  import org.apache.maven.artifact.Artifact;
31  import org.apache.maven.artifact.ArtifactUtils;
32  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
33  import org.apache.maven.artifact.repository.ArtifactRepository;
34  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
35  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
36  import org.apache.maven.execution.MavenSession;
37  import org.apache.maven.model.Dependency;
38  import org.apache.maven.plugin.AbstractMojo;
39  import org.apache.maven.plugin.MojoExecution;
40  import org.apache.maven.plugin.MojoExecution.Source;
41  import org.apache.maven.plugin.MojoExecutionException;
42  import org.apache.maven.plugin.MojoFailureException;
43  import org.apache.maven.plugins.annotations.Component;
44  import org.apache.maven.plugins.annotations.Mojo;
45  import org.apache.maven.plugins.annotations.Parameter;
46  import org.apache.maven.project.MavenProject;
47  import org.apache.maven.shared.artifact.filter.resolve.AbstractFilter;
48  import org.apache.maven.shared.artifact.filter.resolve.AndFilter;
49  import org.apache.maven.shared.artifact.filter.resolve.Node;
50  import org.apache.maven.shared.artifact.filter.resolve.PatternExclusionsFilter;
51  import org.apache.maven.shared.artifact.filter.resolve.PatternInclusionsFilter;
52  import org.apache.maven.shared.artifact.filter.resolve.ScopeFilter;
53  import org.apache.maven.shared.artifact.filter.resolve.TransformableFilter;
54  import org.apache.maven.shared.artifact.filter.resolve.transform.ArtifactIncludeFilterTransformer;
55  import org.apache.maven.shared.transfer.artifact.DefaultArtifactCoordinate;
56  import org.apache.maven.shared.transfer.artifact.TransferUtils;
57  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver;
58  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolverException;
59  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult;
60  import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver;
61  import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolverException;
62  import org.apache.maven.shared.utils.logging.MessageBuilder;
63  import org.apache.maven.shared.utils.logging.MessageUtils;
64  import org.codehaus.plexus.util.FileUtils;
65  
66  /**
67   * When run on a project, remove the project dependencies from the local repository, and optionally re-resolve them.
68   * Outside of a project, remove the manually given dependencies.
69   *
70   * @author jdcasey
71   * @since 2.0
72   */
73  @Mojo(name = "purge-local-repository", threadSafe = true, requiresProject = false)
74  public class PurgeLocalRepositoryMojo extends AbstractMojo {
75  
76      private static final String VERSION_FUZZINESS = "version";
77  
78      private static final String ARTIFACT_ID_FUZZINESS = "artifactId";
79  
80      private static final String GROUP_ID_FUZZINESS = "groupId";
81  
82      /**
83       * The Maven projects in the reactor.
84       */
85      @Parameter(defaultValue = "${reactorProjects}", readonly = true, required = true)
86      private List<MavenProject> reactorProjects;
87  
88      /**
89       * The current Maven project.
90       */
91      @Component
92      private MavenProject project;
93  
94      @Component
95      private MavenSession session;
96  
97      /**
98       * This mojo execution, used to determine if it was launched from the lifecycle or the command-line.
99       */
100     @Parameter(defaultValue = "${mojo}", required = true, readonly = true)
101     private MojoExecution mojoExecution;
102 
103     /**
104      * Artifact handler manager.
105      */
106     @Component
107     private ArtifactHandlerManager artifactHandlerManager;
108 
109     /**
110      * The list of dependencies in the form of groupId:artifactId which should BE deleted/purged from the local
111      * repository. Note that using this parameter will deactivate the normal process for purging the current project
112      * dependency tree. If this parameter is used, only the included artifacts will be purged. The manualIncludes
113      * parameter should not be used in combination with the includes/excludes parameters.
114      *
115      * @since 2.6
116      */
117     @Parameter
118     private List<String> manualIncludes;
119 
120     /**
121      * Comma-separated list of groupId:artifactId entries, which should be used to manually include artifacts for
122      * deletion. This is a command-line alternative to the <code>manualIncludes</code> parameter, since List parameters
123      * are not currently compatible with CLI specification.
124      *
125      * @since 2.6
126      */
127     @Parameter(property = "manualInclude")
128     private String manualInclude;
129 
130     /**
131      * The list of dependencies in the form of groupId:artifactId which should BE deleted/refreshed.
132      *
133      * @since 2.6
134      */
135     @Parameter
136     private List<String> includes;
137 
138     /**
139      * Comma-separated list of groupId:artifactId entries, which should be used to include artifacts for
140      * deletion/refresh. This is a command-line alternative to the <code>includes</code> parameter, since List
141      * parameters are not currently compatible with CLI specification.
142      *
143      * @since 2.6
144      */
145     @Parameter(property = "include")
146     private String include;
147 
148     /**
149      * The list of dependencies in the form of groupId:artifactId which should NOT be deleted/refreshed.
150      */
151     @Parameter
152     private List<String> excludes;
153 
154     /**
155      * Comma-separated list of groupId:artifactId entries, which should be used to exclude artifacts from
156      * deletion/refresh. This is a command-line alternative to the <code>excludes</code> parameter, since List
157      * parameters are not currently compatible with CLI specification.
158      */
159     @Parameter(property = "exclude")
160     private String exclude;
161 
162     /**
163      * Whether to re-resolve the artifacts once they have been deleted from the local repository. If you are running
164      * this mojo from the command-line, you may want to disable this. By default, artifacts will be re-resolved.
165      */
166     @Parameter(property = "reResolve", defaultValue = "true")
167     private boolean reResolve;
168 
169     /**
170      * The local repository, from which to delete artifacts.
171      */
172     @Parameter(defaultValue = "${localRepository}", readonly = true, required = true)
173     private ArtifactRepository localRepository;
174 
175     /**
176      * The dependency resolver
177      */
178     @Component
179     private DependencyResolver dependencyResolver;
180 
181     /**
182      * The artifact resolver used to re-resolve dependencies, if that option is enabled.
183      */
184     @Component
185     private ArtifactResolver artifactResolver;
186 
187     /**
188      * Determines how liberally the plugin will delete an artifact from the local repository. Values are: <br/>
189      * <ul>
190      * <li><b>file</b> - Eliminate only the artifact's file.</li>
191      * <li><b>version</b> <i>(default)</i> - Eliminate all files associated with the version of the artifact.</li>
192      * <li><b>artifactId</b> - Eliminate all files associated with the artifact's artifactId.</li>
193      * <li><b>groupId</b> - Eliminate all files associated with the artifact's groupId.</li>
194      * </ul>
195      */
196     @Parameter(property = "resolutionFuzziness", defaultValue = "version")
197     private String resolutionFuzziness;
198 
199     /**
200      * Whether this mojo should act on all transitive dependencies. Default value is true.
201      */
202     @Parameter(property = "actTransitively", defaultValue = "true")
203     private boolean actTransitively;
204 
205     /**
206      * Whether this plugin should output verbose messages. Default is false.
207      */
208     @Parameter(property = "verbose", defaultValue = "false")
209     private boolean verbose;
210 
211     /**
212      * Whether to purge only snapshot artifacts.
213      *
214      * @since 2.4
215      */
216     @Parameter(property = "snapshotsOnly", defaultValue = "false")
217     private boolean snapshotsOnly;
218 
219     /**
220      * Skip plugin execution completely.
221      *
222      * @since 2.7
223      */
224     @Parameter(property = "skip", defaultValue = "false")
225     private boolean skip;
226 
227     /**
228      * Includes only direct project dependencies.
229      */
230     private class DirectDependencyFilter extends AbstractFilter {
231         private final Artifact projectArtifact;
232 
233         private final List<Dependency> directDependencies;
234 
235         /**
236          * Default constructor
237          *
238          * @param directDependencies Set of dependencies objects which represent the direct dependencies of the project
239          */
240         DirectDependencyFilter(Artifact projectArtifact, List<Dependency> directDependencies) {
241             this.projectArtifact = projectArtifact;
242             this.directDependencies = directDependencies;
243         }
244 
245         @Override
246         public boolean accept(Node node, List<Node> parents) {
247 
248             if (artifactsGAMatch(node, projectArtifact.getGroupId(), projectArtifact.getArtifactId())) {
249                 return true;
250             }
251             for (Dependency dep : directDependencies) {
252                 if (this.artifactsGAMatch(node, dep.getGroupId(), dep.getArtifactId())) {
253                     return true;
254                 }
255             }
256             return false;
257         }
258 
259         /*
260          * Compare the groupId:artifactId of two artifacts.
261          */
262         private boolean artifactsGAMatch(Node node, String groupId, String artifactId) {
263             if (node.getDependency() == null) {
264                 return false;
265             }
266 
267             if (!node.getDependency().getGroupId().equals(groupId)) {
268                 getLog().debug("Different groupId: " + node.getDependency() + "  " + groupId);
269                 return false;
270             }
271             if (!node.getDependency().getArtifactId().equals(artifactId)) {
272                 getLog().debug("Different artifactId: " + node.getDependency() + "  " + artifactId);
273                 return false;
274             }
275             return true;
276         }
277     }
278 
279     /**
280      * Includes only snapshot artifacts
281      */
282     private static class SnapshotsFilter extends AbstractFilter {
283         @Override
284         public boolean accept(Node node, List<Node> parents) {
285             if (node.getDependency() == null) {
286                 return false;
287             } else {
288                 return ArtifactUtils.isSnapshot(node.getDependency().getVersion());
289             }
290         }
291     }
292 
293     @Override
294     public void execute() throws MojoExecutionException, MojoFailureException {
295         if (isSkip()) {
296             getLog().info("Skipping plugin execution");
297             return;
298         }
299 
300         if (!(manualInclude == null || manualInclude.isEmpty())) {
301             manualIncludes = this.parseIncludes(manualInclude);
302         }
303         // If it's a manual purge, the only step is to delete from the local repo
304         if (manualIncludes != null && !manualIncludes.isEmpty()) {
305             manualPurge(manualIncludes);
306             return;
307         }
308 
309         Set<Artifact> purgedArtifacts = new HashSet<>();
310         if (shouldPurgeAllProjectsInReactor()) {
311             for (MavenProject reactorProject : reactorProjects) {
312                 purgeLocalRepository(reactorProject, purgedArtifacts);
313             }
314         } else {
315             purgeLocalRepository(project, purgedArtifacts);
316         }
317     }
318 
319     /**
320      * Determines if all projects in the reactor should be purged from their dependencies. When this goal is started on
321      * the command-line, it is always the case. When it is bound to a phase in the lifecycle, it is never the case.
322      *
323      * @return <code>true</code> if all projects in the reactor should be purged, <code>false</code> otherwise.
324      */
325     private boolean shouldPurgeAllProjectsInReactor() {
326         Source source = mojoExecution.getSource();
327         return reactorProjects.size() > 1 && source == Source.CLI;
328     }
329 
330     /**
331      * Purges the local repository for the dependencies in the given Maven project.
332      *
333      * @param theProject Maven project.
334      * @param purgedArtifacts The artifacts that were already purged.
335      * @throws MojoFailureException in case of errors during the purge.
336      */
337     private void purgeLocalRepository(MavenProject theProject, Set<Artifact> purgedArtifacts)
338             throws MojoFailureException {
339         List<Dependency> dependencies = theProject.getDependencies();
340 
341         TransformableFilter dependencyFilter = createPurgeArtifactsFilter(theProject, dependencies, purgedArtifacts);
342 
343         Set<Artifact> resolvedArtifactsToPurge =
344                 getFilteredResolvedArtifacts(theProject, dependencies, dependencyFilter);
345 
346         if (resolvedArtifactsToPurge.isEmpty()) {
347             getLog().info("No artifacts included for purge for project: " + getProjectKey(theProject));
348             return;
349         }
350 
351         purgeArtifacts(theProject, resolvedArtifactsToPurge);
352         purgedArtifacts.addAll(resolvedArtifactsToPurge);
353 
354         if (reResolve) {
355             getLog().info("Re-resolving dependencies");
356             try {
357                 reResolveArtifacts(theProject, resolvedArtifactsToPurge);
358             } catch (ArtifactResolutionException e) {
359                 String failureMessage = "Failed to refresh project dependencies for: " + theProject.getId();
360                 throw new MojoFailureException(failureMessage, e);
361             }
362         }
363     }
364 
365     /**
366      * Purge/Delete artifacts from the local repository according to the given patterns.
367      *
368      * @param theIncludes The includes.
369      * @throws MojoExecutionException in case of an error.
370      */
371     private void manualPurge(List<String> theIncludes) throws MojoExecutionException {
372         MessageBuilder messageBuilder = MessageUtils.buffer();
373 
374         getLog().info(messageBuilder
375                 .a("Deleting ")
376                 .strong(theIncludes.size())
377                 .a(" manual ")
378                 .a(theIncludes.size() != 1 ? "dependencies" : "dependency")
379                 .a(" from ")
380                 .strong(localRepository.getBasedir())
381                 .build());
382 
383         for (String gavPattern : theIncludes) {
384             if (gavPattern == null || gavPattern.isEmpty()) {
385                 getLog().debug("Skipping empty gav pattern");
386                 continue;
387             }
388 
389             String relativePath = gavToPath(gavPattern);
390             if (relativePath == null || relativePath.isEmpty()) {
391                 getLog().debug("Skipping empty relative path for gav pattern: " + gavPattern);
392                 continue;
393             }
394 
395             File purgeDir = new File(localRepository.getBasedir(), relativePath);
396             if (purgeDir.exists()) {
397                 getLog().debug("Deleting directory: " + purgeDir);
398                 try {
399                     FileUtils.deleteDirectory(purgeDir);
400                 } catch (IOException e) {
401                     throw new MojoExecutionException("Unable to purge directory: " + purgeDir);
402                 }
403             } else {
404                 getLog().debug("Directory: " + purgeDir + " doesn't exist");
405             }
406         }
407     }
408 
409     /**
410      * Convert a groupId:artifactId:version to a file system path
411      *
412      * @param gav the groupId:artifactId:version string
413      * @return the corresponding path
414      */
415     private String gavToPath(String gav) {
416         if (gav == null || gav.isEmpty()) {
417             return null;
418         }
419 
420         String[] pathComponents = gav.split(":");
421 
422         StringBuilder path = new StringBuilder(pathComponents[0].replace('.', '/'));
423 
424         for (int i = 1; i < pathComponents.length; ++i) {
425             path.append("/").append(pathComponents[i]);
426         }
427 
428         return path.toString();
429     }
430 
431     /**
432      * Create the includes exclude filter to use when resolving and purging dependencies Also excludes any "system"
433      * scope dependencies
434      *
435      * @param theProject The Maven project.
436      * @param dependencies The dependencies to use as a reference if we're excluding transitive dependencies
437      * @param purgedArtifacts The artifacts already purged.
438      * @return the created filter
439      */
440     private TransformableFilter createPurgeArtifactsFilter(
441             MavenProject theProject, List<Dependency> dependencies, Set<Artifact> purgedArtifacts) {
442         List<TransformableFilter> subFilters = new ArrayList<>();
443 
444         // System dependencies should never be purged
445         subFilters.add(ScopeFilter.excluding(Artifact.SCOPE_SYSTEM));
446 
447         if (this.snapshotsOnly) {
448             subFilters.add(new SnapshotsFilter());
449         }
450 
451         // The CLI includes/excludes overrides configuration in the pom
452         if (!(this.include == null || this.include.isEmpty())) {
453             this.includes = parseIncludes(this.include);
454         }
455         if (this.includes != null) {
456             subFilters.add(new PatternInclusionsFilter(includes));
457         }
458 
459         if (!(this.exclude == null || this.exclude.isEmpty())) {
460             this.excludes = parseIncludes(this.exclude);
461         }
462         if (this.excludes != null) {
463             subFilters.add(new PatternExclusionsFilter(excludes));
464         }
465 
466         if (!actTransitively) {
467             subFilters.add(new DirectDependencyFilter(theProject.getArtifact(), dependencies));
468         }
469 
470         List<String> exclusions = new ArrayList<>(reactorProjects.size());
471         // It doesn't make sense to include projects from the reactor here since they're likely not able to be resolved
472         for (MavenProject reactorProject : reactorProjects) {
473             exclusions.add(toPatternExcludes(reactorProject.getArtifact()));
474         }
475         // There is no need to consider a second time artifacts that were already purged (re-resolved or not)
476         for (Artifact purgedArtifact : purgedArtifacts) {
477             exclusions.add(toPatternExcludes(purgedArtifact));
478         }
479         subFilters.add(new PatternExclusionsFilter(exclusions));
480 
481         return new AndFilter(subFilters);
482     }
483 
484     /**
485      * Returns a string that represents a pattern for an exclude filter for the given artifact.
486      *
487      * @param artifact Artifact.
488      * @return String representation of a pattern for an exclude filter for the given artifact.
489      */
490     private String toPatternExcludes(Artifact artifact) {
491         return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":"
492                 + artifact.getArtifactHandler().getExtension() + ":" + artifact.getVersion();
493     }
494 
495     /**
496      * Convert comma separated list of includes to List object
497      *
498      * @param theInclude The list of includes
499      * @return the includes list
500      */
501     private List<String> parseIncludes(String theInclude) {
502         List<String> theIncludes = new ArrayList<>();
503 
504         if (theInclude != null) {
505             String[] elements = theInclude.split(",");
506             theIncludes.addAll(Arrays.asList(elements));
507         }
508 
509         return theIncludes;
510     }
511 
512     private Set<Artifact> getFilteredResolvedArtifacts(
513             MavenProject theProject, List<Dependency> dependencies, TransformableFilter filter) {
514         try {
515             Iterable<ArtifactResult> results = dependencyResolver.resolveDependencies(
516                     session.getProjectBuildingRequest(), theProject.getModel(), filter);
517 
518             Set<Artifact> resolvedArtifacts = new LinkedHashSet<>();
519 
520             for (ArtifactResult artResult : results) {
521                 resolvedArtifacts.add(artResult.getArtifact());
522             }
523 
524             return resolvedArtifacts;
525         } catch (DependencyResolverException e) {
526             getLog().info("Unable to resolve all dependencies for: " + getProjectKey(theProject)
527                     + ". Falling back to non-transitive mode for initial artifact resolution.");
528         }
529 
530         Set<Artifact> resolvedArtifacts = new LinkedHashSet<>();
531 
532         ArtifactFilter artifactFilter = filter.transform(new ArtifactIncludeFilterTransformer());
533 
534         for (Dependency dependency : dependencies) {
535             DefaultArtifactCoordinate coordinate = new DefaultArtifactCoordinate();
536             coordinate.setGroupId(dependency.getGroupId());
537             coordinate.setArtifactId(dependency.getArtifactId());
538             coordinate.setVersion(dependency.getVersion());
539             coordinate.setExtension(artifactHandlerManager
540                     .getArtifactHandler(dependency.getType())
541                     .getExtension());
542             try {
543                 Artifact artifact = artifactResolver
544                         .resolveArtifact(session.getProjectBuildingRequest(), coordinate)
545                         .getArtifact();
546                 if (artifactFilter.include(artifact)) {
547                     resolvedArtifacts.add(artifact);
548                 }
549             } catch (ArtifactResolverException e) {
550                 getLog().debug("Unable to resolve artifact: " + coordinate);
551             }
552         }
553         return resolvedArtifacts;
554     }
555 
556     private void purgeArtifacts(MavenProject theProject, Set<Artifact> artifacts) {
557         MessageBuilder messageBuilder = MessageUtils.buffer();
558 
559         getLog().info(messageBuilder
560                 .a("Deleting ")
561                 .strong(artifacts.size())
562                 .a(" ")
563                 .strong(actTransitively ? "transitive" : "direct")
564                 .a(artifacts.size() != 1 ? " dependencies" : " dependency")
565                 .a(" for project ")
566                 .strong(getProjectKey(theProject))
567                 .a(" from ")
568                 .strong(localRepository.getBasedir())
569                 .a(" with artifact ")
570                 .strong(resolutionFuzziness)
571                 .a(" resolution fuzziness")
572                 .build());
573 
574         for (Artifact artifact : artifacts) {
575             verbose("Purging artifact: " + artifact.getId());
576 
577             File deleteTarget = findDeleteTarget(artifact);
578 
579             verbose("Deleting: " + deleteTarget);
580 
581             if (deleteTarget.isDirectory()) {
582                 try {
583                     FileUtils.deleteDirectory(deleteTarget);
584                 } catch (IOException e) {
585                     getLog().warn("Unable to purge local repository location: " + deleteTarget, e);
586                 }
587             } else {
588                 if (!deleteTarget.delete()) {
589                     deleteTarget.deleteOnExit();
590                     getLog().warn("Unable to purge local repository location immediately: " + deleteTarget);
591                 }
592             }
593             artifact.setResolved(false);
594         }
595     }
596 
597     private void reResolveArtifacts(MavenProject theProject, Set<Artifact> artifacts)
598             throws ArtifactResolutionException {
599         // Always need to re-resolve the poms in case they were purged along with the artifact
600         // because Maven 2 will not automatically re-resolve them when resolving the artifact
601         for (Artifact artifact : artifacts) {
602             verbose("Resolving artifact: " + artifact.getId());
603 
604             try {
605                 // CHECKSTYLE_OFF: LineLength
606                 artifactResolver.resolveArtifact(
607                         session.getProjectBuildingRequest(), TransferUtils.toArtifactCoordinate(artifact));
608                 // CHECKSTYLE_ON: LineLength
609             } catch (ArtifactResolverException e) {
610                 verbose(e.getMessage());
611             }
612         }
613 
614         List<Artifact> missingArtifacts = new ArrayList<>();
615 
616         for (Artifact artifact : artifacts) {
617             try {
618                 artifactResolver.resolveArtifact(session.getProjectBuildingRequest(), artifact);
619             } catch (ArtifactResolverException e) {
620                 verbose(e.getMessage());
621                 missingArtifacts.add(artifact);
622             }
623         }
624 
625         if (!missingArtifacts.isEmpty()) {
626             StringBuilder message = new StringBuilder("required artifacts missing:");
627             message.append(System.lineSeparator());
628             for (Artifact missingArtifact : missingArtifacts) {
629                 message.append("  ").append(missingArtifact.getId()).append(System.lineSeparator());
630             }
631             message.append(System.lineSeparator());
632             message.append("for the artifact:");
633 
634             throw new ArtifactResolutionException(
635                     message.toString(), theProject.getArtifact(), theProject.getRemoteArtifactRepositories());
636         }
637     }
638 
639     private File findDeleteTarget(Artifact artifact) {
640         // Use localRepository.pathOf() in case artifact.getFile() is not set
641         File deleteTarget = new File(localRepository.getBasedir(), localRepository.pathOf(artifact));
642 
643         if (GROUP_ID_FUZZINESS.equals(resolutionFuzziness)) {
644             // get the groupId dir.
645             deleteTarget = deleteTarget.getParentFile().getParentFile().getParentFile();
646         } else if (ARTIFACT_ID_FUZZINESS.equals(resolutionFuzziness)) {
647             // get the artifactId dir.
648             deleteTarget = deleteTarget.getParentFile().getParentFile();
649         } else if (VERSION_FUZZINESS.equals(resolutionFuzziness)) {
650             // get the version dir.
651             deleteTarget = deleteTarget.getParentFile();
652         }
653         // else it's file fuzziness.
654         return deleteTarget;
655     }
656 
657     private void verbose(String message) {
658         if (verbose || getLog().isDebugEnabled()) {
659             getLog().info(message);
660         }
661     }
662 
663     private String getProjectKey(MavenProject project) {
664         return project.getArtifactId();
665     }
666 
667     /**
668      * @return {@link #skip}
669      */
670     public boolean isSkip() {
671         return skip;
672     }
673 
674     /**
675      * @param skip {@link #skip}
676      */
677     public void setSkip(boolean skip) {
678         this.skip = skip;
679     }
680 }