2020
2121import java .io .IOException ;
2222import java .nio .file .Path ;
23+ import java .util .ArrayList ;
24+ import java .util .Arrays ;
25+ import java .util .List ;
26+ import java .util .Objects ;
2327
28+ import org .apache .maven .api .Project ;
29+ import org .apache .maven .api .ProjectScope ;
2430import org .apache .maven .api .Session ;
2531import org .apache .maven .api .di .Inject ;
32+ import org .apache .maven .api .model .Build ;
2633import org .apache .maven .api .plugin .Log ;
2734import org .apache .maven .api .plugin .MojoException ;
2835import org .apache .maven .api .plugin .annotations .Mojo ;
2936import org .apache .maven .api .plugin .annotations .Parameter ;
37+ import org .apache .maven .api .services .ProjectManager ;
3038
3139/**
3240 * Goal which cleans the build.
@@ -52,35 +60,32 @@ public class CleanMojo implements org.apache.maven.api.plugin.Mojo {
5260
5361 public static final String FAST_MODE_DEFER = "defer" ;
5462
63+ /**
64+ * The logger where to send information about what the plugin is doing.
65+ */
5566 @ Inject
5667 private Log logger ;
5768
5869 /**
59- * This is where build results go .
70+ * The directory where build results went .
6071 */
6172 @ Parameter (defaultValue = "${project.build.directory}" , readonly = true , required = true )
6273 private Path directory ;
6374
6475 /**
65- * This is where compiled classes go.
76+ * The directory where compiled main classes went. This is usually a sub-directory
77+ * of {@link #directory}, but could be configured by the user to another location.
6678 */
6779 @ Parameter (defaultValue = "${project.build.outputDirectory}" , readonly = true , required = true )
6880 private Path outputDirectory ;
6981
7082 /**
71- * This is where compiled test classes go.
83+ * The directory where compiled test classes went. This is usually a sub-directory
84+ * of {@link #directory}, but could be configured by the user to another location.
7285 */
7386 @ Parameter (defaultValue = "${project.build.testOutputDirectory}" , readonly = true , required = true )
7487 private Path testOutputDirectory ;
7588
76- /**
77- * This is where the site plugin generates its pages.
78- *
79- * @since 2.1.1
80- */
81- @ Parameter (defaultValue = "${project.build.outputDirectory}" , readonly = true , required = true )
82- private Path reportDirectory ;
83-
8489 /**
8590 * Sets whether the plugin runs in verbose mode. As of plugin version 2.3, the default value is derived from Maven's
8691 * global debug flag (compare command line switch {@code -X}).
@@ -218,15 +223,34 @@ public class CleanMojo implements org.apache.maven.api.plugin.Mojo {
218223 @ Parameter (property = "maven.clean.fastMode" , defaultValue = FAST_MODE_BACKGROUND )
219224 private String fastMode ;
220225
226+ /**
227+ * The current session. May be null during some tests.
228+ */
221229 @ Inject
222230 private Session session ;
223231
224232 /**
225- * Deletes file-sets in the following project build directory order: (source) directory, output directory, test
226- * directory, report directory, and then the additional file-sets.
233+ * The current project instance.
234+ */
235+ @ Inject
236+ private Project project ;
237+
238+ /**
239+ * Deletes build directories and file-sets.
240+ * Directories are deleted in the following order:
227241 *
228- * @throws MojoException When a directory failed to get deleted.
229- * @see org.apache.maven.api.plugin.Mojo#execute()
242+ * <ol>
243+ * <li>Build directory ({@code ${project.build.directory}})</li>
244+ * <li>Main classes directory ({@code ${project.build.outputDirectory}})</li>
245+ * <li>Test classes directory ({@code ${project.build.testOutputDirectory}})</li>
246+ * <li>Directories specified in the {@code <targetPath>} child of {@code <source>} elements</li>
247+ * <li>Additional file-sets</li>
248+ * </ol>
249+ *
250+ * Redundant directories are omitted. For example, the main classes directory will be skipped
251+ * in the usual case where it is a sub-directory of the build directory.
252+ *
253+ * @throws MojoException if a directory cannot be deleted
230254 */
231255 @ Override
232256 public void execute () {
@@ -263,9 +287,7 @@ public void execute() {
263287 session , logger , isVerbose (), fastDir , fastMode , followSymLinks , force , failOnError , retryOnError );
264288 try {
265289 for (Path directoryItem : getDirectories ()) {
266- if (directoryItem != null ) {
267- cleaner .delete (directoryItem );
268- }
290+ cleaner .delete (directoryItem );
269291 }
270292 if (filesets != null ) {
271293 for (Fileset fileset : filesets ) {
@@ -290,16 +312,53 @@ private boolean isVerbose() {
290312 }
291313
292314 /**
293- * Gets the directories to clean (if any). The returned array may contain null entries.
294- *
295- * @return The directories to clean or an empty array if none, never {@code null} .
315+ * {@return the default directories to clean, or an empty list if none}
316+ * The list includes the directories specified in both the Maven 3 and Maven 4 ways.
317+ * Null items and redundant directories (children of other items) are omitted .
296318 */
297- private Path [] getDirectories () {
298- Path [] directories ;
319+ private List <Path > getDirectories () {
299320 if (excludeDefaultDirectories ) {
300- directories = new Path [0 ];
301- } else {
302- directories = new Path [] {directory , outputDirectory , testOutputDirectory , reportDirectory };
321+ return List .of ();
322+ }
323+
324+ // Directories declared in the Maven 3 way.
325+ var directories = new ArrayList <Path >(Arrays .asList (directory , outputDirectory , testOutputDirectory ));
326+
327+ // Directories declared in the Maven 4 way, with <source> elements.
328+ if (project != null && session != null ) {
329+ ProjectManager projectManager = session .getService (ProjectManager .class );
330+ if (projectManager != null ) {
331+ final Build build = project .getBuild ();
332+ projectManager .getSourceRoots (project ).forEach ((source ) -> {
333+ source .targetPath ().ifPresent ((targetPath ) -> {
334+ // TODO: we can replace by `Project.getOutputDirectory(scope)` if MVN-11322 is merged.
335+ String base ;
336+ ProjectScope scope = source .scope ();
337+ if (scope == ProjectScope .MAIN ) {
338+ base = build .getOutputDirectory ();
339+ } else if (scope == ProjectScope .TEST ) {
340+ base = build .getTestOutputDirectory ();
341+ } else {
342+ base = build .getDirectory ();
343+ }
344+ Path dir = project .getBasedir ().resolve (base );
345+ directories .add (dir .resolve (targetPath ));
346+ });
347+ });
348+ }
349+ }
350+ /*
351+ * Remove children of included parents. In the common case where the first element in the list is
352+ * the parent of all other directories, these loops will do only one iteration over all elements.
353+ */
354+ directories .removeIf (Objects ::isNull );
355+ for (int i = 0 ; i < directories .size (); i ++) {
356+ final Path prefix = directories .get (i );
357+ for (int j = directories .size (); --j >= 0 ; ) {
358+ if (j != i && directories .get (j ).startsWith (prefix )) {
359+ directories .remove (j ); // Keep only the base directories.
360+ }
361+ }
303362 }
304363 return directories ;
305364 }
0 commit comments