diff --git a/src/it/sources-targetPath/outside-target/README.md b/src/it/sources-targetPath/outside-target/README.md new file mode 100644 index 0000000..47afc41 --- /dev/null +++ b/src/it/sources-targetPath/outside-target/README.md @@ -0,0 +1,20 @@ + + +The purpose of this file is to verify that the directory specified by `` is deleted. +This verification requires a directory outside the `target` directory, otherwise its deletion could +not be distinguished from the usual deletion of `${maven.build.directory}`. diff --git a/src/it/sources-targetPath/pom.xml b/src/it/sources-targetPath/pom.xml new file mode 100644 index 0000000..3ef54a0 --- /dev/null +++ b/src/it/sources-targetPath/pom.xml @@ -0,0 +1,46 @@ + + + + 4.1.0 + + test + sources-targetPath + 1.0-SNAPSHOT + + Test <targetPath> cleaning + Check for proper cleaning of output files in directories specified by <targetPath>. + + + + + org.apache.maven.plugins + maven-clean-plugin + @pom.version@ + + + + + + ${project.basedir}/outside-target + + + + + diff --git a/src/it/sources-targetPath/setup.bsh b/src/it/sources-targetPath/setup.bsh new file mode 100644 index 0000000..a9fc136 --- /dev/null +++ b/src/it/sources-targetPath/setup.bsh @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; + +return new File(basedir, "target").exists() && new File(basedir, "outside-target").exists(); +// The opposite condition is tested in "verify.bsh". diff --git a/src/it/sources-targetPath/target/test-classes/README.md b/src/it/sources-targetPath/target/test-classes/README.md new file mode 100644 index 0000000..8b4a9af --- /dev/null +++ b/src/it/sources-targetPath/target/test-classes/README.md @@ -0,0 +1,19 @@ + + +The purpose of this file is to verify that the directory +specified by `` is still deleted. diff --git a/src/it/sources-targetPath/verify.bsh b/src/it/sources-targetPath/verify.bsh new file mode 100644 index 0000000..5022ea2 --- /dev/null +++ b/src/it/sources-targetPath/verify.bsh @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; + +return !(new File(basedir, "target").exists() || new File(basedir, "outside-target").exists()); diff --git a/src/main/java/org/apache/maven/plugins/clean/CleanMojo.java b/src/main/java/org/apache/maven/plugins/clean/CleanMojo.java index dc531d8..090e804 100644 --- a/src/main/java/org/apache/maven/plugins/clean/CleanMojo.java +++ b/src/main/java/org/apache/maven/plugins/clean/CleanMojo.java @@ -20,13 +20,21 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import org.apache.maven.api.Project; +import org.apache.maven.api.ProjectScope; import org.apache.maven.api.Session; import org.apache.maven.api.di.Inject; +import org.apache.maven.api.model.Build; import org.apache.maven.api.plugin.Log; import org.apache.maven.api.plugin.MojoException; import org.apache.maven.api.plugin.annotations.Mojo; import org.apache.maven.api.plugin.annotations.Parameter; +import org.apache.maven.api.services.ProjectManager; /** * Goal which cleans the build. @@ -52,35 +60,32 @@ public class CleanMojo implements org.apache.maven.api.plugin.Mojo { public static final String FAST_MODE_DEFER = "defer"; + /** + * The logger where to send information about what the plugin is doing. + */ @Inject private Log logger; /** - * This is where build results go. + * The directory where build results went. */ @Parameter(defaultValue = "${project.build.directory}", readonly = true, required = true) private Path directory; /** - * This is where compiled classes go. + * The directory where compiled main classes went. This is usually a sub-directory + * of {@link #directory}, but could be configured by the user to another location. */ @Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true, required = true) private Path outputDirectory; /** - * This is where compiled test classes go. + * The directory where compiled test classes went. This is usually a sub-directory + * of {@link #directory}, but could be configured by the user to another location. */ @Parameter(defaultValue = "${project.build.testOutputDirectory}", readonly = true, required = true) private Path testOutputDirectory; - /** - * This is where the site plugin generates its pages. - * - * @since 2.1.1 - */ - @Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true, required = true) - private Path reportDirectory; - /** * Sets whether the plugin runs in verbose mode. As of plugin version 2.3, the default value is derived from Maven's * global debug flag (compare command line switch {@code -X}). @@ -218,15 +223,34 @@ public class CleanMojo implements org.apache.maven.api.plugin.Mojo { @Parameter(property = "maven.clean.fastMode", defaultValue = FAST_MODE_BACKGROUND) private String fastMode; + /** + * The current session. May be null during some tests. + */ @Inject private Session session; /** - * Deletes file-sets in the following project build directory order: (source) directory, output directory, test - * directory, report directory, and then the additional file-sets. + * The current project instance. + */ + @Inject + private Project project; + + /** + * Deletes build directories and file-sets. + * Directories are deleted in the following order: * - * @throws MojoException When a directory failed to get deleted. - * @see org.apache.maven.api.plugin.Mojo#execute() + *
    + *
  1. Build directory ({@code ${project.build.directory}})
  2. + *
  3. Main classes directory ({@code ${project.build.outputDirectory}})
  4. + *
  5. Test classes directory ({@code ${project.build.testOutputDirectory}})
  6. + *
  7. Directories specified in the {@code } child of {@code } elements
  8. + *
  9. Additional file-sets
  10. + *
+ * + * Redundant directories are omitted. For example, the main classes directory will be skipped + * in the usual case where it is a sub-directory of the build directory. + * + * @throws MojoException if a directory cannot be deleted */ @Override public void execute() { @@ -263,9 +287,7 @@ public void execute() { session, logger, isVerbose(), fastDir, fastMode, followSymLinks, force, failOnError, retryOnError); try { for (Path directoryItem : getDirectories()) { - if (directoryItem != null) { - cleaner.delete(directoryItem); - } + cleaner.delete(directoryItem); } if (filesets != null) { for (Fileset fileset : filesets) { @@ -290,16 +312,53 @@ private boolean isVerbose() { } /** - * Gets the directories to clean (if any). The returned array may contain null entries. - * - * @return The directories to clean or an empty array if none, never {@code null}. + * {@return the default directories to clean, or an empty list if none} + * The list includes the directories specified in both the Maven 3 and Maven 4 ways. + * Null items and redundant directories (children of other items) are omitted. */ - private Path[] getDirectories() { - Path[] directories; + private List getDirectories() { if (excludeDefaultDirectories) { - directories = new Path[0]; - } else { - directories = new Path[] {directory, outputDirectory, testOutputDirectory, reportDirectory}; + return List.of(); + } + + // Directories declared in the Maven 3 way. + var directories = new ArrayList(Arrays.asList(directory, outputDirectory, testOutputDirectory)); + + // Directories declared in the Maven 4 way, with elements. + if (project != null && session != null) { + ProjectManager projectManager = session.getService(ProjectManager.class); + if (projectManager != null) { + final Build build = project.getBuild(); + projectManager.getSourceRoots(project).forEach((source) -> { + source.targetPath().ifPresent((targetPath) -> { + // TODO: we can replace by `Project.getOutputDirectory(scope)` if MVN-11322 is merged. + String base; + ProjectScope scope = source.scope(); + if (scope == ProjectScope.MAIN) { + base = build.getOutputDirectory(); + } else if (scope == ProjectScope.TEST) { + base = build.getTestOutputDirectory(); + } else { + base = build.getDirectory(); + } + Path dir = project.getBasedir().resolve(base); + directories.add(dir.resolve(targetPath)); + }); + }); + } + } + /* + * Remove children of included parents. In the common case where the first element in the list is + * the parent of all other directories, these loops will do only one iteration over all elements. + */ + directories.removeIf(Objects::isNull); + for (int i = 0; i < directories.size(); i++) { + final Path prefix = directories.get(i); + for (int j = directories.size(); --j >= 0; ) { + if (j != i && directories.get(j).startsWith(prefix)) { + directories.remove(j); // Keep only the base directories. + } + } } return directories; }