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.clean;
20  
21  import java.io.IOException;
22  import java.nio.file.Path;
23  import java.nio.file.Paths;
24  
25  import org.apache.maven.api.Session;
26  import org.apache.maven.api.di.Inject;
27  import org.apache.maven.api.plugin.Log;
28  import org.apache.maven.api.plugin.MojoException;
29  import org.apache.maven.api.plugin.annotations.Mojo;
30  import org.apache.maven.api.plugin.annotations.Parameter;
31  
32  /**
33   * Goal which cleans the build.
34   * <p>
35   * This attempts to clean a project's working directory of the files that were generated at build-time. By default, it
36   * discovers and deletes the directories configured in <code>project.build.directory</code>,
37   * <code>project.build.outputDirectory</code>, <code>project.build.testOutputDirectory</code>, and
38   * <code>project.reporting.outputDirectory</code>.
39   * </p>
40   * <p>
41   * Files outside the default may also be included in the deletion by configuring the <code>filesets</code> tag.
42   * </p>
43   *
44   * @author <a href="mailto:evenisse@maven.org">Emmanuel Venisse</a>
45   * @see org.apache.maven.plugins.clean.Fileset
46   * @since 2.0
47   */
48  @Mojo(name = "clean")
49  public class CleanMojo implements org.apache.maven.api.plugin.Mojo {
50  
51      public static final String FAST_MODE_BACKGROUND = "background";
52  
53      public static final String FAST_MODE_AT_END = "at-end";
54  
55      public static final String FAST_MODE_DEFER = "defer";
56  
57      @Inject
58      private Log logger;
59  
60      /**
61       * This is where build results go.
62       */
63      @Parameter(defaultValue = "${project.build.directory}", readonly = true, required = true)
64      private Path directory;
65  
66      /**
67       * This is where compiled classes go.
68       */
69      @Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true, required = true)
70      private Path outputDirectory;
71  
72      /**
73       * This is where compiled test classes go.
74       */
75      @Parameter(defaultValue = "${project.build.testOutputDirectory}", readonly = true, required = true)
76      private Path testOutputDirectory;
77  
78      /**
79       * This is where the site plugin generates its pages.
80       *
81       * @since 2.1.1
82       */
83      @Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true, required = true)
84      private Path reportDirectory;
85  
86      /**
87       * Sets whether the plugin runs in verbose mode. As of plugin version 2.3, the default value is derived from Maven's
88       * global debug flag (compare command line switch <code>-X</code>). <br/>
89       * Starting with <b>3.0.0</b> the property has been renamed from <code>clean.verbose</code> to
90       * <code>maven.clean.verbose</code>.
91       *
92       * @since 2.1
93       */
94      @Parameter(property = "maven.clean.verbose")
95      private Boolean verbose;
96  
97      /**
98       * The list of file sets to delete, in addition to the default directories. For example:
99       *
100      * <pre>
101      * &lt;filesets&gt;
102      *   &lt;fileset&gt;
103      *     &lt;directory&gt;src/main/generated&lt;/directory&gt;
104      *     &lt;followSymlinks&gt;false&lt;/followSymlinks&gt;
105      *     &lt;useDefaultExcludes&gt;true&lt;/useDefaultExcludes&gt;
106      *     &lt;includes&gt;
107      *       &lt;include&gt;*.java&lt;/include&gt;
108      *     &lt;/includes&gt;
109      *     &lt;excludes&gt;
110      *       &lt;exclude&gt;Template*&lt;/exclude&gt;
111      *     &lt;/excludes&gt;
112      *   &lt;/fileset&gt;
113      * &lt;/filesets&gt;
114      * </pre>
115      *
116      * @since 2.1
117      */
118     @Parameter
119     private Fileset[] filesets;
120 
121     /**
122      * Sets whether the plugin should follow symbolic links while deleting files from the default output directories of
123      * the project. Not following symlinks requires more IO operations and heap memory, regardless whether symlinks are
124      * actually present. So projects with a huge output directory that knowingly does not contain symlinks can improve
125      * performance by setting this parameter to <code>true</code>. <br/>
126      * Starting with <code>3.0.0</code> the property has been renamed from <code>clean.followSymLinks</code> to
127      * <code>maven.clean.followSymLinks</code>.
128      *
129      * @since 2.1
130      */
131     @Parameter(property = "maven.clean.followSymLinks", defaultValue = "false")
132     private boolean followSymLinks;
133 
134     /**
135      * Disables the plugin execution. <br/>
136      * Starting with <code>3.0.0</code> the property has been renamed from <code>clean.skip</code> to
137      * <code>maven.clean.skip</code>.
138      *
139      * @since 2.2
140      */
141     @Parameter(property = "maven.clean.skip", defaultValue = "false")
142     private boolean skip;
143 
144     /**
145      * Indicates whether the build will continue even if there are clean errors.
146      *
147      * @since 2.2
148      */
149     @Parameter(property = "maven.clean.failOnError", defaultValue = "true")
150     private boolean failOnError;
151 
152     /**
153      * Indicates whether the plugin should undertake additional attempts (after a short delay) to delete a file if the
154      * first attempt failed. This is meant to help deleting files that are temporarily locked by third-party tools like
155      * virus scanners or search indexing.
156      *
157      * @since 2.4.2
158      */
159     @Parameter(property = "maven.clean.retryOnError", defaultValue = "true")
160     private boolean retryOnError;
161 
162     /**
163      * Disables the deletion of the default output directories configured for a project. If set to <code>true</code>,
164      * only the files/directories selected via the parameter {@link #filesets} will be deleted. <br/>
165      * Starting with <b>3.0.0</b> the property has been renamed from <code>clean.excludeDefaultDirectories</code> to
166      * <code>maven.clean.excludeDefaultDirectories</code>.
167      *
168      * @since 2.3
169      */
170     @Parameter(property = "maven.clean.excludeDefaultDirectories", defaultValue = "false")
171     private boolean excludeDefaultDirectories;
172 
173     /**
174      * Enables fast clean if possible. If set to <code>true</code>, when the plugin is executed, a directory to
175      * be deleted will be atomically moved inside the <code>maven.clean.fastDir</code> directory and a thread will
176      * be launched to delete the needed files in the background.  When the build is completed, maven will wait
177      * until all the files have been deleted.  If any problem occurs during the atomic move of the directories,
178      * the plugin will default to the traditional deletion mechanism.
179      *
180      * @since 3.2
181      */
182     @Parameter(property = "maven.clean.fast", defaultValue = "false")
183     private boolean fast;
184 
185     /**
186      * When fast clean is specified, the <code>fastDir</code> property will be used as the location where directories
187      * to be deleted will be moved prior to background deletion.  If not specified, the
188      * <code>${maven.multiModuleProjectDirectory}/target/.clean</code> directory will be used.  If the
189      * <code>${build.directory}</code> has been modified, you'll have to adjust this property explicitly.
190      * In order for fast clean to work correctly, this directory and the various directories that will be deleted
191      * should usually reside on the same volume. The exact conditions are system dependant though, but if an atomic
192      * move is not supported, the standard deletion mechanism will be used.
193      *
194      * @since 3.2
195      * @see #fast
196      */
197     @Parameter(property = "maven.clean.fastDir")
198     private Path fastDir;
199 
200     /**
201      * Mode to use when using fast clean.  Values are: <code>background</code> to start deletion immediately and
202      * waiting for all files to be deleted when the session ends, <code>at-end</code> to indicate that the actual
203      * deletion should be performed synchronously when the session ends, or <code>defer</code> to specify that
204      * the actual file deletion should be started in the background when the session ends (this should only be used
205      * when maven is embedded in a long running process).
206      *
207      * @since 3.2
208      * @see #fast
209      */
210     @Parameter(property = "maven.clean.fastMode", defaultValue = FAST_MODE_BACKGROUND)
211     private String fastMode;
212 
213     @Inject
214     private Session session;
215 
216     /**
217      * Deletes file-sets in the following project build directory order: (source) directory, output directory, test
218      * directory, report directory, and then the additional file-sets.
219      *
220      * @throws MojoException When a directory failed to get deleted.
221      * @see org.apache.maven.api.plugin.Mojo#execute()
222      */
223     public void execute() {
224         if (skip) {
225             getLog().info("Clean is skipped.");
226             return;
227         }
228 
229         String multiModuleProjectDirectory =
230                 session != null ? session.getSystemProperties().get("maven.multiModuleProjectDirectory") : null;
231         Path fastDir;
232         if (fast && this.fastDir != null) {
233             fastDir = this.fastDir;
234         } else if (fast && multiModuleProjectDirectory != null) {
235             fastDir = Paths.get(multiModuleProjectDirectory, "target/.clean");
236         } else {
237             fastDir = null;
238             if (fast) {
239                 getLog().warn("Fast clean requires maven 3.3.1 or newer, "
240                         + "or an explicit directory to be specified with the 'fastDir' configuration of "
241                         + "this plugin, or the 'maven.clean.fastDir' user property to be set.");
242             }
243         }
244         if (fast
245                 && !FAST_MODE_BACKGROUND.equals(fastMode)
246                 && !FAST_MODE_AT_END.equals(fastMode)
247                 && !FAST_MODE_DEFER.equals(fastMode)) {
248             throw new IllegalArgumentException("Illegal value '" + fastMode + "' for fastMode. Allowed values are '"
249                     + FAST_MODE_BACKGROUND + "', '" + FAST_MODE_AT_END + "' and '" + FAST_MODE_DEFER + "'.");
250         }
251 
252         Cleaner cleaner = new Cleaner(session, getLog(), isVerbose(), fastDir, fastMode);
253 
254         try {
255             for (Path directoryItem : getDirectories()) {
256                 if (directoryItem != null) {
257                     cleaner.delete(directoryItem, null, followSymLinks, failOnError, retryOnError);
258                 }
259             }
260 
261             if (filesets != null) {
262                 for (Fileset fileset : filesets) {
263                     if (fileset.getDirectory() == null) {
264                         throw new MojoException("Missing base directory for " + fileset);
265                     }
266                     final String[] includes = fileset.getIncludes();
267                     final String[] excludes = fileset.getExcludes();
268                     final boolean useDefaultExcludes = fileset.isUseDefaultExcludes();
269                     final GlobSelector selector;
270                     if ((includes != null && includes.length != 0)
271                             || (excludes != null && excludes.length != 0)
272                             || useDefaultExcludes) {
273                         selector = new GlobSelector(includes, excludes, useDefaultExcludes);
274                     } else {
275                         selector = null;
276                     }
277                     cleaner.delete(
278                             fileset.getDirectory(), selector, fileset.isFollowSymlinks(), failOnError, retryOnError);
279                 }
280             }
281 
282         } catch (IOException e) {
283             throw new MojoException("Failed to clean project: " + e.getMessage(), e);
284         }
285     }
286 
287     /**
288      * Indicates whether verbose output is enabled.
289      *
290      * @return <code>true</code> if verbose output is enabled, <code>false</code> otherwise.
291      */
292     private boolean isVerbose() {
293         return (verbose != null) ? verbose : getLog().isDebugEnabled();
294     }
295 
296     /**
297      * Gets the directories to clean (if any). The returned array may contain null entries.
298      *
299      * @return The directories to clean or an empty array if none, never <code>null</code>.
300      */
301     private Path[] getDirectories() {
302         Path[] directories;
303         if (excludeDefaultDirectories) {
304             directories = new Path[0];
305         } else {
306             directories = new Path[] {directory, outputDirectory, testOutputDirectory, reportDirectory};
307         }
308         return directories;
309     }
310 
311     private Log getLog() {
312         return logger;
313     }
314 }