From 2da147458800dfe9ecc24865aef0e718ac56df57 Mon Sep 17 00:00:00 2001
From: Guillaume Nodet
Date: Mon, 24 Jun 2024 17:05:32 +0200
Subject: [PATCH 01/90] [MCLEAN-123] Switch to Maven 4 and the new api (#20)
---
.github/workflows/maven-verify.yml | 4 +-
pom.xml | 62 ++--
src/it/settings.xml | 4 +
.../apache/maven/plugins/clean/CleanMojo.java | 70 ++--
.../apache/maven/plugins/clean/Cleaner.java | 334 ++++++++++--------
.../apache/maven/plugins/clean/Fileset.java | 6 +-
.../examples/delete_additional_files.apt.vm | 2 +-
.../maven/plugins/clean/CleanMojoTest.java | 170 +++++----
.../unit/basic-clean-test/plugin-pom.xml | 35 --
.../pom.xml} | 6 +-
.../{plugin-pom.xml => pom.xml} | 0
.../{plugin-pom.xml => pom.xml} | 5 +-
.../pom.xml} | 2 +-
.../pom.xml} | 2 +-
.../pom.xml} | 2 +-
.../resources/unit/nested-clean-test/pom.xml | 35 ++
16 files changed, 411 insertions(+), 328 deletions(-)
delete mode 100644 src/test/resources/unit/basic-clean-test/plugin-pom.xml
rename src/test/resources/unit/{nested-clean-test/plugin-pom.xml => basic-clean-test/pom.xml} (76%)
rename src/test/resources/unit/empty-clean-test/{plugin-pom.xml => pom.xml} (100%)
rename src/test/resources/unit/fileset-clean-test/{plugin-pom.xml => pom.xml} (88%)
rename src/test/resources/unit/{locked-file-test/plugin-pom.xml => invalid-directory-test/pom.xml} (91%)
rename src/test/resources/unit/{missing-directory-test/plugin-pom.xml => locked-file-test/pom.xml} (91%)
rename src/test/resources/unit/{invalid-directory-test/plugin-pom.xml => missing-directory-test/pom.xml} (91%)
create mode 100644 src/test/resources/unit/nested-clean-test/pom.xml
diff --git a/.github/workflows/maven-verify.yml b/.github/workflows/maven-verify.yml
index ce4b500d..56062b54 100644
--- a/.github/workflows/maven-verify.yml
+++ b/.github/workflows/maven-verify.yml
@@ -26,4 +26,6 @@ jobs:
name: Verify
uses: apache/maven-gh-actions-shared/.github/workflows/maven-verify.yml@v4
with:
- maven4-enabled: true
+ ff-maven: "4.0.0-beta-3" # Maven version for fail-fast-build
+ maven-matrix: '[ "4.0.0-beta-3" ]'
+ jdk-matrix: '[ "17", "21" ]'
diff --git a/pom.xml b/pom.xml
index 73c3c727..a7ef0bb5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@ under the License.
maven-clean-plugin
- 3.4.1-SNAPSHOT
+ 4.0.0-SNAPSHOTmaven-pluginApache Maven Clean Plugin
@@ -61,46 +61,44 @@ under the License.
- 3.6.3
+ 4.0.0-beta-3
+ 17
+ 3.7.0
+ 4.0.0-alpha-3-SNAPSHOT
+ 4.0.0-SNAPSHOT2024-06-16T10:25:11Zorg.apache.maven
- maven-plugin-api
+ maven-api-core${mavenVersion}providedorg.apache.maven
- maven-core
+ maven-api-di${mavenVersion}provided
- org.apache.maven.resolver
- maven-resolver-api
- 1.1.1
+ org.apache.maven
+ maven-api-meta
+ ${mavenVersion}provided
+
org.codehaus.plexusplexus-utils
-
-
- org.apache.maven.plugin-tools
- maven-plugin-annotations
- provided
-
-
org.apache.maven.plugin-testingmaven-plugin-testing-harness
- 4.0.0-alpha-2
+ ${version.maven-plugin-testing}test
@@ -109,18 +107,42 @@ under the License.
test
- org.codehaus.plexus
- plexus-testing
- 1.3.0
+ org.apache.maven
+ maven-core
+ ${mavenVersion}test
- org.codehaus.plexus
- plexus-xml
+ com.google.inject
+ guice
+ 6.0.0
+ test
+
+
+ org.slf4j
+ slf4j-simple
+ 2.0.13test
+
+
+
+ org.apache.maven.plugins
+ maven-plugin-plugin
+ ${version.maven-plugin-tools}
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ true
+
+
+
+
+
run-its
diff --git a/src/it/settings.xml b/src/it/settings.xml
index c8f77f0b..5617e4e5 100644
--- a/src/it/settings.xml
+++ b/src/it/settings.xml
@@ -32,9 +32,11 @@ under the License.
@localRepositoryUrl@true
+ ignoretrue
+ ignore
@@ -44,9 +46,11 @@ under the License.
@localRepositoryUrl@true
+ ignoretrue
+ ignore
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 4f77c009..44ceb480 100644
--- a/src/main/java/org/apache/maven/plugins/clean/CleanMojo.java
+++ b/src/main/java/org/apache/maven/plugins/clean/CleanMojo.java
@@ -18,14 +18,16 @@
*/
package org.apache.maven.plugins.clean;
-import java.io.File;
import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
-import org.apache.maven.execution.MavenSession;
-import org.apache.maven.plugin.AbstractMojo;
-import org.apache.maven.plugin.MojoExecutionException;
-import org.apache.maven.plugins.annotations.Mojo;
-import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.api.Session;
+import org.apache.maven.api.di.Inject;
+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;
/**
* Goal which cleans the build.
@@ -43,8 +45,8 @@
* @see org.apache.maven.plugins.clean.Fileset
* @since 2.0
*/
-@Mojo(name = "clean", threadSafe = true)
-public class CleanMojo extends AbstractMojo {
+@Mojo(name = "clean")
+public class CleanMojo implements org.apache.maven.api.plugin.Mojo {
public static final String FAST_MODE_BACKGROUND = "background";
@@ -52,23 +54,26 @@ public class CleanMojo extends AbstractMojo {
public static final String FAST_MODE_DEFER = "defer";
+ @Inject
+ private Log logger;
+
/**
* This is where build results go.
*/
@Parameter(defaultValue = "${project.build.directory}", readonly = true, required = true)
- private File directory;
+ private Path directory;
/**
* This is where compiled classes go.
*/
@Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true, required = true)
- private File outputDirectory;
+ private Path outputDirectory;
/**
* This is where compiled test classes go.
*/
@Parameter(defaultValue = "${project.build.testOutputDirectory}", readonly = true, required = true)
- private File testOutputDirectory;
+ private Path testOutputDirectory;
/**
* This is where the site plugin generates its pages.
@@ -76,7 +81,7 @@ public class CleanMojo extends AbstractMojo {
* @since 2.1.1
*/
@Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true, required = true)
- private File reportDirectory;
+ 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
@@ -186,11 +191,11 @@ public class CleanMojo extends AbstractMojo {
* should usually reside on the same volume. The exact conditions are system dependant though, but if an atomic
* move is not supported, the standard deletion mechanism will be used.
*
- * @see #fast
* @since 3.2
+ * @see #fast
*/
@Parameter(property = "maven.clean.fastDir")
- private File fastDir;
+ private Path fastDir;
/**
* Mode to use when using fast clean. Values are: background to start deletion immediately and
@@ -199,35 +204,35 @@ public class CleanMojo extends AbstractMojo {
* the actual file deletion should be started in the background when the session ends (this should only be used
* when maven is embedded in a long running process).
*
- * @see #fast
* @since 3.2
+ * @see #fast
*/
@Parameter(property = "maven.clean.fastMode", defaultValue = FAST_MODE_BACKGROUND)
private String fastMode;
- @Parameter(defaultValue = "${session}", readonly = true)
- private MavenSession session;
+ @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.
*
- * @throws MojoExecutionException When a directory failed to get deleted.
- * @see org.apache.maven.plugin.Mojo#execute()
+ * @throws MojoException When a directory failed to get deleted.
+ * @see org.apache.maven.api.plugin.Mojo#execute()
*/
- public void execute() throws MojoExecutionException {
+ public void execute() {
if (skip) {
getLog().info("Clean is skipped.");
return;
}
String multiModuleProjectDirectory =
- session != null ? session.getSystemProperties().getProperty("maven.multiModuleProjectDirectory") : null;
- File fastDir;
+ session != null ? session.getSystemProperties().get("maven.multiModuleProjectDirectory") : null;
+ Path fastDir;
if (fast && this.fastDir != null) {
fastDir = this.fastDir;
} else if (fast && multiModuleProjectDirectory != null) {
- fastDir = new File(multiModuleProjectDirectory, "target/.clean");
+ fastDir = Paths.get(multiModuleProjectDirectory, "target/.clean");
} else {
fastDir = null;
if (fast) {
@@ -247,7 +252,7 @@ public void execute() throws MojoExecutionException {
Cleaner cleaner = new Cleaner(session, getLog(), isVerbose(), fastDir, fastMode);
try {
- for (File directoryItem : getDirectories()) {
+ for (Path directoryItem : getDirectories()) {
if (directoryItem != null) {
cleaner.delete(directoryItem, null, followSymLinks, failOnError, retryOnError);
}
@@ -256,7 +261,7 @@ public void execute() throws MojoExecutionException {
if (filesets != null) {
for (Fileset fileset : filesets) {
if (fileset.getDirectory() == null) {
- throw new MojoExecutionException("Missing base directory for " + fileset);
+ throw new MojoException("Missing base directory for " + fileset);
}
final String[] includes = fileset.getIncludes();
final String[] excludes = fileset.getExcludes();
@@ -273,8 +278,9 @@ public void execute() throws MojoExecutionException {
fileset.getDirectory(), selector, fileset.isFollowSymlinks(), failOnError, retryOnError);
}
}
+
} catch (IOException e) {
- throw new MojoExecutionException("Failed to clean project: " + e.getMessage(), e);
+ throw new MojoException("Failed to clean project: " + e.getMessage(), e);
}
}
@@ -292,13 +298,17 @@ private boolean isVerbose() {
*
* @return The directories to clean or an empty array if none, never null.
*/
- private File[] getDirectories() {
- File[] directories;
+ private Path[] getDirectories() {
+ Path[] directories;
if (excludeDefaultDirectories) {
- directories = new File[0];
+ directories = new Path[0];
} else {
- directories = new File[] {directory, outputDirectory, testOutputDirectory, reportDirectory};
+ directories = new Path[] {directory, outputDirectory, testOutputDirectory, reportDirectory};
}
return directories;
}
+
+ private Log getLog() {
+ return logger;
+ }
}
diff --git a/src/main/java/org/apache/maven/plugins/clean/Cleaner.java b/src/main/java/org/apache/maven/plugins/clean/Cleaner.java
index f5c25d27..533a2241 100644
--- a/src/main/java/org/apache/maven/plugins/clean/Cleaner.java
+++ b/src/main/java/org/apache/maven/plugins/clean/Cleaner.java
@@ -20,9 +20,6 @@
import java.io.File;
import java.io.IOException;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
@@ -30,12 +27,17 @@
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayDeque;
import java.util.Deque;
-
-import org.apache.maven.execution.ExecutionListener;
-import org.apache.maven.execution.MavenSession;
-import org.apache.maven.plugin.logging.Log;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import org.apache.maven.api.Event;
+import org.apache.maven.api.EventType;
+import org.apache.maven.api.Listener;
+import org.apache.maven.api.Session;
+import org.apache.maven.api.SessionData;
+import org.apache.maven.api.plugin.Log;
import org.codehaus.plexus.util.Os;
-import org.eclipse.aether.SessionData;
import static org.apache.maven.plugins.clean.CleanMojo.FAST_MODE_BACKGROUND;
import static org.apache.maven.plugins.clean.CleanMojo.FAST_MODE_DEFER;
@@ -49,70 +51,93 @@ class Cleaner {
private static final boolean ON_WINDOWS = Os.isFamily(Os.FAMILY_WINDOWS);
- private static final String LAST_DIRECTORY_TO_DELETE = Cleaner.class.getName() + ".lastDirectoryToDelete";
+ private static final SessionData.Key LAST_DIRECTORY_TO_DELETE =
+ SessionData.key(Path.class, Cleaner.class.getName() + ".lastDirectoryToDelete");
/**
* The maven session. This is typically non-null in a real run, but it can be during unit tests.
*/
- private final MavenSession session;
+ private final Session session;
- private final File fastDir;
+ private final Logger logDebug;
- private final String fastMode;
+ private final Logger logInfo;
+
+ private final Logger logVerbose;
- private final boolean verbose;
+ private final Logger logWarn;
- private Log log;
+ private final Path fastDir;
+
+ private final String fastMode;
/**
* Creates a new cleaner.
*
* @param session The Maven session to be used.
- * @param log The logger to use.
+ * @param log The logger to use, may be null to disable logging.
* @param verbose Whether to perform verbose logging.
* @param fastDir The explicit configured directory or to be deleted in fast mode.
* @param fastMode The fast deletion mode.
*/
- Cleaner(MavenSession session, final Log log, boolean verbose, File fastDir, String fastMode) {
+ Cleaner(Session session, Log log, boolean verbose, Path fastDir, String fastMode) {
+ logDebug = (log == null || !log.isDebugEnabled()) ? null : logger(log::debug, log::debug);
+
+ logInfo = (log == null || !log.isInfoEnabled()) ? null : logger(log::info, log::info);
+
+ logWarn = (log == null || !log.isWarnEnabled()) ? null : logger(log::warn, log::warn);
+
+ logVerbose = verbose ? logInfo : logDebug;
+
this.session = session;
- // This can't be null as the Cleaner gets it from the CleanMojo which gets it from AbstractMojo class, where it
- // is never null.
- this.log = log;
this.fastDir = fastDir;
this.fastMode = fastMode;
- this.verbose = verbose;
+ }
+
+ private Logger logger(Consumer l1, BiConsumer l2) {
+ return new Logger() {
+ @Override
+ public void log(CharSequence message) {
+ l1.accept(message);
+ }
+
+ @Override
+ public void log(CharSequence message, Throwable t) {
+ l2.accept(message, t);
+ }
+ };
}
/**
* Deletes the specified directories and its contents.
*
- * @param basedir The directory to delete, must not be null. Non-existing directories will be silently
- * ignored.
- * @param selector The selector used to determine what contents to delete, may be null to delete
- * everything.
+ * @param basedir The directory to delete, must not be null. Non-existing directories will be silently
+ * ignored.
+ * @param selector The selector used to determine what contents to delete, may be null to delete
+ * everything.
* @param followSymlinks Whether to follow symlinks.
- * @param failOnError Whether to abort with an exception in case a selected file/directory could not be deleted.
- * @param retryOnError Whether to undertake additional delete attempts in case the first attempt failed.
+ * @param failOnError Whether to abort with an exception in case a selected file/directory could not be deleted.
+ * @param retryOnError Whether to undertake additional delete attempts in case the first attempt failed.
* @throws IOException If a file/directory could not be deleted and failOnError is true.
*/
public void delete(
- File basedir, Selector selector, boolean followSymlinks, boolean failOnError, boolean retryOnError)
+ Path basedir, Selector selector, boolean followSymlinks, boolean failOnError, boolean retryOnError)
throws IOException {
- if (!basedir.isDirectory()) {
- if (!basedir.exists()) {
- if (log.isDebugEnabled()) {
- log.debug("Skipping non-existing directory " + basedir);
+ if (!Files.isDirectory(basedir)) {
+ if (!Files.exists(basedir)) {
+ if (logDebug != null) {
+ logDebug.log("Skipping non-existing directory " + basedir);
}
return;
}
throw new IOException("Invalid base directory " + basedir);
}
- if (log.isInfoEnabled()) {
- log.info("Deleting " + basedir + (selector != null ? " (" + selector + ")" : ""));
+ if (logInfo != null) {
+ logInfo.log("Deleting " + basedir + (selector != null ? " (" + selector + ")" : ""));
}
- File file = followSymlinks ? basedir : basedir.getCanonicalFile();
+ Path file = followSymlinks ? basedir : getCanonicalPath(basedir);
if (selector == null && !followSymlinks && fastDir != null && session != null) {
// If anything wrong happens, we'll just use the usual deletion mechanism
@@ -124,9 +149,8 @@ public void delete(
delete(file, "", selector, followSymlinks, failOnError, retryOnError);
}
- private boolean fastDelete(File baseDirFile) {
- Path baseDir = baseDirFile.toPath();
- Path fastDir = this.fastDir.toPath();
+ private boolean fastDelete(Path baseDir) {
+ Path fastDir = this.fastDir;
// Handle the case where we use ${maven.multiModuleProjectDirectory}/target/.clean for example
if (fastDir.toAbsolutePath().startsWith(baseDir.toAbsolutePath())) {
try {
@@ -135,7 +159,7 @@ private boolean fastDelete(File baseDirFile) {
try {
Files.move(baseDir, tmpDir, StandardCopyOption.REPLACE_EXISTING);
if (session != null) {
- session.getRepositorySession().getData().set(LAST_DIRECTORY_TO_DELETE, baseDir.toFile());
+ session.getData().set(LAST_DIRECTORY_TO_DELETE, baseDir);
}
baseDir = tmpDir;
} catch (IOException e) {
@@ -143,8 +167,8 @@ private boolean fastDelete(File baseDirFile) {
throw e;
}
} catch (IOException e) {
- if (log.isDebugEnabled()) {
- log.debug("Unable to fast delete directory: ", e);
+ if (logDebug != null) {
+ logDebug.log("Unable to fast delete directory", e);
}
return false;
}
@@ -155,10 +179,10 @@ private boolean fastDelete(File baseDirFile) {
Files.createDirectories(fastDir);
}
} catch (IOException e) {
- if (log.isDebugEnabled()) {
- log.debug(
+ if (logDebug != null) {
+ logDebug.log(
"Unable to fast delete directory as the path " + fastDir
- + " does not point to a directory or cannot be created: ",
+ + " does not point to a directory or cannot be created",
e);
}
return false;
@@ -172,11 +196,11 @@ private boolean fastDelete(File baseDirFile) {
// or any other exception occurs, an exception will be thrown in which case
// the method will return false and the usual deletion will be performed.
Files.move(baseDir, dstDir, StandardCopyOption.ATOMIC_MOVE);
- BackgroundCleaner.delete(this, tmpDir.toFile(), fastMode);
+ BackgroundCleaner.delete(this, tmpDir, fastMode);
return true;
} catch (IOException e) {
- if (log.isDebugEnabled()) {
- log.debug("Unable to fast delete directory: ", e);
+ if (logDebug != null) {
+ logDebug.log("Unable to fast delete directory", e);
}
return false;
}
@@ -185,20 +209,20 @@ private boolean fastDelete(File baseDirFile) {
/**
* Deletes the specified file or directory.
*
- * @param file The file/directory to delete, must not be null. If followSymlinks is
- * false, it is assumed that the parent file is canonical.
- * @param pathname The relative pathname of the file, using {@link File#separatorChar}, must not be
- * null.
- * @param selector The selector used to determine what contents to delete, may be null to delete
- * everything.
+ * @param file The file/directory to delete, must not be null. If followSymlinks is
+ * false, it is assumed that the parent file is canonical.
+ * @param pathname The relative pathname of the file, using {@link File#separatorChar}, must not be
+ * null.
+ * @param selector The selector used to determine what contents to delete, may be null to delete
+ * everything.
* @param followSymlinks Whether to follow symlinks.
- * @param failOnError Whether to abort with an exception in case a selected file/directory could not be deleted.
- * @param retryOnError Whether to undertake additional delete attempts in case the first attempt failed.
+ * @param failOnError Whether to abort with an exception in case a selected file/directory could not be deleted.
+ * @param retryOnError Whether to undertake additional delete attempts in case the first attempt failed.
* @return The result of the cleaning, never null.
* @throws IOException If a file/directory could not be deleted and failOnError is true.
*/
private Result delete(
- File file,
+ Path file,
String pathname,
Selector selector,
boolean followSymlinks,
@@ -207,46 +231,43 @@ private Result delete(
throws IOException {
Result result = new Result();
- boolean isDirectory = file.isDirectory();
+ boolean isDirectory = Files.isDirectory(file);
if (isDirectory) {
if (selector == null || selector.couldHoldSelected(pathname)) {
- if (followSymlinks || !isSymbolicLink(file.toPath())) {
- File canonical = followSymlinks ? file : file.getCanonicalFile();
- String[] filenames = canonical.list();
- if (filenames != null) {
- String prefix = pathname.length() > 0 ? pathname + File.separatorChar : "";
- for (int i = filenames.length - 1; i >= 0; i--) {
- String filename = filenames[i];
- File child = new File(canonical, filename);
+ final boolean isSymlink = isSymbolicLink(file);
+ Path canonical = followSymlinks ? file : getCanonicalPath(file);
+ if (followSymlinks || !isSymlink) {
+ String prefix = !pathname.isEmpty() ? pathname + File.separatorChar : "";
+ try (Stream children = Files.list(canonical)) {
+ for (Path child : children.toList()) {
result.update(delete(
- child, prefix + filename, selector, followSymlinks, failOnError, retryOnError));
+ child,
+ prefix + child.getFileName(),
+ selector,
+ followSymlinks,
+ failOnError,
+ retryOnError));
}
}
- } else if (log.isDebugEnabled()) {
- log.debug("Not recursing into symlink " + file);
+ } else if (logDebug != null) {
+ logDebug.log("Not recursing into symlink " + file);
}
- } else if (log.isDebugEnabled()) {
- log.debug("Not recursing into directory without included files " + file);
+ } else if (logDebug != null) {
+ logDebug.log("Not recursing into directory without included files " + file);
}
}
if (!result.excluded && (selector == null || selector.isSelected(pathname))) {
- String logmessage;
- if (isDirectory) {
- logmessage = "Deleting directory " + file;
- } else if (file.exists()) {
- logmessage = "Deleting file " + file;
- } else {
- logmessage = "Deleting dangling symlink " + file;
- }
-
- if (verbose && log.isInfoEnabled()) {
- log.info(logmessage);
- } else if (log.isDebugEnabled()) {
- log.debug(logmessage);
+ if (logVerbose != null) {
+ if (isDirectory) {
+ logVerbose.log("Deleting directory " + file);
+ } else if (Files.exists(file)) {
+ logVerbose.log("Deleting file " + file);
+ } else {
+ logVerbose.log("Deleting dangling symlink " + file);
+ }
}
-
result.failures += delete(file, failOnError, retryOnError);
} else {
result.excluded = true;
@@ -255,6 +276,14 @@ private Result delete(
return result;
}
+ private static Path getCanonicalPath(Path path) {
+ try {
+ return path.toRealPath();
+ } catch (IOException e) {
+ return getCanonicalPath(path.getParent()).resolve(path.getFileName());
+ }
+ }
+
private boolean isSymbolicLink(Path path) throws IOException {
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
return attrs.isSymbolicLink()
@@ -262,19 +291,13 @@ private boolean isSymbolicLink(Path path) throws IOException {
|| (attrs.isDirectory() && attrs.isOther());
}
- /**
- * Deletes the specified file, directory. If the path denotes a symlink, only the link is removed, its target is
- * left untouched.
- *
- * @param file The file/directory to delete, must not be null.
- * @param failOnError Whether to abort with an exception in case the file/directory could not be deleted.
- * @param retryOnError Whether to undertake additional delete attempts in case the first attempt failed.
- * @return 0 if the file was deleted, 1 otherwise.
- * @throws IOException If a file/directory could not be deleted and failOnError is true.
- */
- private int delete(File file, boolean failOnError, boolean retryOnError) throws IOException {
- if (!file.delete()) {
- boolean deleted = false;
+ private int delete(Path file, boolean failOnError, boolean retryOnError) throws IOException {
+ try {
+ Files.deleteIfExists(file);
+ return 0;
+ } catch (IOException e) {
+ IOException exception = new IOException("Failed to delete " + file);
+ exception.addSuppressed(e);
if (retryOnError) {
if (ON_WINDOWS) {
@@ -283,24 +306,27 @@ private int delete(File file, boolean failOnError, boolean retryOnError) throws
}
final int[] delays = {50, 250, 750};
- for (int i = 0; !deleted && i < delays.length; i++) {
+ for (int delay : delays) {
try {
- Thread.sleep(delays[i]);
- } catch (InterruptedException e) {
- // ignore
+ Thread.sleep(delay);
+ } catch (InterruptedException e2) {
+ exception.addSuppressed(e2);
+ }
+ try {
+ Files.deleteIfExists(file);
+ return 0;
+ } catch (IOException e2) {
+ exception.addSuppressed(e2);
}
- deleted = file.delete() || !file.exists();
}
- } else {
- deleted = !file.exists();
}
- if (!deleted) {
+ if (Files.exists(file)) {
if (failOnError) {
- throw new IOException("Failed to delete " + file);
+ throw new IOException("Failed to delete " + file, exception);
} else {
- if (log.isWarnEnabled()) {
- log.warn("Failed to delete " + file);
+ if (logWarn != null) {
+ logWarn.log("Failed to delete " + file, exception);
}
return 1;
}
@@ -322,25 +348,30 @@ public void update(Result result) {
}
}
+ private interface Logger {
+
+ void log(CharSequence message);
+
+ void log(CharSequence message, Throwable t);
+ }
+
private static class BackgroundCleaner extends Thread {
- private static final int NEW = 0;
- private static final int RUNNING = 1;
- private static final int STOPPED = 2;
private static BackgroundCleaner instance;
- private final Deque filesToDelete = new ArrayDeque<>();
+
+ private final Deque filesToDelete = new ArrayDeque<>();
+
private final Cleaner cleaner;
+
private final String fastMode;
- private int status = NEW;
- private BackgroundCleaner(Cleaner cleaner, File dir, String fastMode) {
- super("mvn-background-cleaner");
- this.cleaner = cleaner;
- this.fastMode = fastMode;
- init(cleaner.fastDir, dir);
- }
+ private static final int NEW = 0;
+ private static final int RUNNING = 1;
+ private static final int STOPPED = 2;
- public static void delete(Cleaner cleaner, File dir, String fastMode) {
+ private int status = NEW;
+
+ public static void delete(Cleaner cleaner, Path dir, String fastMode) {
synchronized (BackgroundCleaner.class) {
if (instance == null || !instance.doDelete(dir)) {
instance = new BackgroundCleaner(cleaner, dir, fastMode);
@@ -356,9 +387,16 @@ static void sessionEnd() {
}
}
+ private BackgroundCleaner(Cleaner cleaner, Path dir, String fastMode) {
+ super("mvn-background-cleaner");
+ this.cleaner = cleaner;
+ this.fastMode = fastMode;
+ init(cleaner.fastDir, dir);
+ }
+
public void run() {
while (true) {
- File basedir = pollNext();
+ Path basedir = pollNext();
if (basedir == null) {
break;
}
@@ -370,24 +408,25 @@ public void run() {
}
}
- synchronized void init(File fastDir, File dir) {
- if (fastDir.isDirectory()) {
- File[] children = fastDir.listFiles();
- if (children != null && children.length > 0) {
- for (File child : children) {
- doDelete(child);
+ synchronized void init(Path fastDir, Path dir) {
+ if (Files.isDirectory(fastDir)) {
+ try {
+ try (Stream children = Files.list(fastDir)) {
+ children.forEach(this::doDelete);
}
+ } catch (IOException e) {
+ throw new RuntimeException(e);
}
}
doDelete(dir);
}
- synchronized File pollNext() {
- File basedir = filesToDelete.poll();
+ synchronized Path pollNext() {
+ Path basedir = filesToDelete.poll();
if (basedir == null) {
if (cleaner.session != null) {
- SessionData data = cleaner.session.getRepositorySession().getData();
- File lastDir = (File) data.get(LAST_DIRECTORY_TO_DELETE);
+ SessionData data = cleaner.session.getData();
+ Path lastDir = (Path) data.get(LAST_DIRECTORY_TO_DELETE);
if (lastDir != null) {
data.set(LAST_DIRECTORY_TO_DELETE, null);
return lastDir;
@@ -399,7 +438,7 @@ synchronized File pollNext() {
return basedir;
}
- synchronized boolean doDelete(File dir) {
+ synchronized boolean doDelete(Path dir) {
if (status == STOPPED) {
return false;
}
@@ -421,15 +460,10 @@ synchronized boolean doDelete(File dir) {
* to outlive its main execution.
*/
private void wrapExecutionListener() {
- ExecutionListener executionListener = cleaner.session.getRequest().getExecutionListener();
- if (executionListener == null
- || !Proxy.isProxyClass(executionListener.getClass())
- || !(Proxy.getInvocationHandler(executionListener) instanceof SpyInvocationHandler)) {
- ExecutionListener listener = (ExecutionListener) Proxy.newProxyInstance(
- ExecutionListener.class.getClassLoader(),
- new Class[] {ExecutionListener.class},
- new SpyInvocationHandler(executionListener));
- cleaner.session.getRequest().setExecutionListener(listener);
+ synchronized (CleanerListener.class) {
+ if (cleaner.session.getListeners().stream().noneMatch(l -> l instanceof CleanerListener)) {
+ cleaner.session.registerListener(new CleanerListener());
+ }
}
}
@@ -440,8 +474,8 @@ synchronized void doSessionEnd() {
}
if (!FAST_MODE_DEFER.equals(fastMode)) {
try {
- if (cleaner.log.isInfoEnabled()) {
- cleaner.log.info("Waiting for background file deletion");
+ if (cleaner.logInfo != null) {
+ cleaner.logInfo.log("Waiting for background file deletion");
}
while (status != STOPPED) {
wait();
@@ -454,22 +488,12 @@ synchronized void doSessionEnd() {
}
}
- static class SpyInvocationHandler implements InvocationHandler {
- private final ExecutionListener delegate;
-
- SpyInvocationHandler(ExecutionListener delegate) {
- this.delegate = delegate;
- }
-
+ static class CleanerListener implements Listener {
@Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- if ("sessionEnded".equals(method.getName())) {
+ public void onEvent(Event event) {
+ if (event.getType() == EventType.SESSION_ENDED) {
BackgroundCleaner.sessionEnd();
}
- if (delegate != null) {
- return method.invoke(delegate, args);
- }
- return null;
}
}
}
diff --git a/src/main/java/org/apache/maven/plugins/clean/Fileset.java b/src/main/java/org/apache/maven/plugins/clean/Fileset.java
index ecd88b0f..75084115 100644
--- a/src/main/java/org/apache/maven/plugins/clean/Fileset.java
+++ b/src/main/java/org/apache/maven/plugins/clean/Fileset.java
@@ -18,7 +18,7 @@
*/
package org.apache.maven.plugins.clean;
-import java.io.File;
+import java.nio.file.Path;
import java.util.Arrays;
/**
@@ -32,7 +32,7 @@
*/
public class Fileset {
- private File directory;
+ private Path directory;
private String[] includes;
@@ -45,7 +45,7 @@ public class Fileset {
/**
* @return {@link #directory}
*/
- public File getDirectory() {
+ public Path getDirectory() {
return directory;
}
diff --git a/src/site/apt/examples/delete_additional_files.apt.vm b/src/site/apt/examples/delete_additional_files.apt.vm
index 0fa768c5..84ed0797 100644
--- a/src/site/apt/examples/delete_additional_files.apt.vm
+++ b/src/site/apt/examples/delete_additional_files.apt.vm
@@ -68,7 +68,7 @@ Delete Additional Files Not Exposed to Maven
is equivalent to:
+--------
- ${project.basedir}/some/relative/path
+ ${basedir}/some/relative/path
+--------
You could also define file set rules in a parent POM. In this case, the clean plugin adds the subproject
diff --git a/src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java b/src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java
index 22287d77..5a5d0d45 100644
--- a/src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java
+++ b/src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java
@@ -29,17 +29,17 @@
import java.nio.file.Paths;
import java.util.Collections;
-import org.apache.maven.plugin.MojoExecutionException;
-import org.apache.maven.plugin.logging.SystemStreamLog;
-import org.apache.maven.plugin.testing.junit5.InjectMojo;
-import org.apache.maven.plugin.testing.junit5.MojoTest;
+import org.apache.maven.api.plugin.MojoException;
+import org.apache.maven.api.plugin.testing.Basedir;
+import org.apache.maven.api.plugin.testing.InjectMojo;
+import org.apache.maven.api.plugin.testing.MojoTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
-import static org.apache.maven.plugin.testing.junit5.MojoExtension.setVariableValueToObject;
-import static org.codehaus.plexus.testing.PlexusExtension.getBasedir;
+import static org.apache.maven.api.plugin.testing.MojoExtension.getBasedir;
+import static org.apache.maven.api.plugin.testing.MojoExtension.setVariableValueToObject;
import static org.codehaus.plexus.util.IOUtil.copy;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -49,30 +49,25 @@
/**
* Test the clean mojo.
- *
- * @author Vincent Siveton
*/
@MojoTest
-class CleanMojoTest {
+public class CleanMojoTest {
+ private static final String LOCAL_REPO = "target/local-repo/";
+
/**
* Tests the simple removal of directories
*
* @throws Exception in case of an error.
*/
@Test
- @InjectMojo(goal = "clean", pom = "classpath:/unit/basic-clean-test/plugin-pom.xml")
- void testBasicClean(CleanMojo mojo) throws Exception {
+ @Basedir("${basedir}/target/test-classes/unit/basic-clean-test")
+ @InjectMojo(goal = "clean")
+ public void testBasicClean(CleanMojo mojo) throws Exception {
mojo.execute();
- assertFalse(
- checkExists(getBasedir() + "/target/test-classes/unit/" + "basic-clean-test/buildDirectory"),
- "Directory exists");
- assertFalse(
- checkExists(getBasedir() + "/target/test-classes/unit/basic-clean-test/" + "buildOutputDirectory"),
- "Directory exists");
- assertFalse(
- checkExists(getBasedir() + "/target/test-classes/unit/basic-clean-test/" + "buildTestDirectory"),
- "Directory exists");
+ assertFalse(checkExists(getBasedir() + "/buildDirectory"), "Directory exists");
+ assertFalse(checkExists(getBasedir() + "/buildOutputDirectory"), "Directory exists");
+ assertFalse(checkExists(getBasedir() + "/buildTestDirectory"), "Directory exists");
}
/**
@@ -81,13 +76,14 @@ void testBasicClean(CleanMojo mojo) throws Exception {
* @throws Exception in case of an error.
*/
@Test
- @InjectMojo(goal = "clean", pom = "classpath:/unit/nested-clean-test/plugin-pom.xml")
- void testCleanNestedStructure(CleanMojo mojo) throws Exception {
+ @Basedir("${basedir}/target/test-classes/unit/nested-clean-test")
+ @InjectMojo(goal = "clean")
+ public void testCleanNestedStructure(CleanMojo mojo) throws Exception {
mojo.execute();
- assertFalse(checkExists(getBasedir() + "/target/test-classes/unit/nested-clean-test/target"));
- assertFalse(checkExists(getBasedir() + "/target/test-classes/unit/nested-clean-test/target/classes"));
- assertFalse(checkExists(getBasedir() + "/target/test-classes/unit/nested-clean-test/target/test-classes"));
+ assertFalse(checkExists(getBasedir() + "/target"));
+ assertFalse(checkExists(getBasedir() + "/target/classes"));
+ assertFalse(checkExists(getBasedir() + "/target/test-classes"));
}
/**
@@ -97,17 +93,15 @@ void testCleanNestedStructure(CleanMojo mojo) throws Exception {
* @throws Exception in case of an error.
*/
@Test
- @InjectMojo(goal = "clean", pom = "classpath:/unit/empty-clean-test/plugin-pom.xml")
- void testCleanEmptyDirectories(CleanMojo mojo) throws Exception {
+ @Basedir("${basedir}/target/test-classes/unit/empty-clean-test")
+ @InjectMojo(goal = "clean")
+ public void testCleanEmptyDirectories(CleanMojo mojo) throws Exception {
mojo.execute();
- assertTrue(checkExists(getBasedir() + "/target/test-classes/unit/empty-clean-test/testDirectoryStructure"));
- assertTrue(checkExists(
- getBasedir() + "/target/test-classes/unit/empty-clean-test/" + "testDirectoryStructure/file.txt"));
- assertTrue(checkExists(getBasedir() + "/target/test-classes/unit/empty-clean-test/"
- + "testDirectoryStructure/outputDirectory"));
- assertTrue(checkExists(getBasedir() + "/target/test-classes/unit/empty-clean-test/"
- + "testDirectoryStructure/outputDirectory/file.txt"));
+ assertTrue(checkExists(getBasedir() + "/testDirectoryStructure"));
+ assertTrue(checkExists(getBasedir() + "/testDirectoryStructure/file.txt"));
+ assertTrue(checkExists(getBasedir() + "/testDirectoryStructure/outputDirectory"));
+ assertTrue(checkExists(getBasedir() + "/testDirectoryStructure/outputDirectory/file.txt"));
}
/**
@@ -116,25 +110,24 @@ void testCleanEmptyDirectories(CleanMojo mojo) throws Exception {
* @throws Exception in case of an error.
*/
@Test
- @InjectMojo(goal = "clean", pom = "classpath:/unit/fileset-clean-test/plugin-pom.xml")
- void testFilesetsClean(CleanMojo mojo) throws Exception {
+ @Basedir("${basedir}/target/test-classes/unit/fileset-clean-test")
+ @InjectMojo(goal = "clean")
+ public void testFilesetsClean(CleanMojo mojo) throws Exception {
mojo.execute();
// fileset 1
- assertTrue(checkExists(getBasedir() + "/target/test-classes/unit/fileset-clean-test/target"));
- assertTrue(checkExists(getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/classes"));
- assertFalse(checkExists(getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/test-classes"));
- assertTrue(checkExists(getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/subdir"));
- assertFalse(checkExists(getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/classes/file.txt"));
- assertTrue(checkEmpty(getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/classes"));
- assertFalse(checkEmpty(getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/subdir"));
- assertTrue(checkExists(getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/subdir/file.txt"));
+ assertTrue(checkExists(getBasedir() + "/target"));
+ assertTrue(checkExists(getBasedir() + "/target/classes"));
+ assertFalse(checkExists(getBasedir() + "/target/test-classes"));
+ assertTrue(checkExists(getBasedir() + "/target/subdir"));
+ assertFalse(checkExists(getBasedir() + "/target/classes/file.txt"));
+ assertTrue(checkEmpty(getBasedir() + "/target/classes"));
+ assertFalse(checkEmpty(getBasedir() + "/target/subdir"));
+ assertTrue(checkExists(getBasedir() + "/target/subdir/file.txt"));
// fileset 2
- assertTrue(
- checkExists(getBasedir() + "/target/test-classes/unit/fileset-clean-test/" + "buildOutputDirectory"));
- assertFalse(checkExists(
- getBasedir() + "/target/test-classes/unit/fileset-clean-test/" + "buildOutputDirectory/file.txt"));
+ assertTrue(checkExists(getBasedir() + "/" + "buildOutputDirectory"));
+ assertFalse(checkExists(getBasedir() + "/" + "buildOutputDirectory/file.txt"));
}
/**
@@ -143,9 +136,10 @@ void testFilesetsClean(CleanMojo mojo) throws Exception {
* @throws Exception in case of an error.
*/
@Test
- @InjectMojo(goal = "clean", pom = "classpath:/unit/invalid-directory-test/plugin-pom.xml")
- void testCleanInvalidDirectory(CleanMojo mojo) throws Exception {
- assertThrows(MojoExecutionException.class, mojo::execute);
+ @Basedir("${basedir}/target/test-classes/unit/invalid-directory-test")
+ @InjectMojo(goal = "clean")
+ public void testCleanInvalidDirectory(CleanMojo mojo) throws Exception {
+ assertThrows(MojoException.class, mojo::execute, "Should fail to delete a file treated as a directory");
}
/**
@@ -154,11 +148,12 @@ void testCleanInvalidDirectory(CleanMojo mojo) throws Exception {
* @throws Exception in case of an error.
*/
@Test
- @InjectMojo(goal = "clean", pom = "classpath:/unit/missing-directory-test/plugin-pom.xml")
- void testMissingDirectory(CleanMojo mojo) throws Exception {
+ @Basedir("${basedir}/target/test-classes/unit/missing-directory-test")
+ @InjectMojo(goal = "clean")
+ public void testMissingDirectory(CleanMojo mojo) throws Exception {
mojo.execute();
- assertFalse(checkExists(getBasedir() + "/target/test-classes/unit/missing-directory-test/does-not-exist"));
+ assertFalse(checkExists(getBasedir() + "/does-not-exist"));
}
/**
@@ -171,14 +166,15 @@ void testMissingDirectory(CleanMojo mojo) throws Exception {
*/
@Test
@EnabledOnOs(OS.WINDOWS)
- @InjectMojo(goal = "clean", pom = "classpath:/unit/locked-file-test/plugin-pom.xml")
- void testCleanLockedFile(CleanMojo mojo) throws Exception {
- File f = new File(getBasedir(), "target/test-classes/unit/locked-file-test/buildDirectory/file.txt");
+ @Basedir("${basedir}/target/test-classes/unit/locked-file-test")
+ @InjectMojo(goal = "clean")
+ public void testCleanLockedFile(CleanMojo mojo) throws Exception {
+ File f = new File(getBasedir(), "buildDirectory/file.txt");
try (FileChannel channel = new RandomAccessFile(f, "rw").getChannel();
FileLock ignored = channel.lock()) {
mojo.execute();
fail("Should fail to delete a file that is locked");
- } catch (MojoExecutionException expected) {
+ } catch (MojoException expected) {
assertTrue(true);
}
}
@@ -193,29 +189,29 @@ void testCleanLockedFile(CleanMojo mojo) throws Exception {
*/
@Test
@EnabledOnOs(OS.WINDOWS)
- @InjectMojo(goal = "clean", pom = "classpath:/unit/locked-file-test/plugin-pom.xml")
- void testCleanLockedFileWithNoError(CleanMojo mojo) throws Exception {
+ @Basedir("${basedir}/target/test-classes/unit/locked-file-test")
+ @InjectMojo(goal = "clean")
+ public void testCleanLockedFileWithNoError(CleanMojo mojo) throws Exception {
setVariableValueToObject(mojo, "failOnError", Boolean.FALSE);
assertNotNull(mojo);
- File f = new File(getBasedir(), "target/test-classes/unit/locked-file-test/buildDirectory/file.txt");
+ File f = new File(getBasedir(), "buildDirectory/file.txt");
try (FileChannel channel = new RandomAccessFile(f, "rw").getChannel();
FileLock ignored = channel.lock()) {
mojo.execute();
assertTrue(true);
- } catch (MojoExecutionException expected) {
+ } catch (MojoException expected) {
fail("Should display a warning when deleting a file that is locked");
}
}
/**
* Test the followLink option with windows junctions
- *
* @throws Exception
*/
@Test
@EnabledOnOs(OS.WINDOWS)
- void testFollowLinksWithWindowsJunction() throws Exception {
+ public void testFollowLinksWithWindowsJunction() throws Exception {
testSymlink((link, target) -> {
Process process = new ProcessBuilder()
.directory(link.getParent().toFile())
@@ -233,12 +229,11 @@ void testFollowLinksWithWindowsJunction() throws Exception {
/**
* Test the followLink option with sym link
- *
* @throws Exception
*/
@Test
@DisabledOnOs(OS.WINDOWS)
- void testFollowLinksWithSymLinkOnPosix() throws Exception {
+ public void testFollowLinksWithSymLinkOnPosix() throws Exception {
testSymlink((link, target) -> {
try {
Files.createSymbolicLink(link, target);
@@ -248,9 +243,13 @@ void testFollowLinksWithSymLinkOnPosix() throws Exception {
});
}
+ @FunctionalInterface
+ interface LinkCreator {
+ void createLink(Path link, Path target) throws Exception;
+ }
+
private void testSymlink(LinkCreator linkCreator) throws Exception {
- // We use the SystemStreamLog() as the AbstractMojo class, because from there the Log is always provided
- Cleaner cleaner = new Cleaner(null, new SystemStreamLog(), false, null, null);
+ Cleaner cleaner = new Cleaner(null, null, false, null, null);
Path testDir = Paths.get("target/test-classes/unit/test-dir").toAbsolutePath();
Path dirWithLnk = testDir.resolve("dir");
Path orgDir = testDir.resolve("org-dir");
@@ -263,7 +262,7 @@ private void testSymlink(LinkCreator linkCreator) throws Exception {
Files.write(file, Collections.singleton("Hello world"));
linkCreator.createLink(jctDir, orgDir);
// delete
- cleaner.delete(dirWithLnk.toFile(), null, false, true, false);
+ cleaner.delete(dirWithLnk, null, false, true, false);
// verify
assertTrue(Files.exists(file));
assertFalse(Files.exists(jctDir));
@@ -276,7 +275,7 @@ private void testSymlink(LinkCreator linkCreator) throws Exception {
Files.write(file, Collections.singleton("Hello world"));
linkCreator.createLink(jctDir, orgDir);
// delete
- cleaner.delete(dirWithLnk.toFile(), null, true, true, false);
+ cleaner.delete(dirWithLnk, null, true, true, false);
// verify
assertFalse(Files.exists(file));
assertFalse(Files.exists(jctDir));
@@ -284,6 +283,32 @@ private void testSymlink(LinkCreator linkCreator) throws Exception {
assertFalse(Files.exists(dirWithLnk));
}
+ // @Provides
+ // @Singleton
+ // private Project createProject() {
+ // ProjectStub project = new ProjectStub();
+ // project.setGroupId("myGroupId");
+ // return project;
+ // }
+
+ // @Provides
+ // @Singleton
+ // @SuppressWarnings("unused")
+ // private InternalSession createSession() {
+ // InternalSession session = SessionStub.getMockSession(LOCAL_REPO);
+ // Properties props = new Properties();
+ // props.put("basedir", MojoExtension.getBasedir());
+ // doReturn(props).when(session).getSystemProperties();
+ // return session;
+ // }
+
+ // @Provides
+ // @Singleton
+ // @SuppressWarnings("unused")
+ // private MojoExecution createMojoExecution() {
+ // return new MojoExecutionStub("default-clean", "clean");
+ // }
+
/**
* @param dir a dir or a file
* @return true if a file/dir exists, false otherwise
@@ -300,9 +325,4 @@ private boolean checkEmpty(String dir) {
File[] files = new File(dir).listFiles();
return files == null || files.length == 0;
}
-
- @FunctionalInterface
- interface LinkCreator {
- void createLink(Path link, Path target) throws Exception;
- }
}
diff --git a/src/test/resources/unit/basic-clean-test/plugin-pom.xml b/src/test/resources/unit/basic-clean-test/plugin-pom.xml
deleted file mode 100644
index 38a6000c..00000000
--- a/src/test/resources/unit/basic-clean-test/plugin-pom.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
- maven-clean-plugin
-
- ${basedir}/target/test-classes/unit/basic-clean-test/buildDirectory
- ${basedir}/target/test-classes/unit/basic-clean-test/buildOutputDirectory
- ${basedir}/target/test-classes/unit/basic-clean-test/buildTestDirectory
- true
- true
-
-
-
-
-
diff --git a/src/test/resources/unit/nested-clean-test/plugin-pom.xml b/src/test/resources/unit/basic-clean-test/pom.xml
similarity index 76%
rename from src/test/resources/unit/nested-clean-test/plugin-pom.xml
rename to src/test/resources/unit/basic-clean-test/pom.xml
index 790d14d5..ba901f06 100644
--- a/src/test/resources/unit/nested-clean-test/plugin-pom.xml
+++ b/src/test/resources/unit/basic-clean-test/pom.xml
@@ -23,9 +23,9 @@
maven-clean-plugin
- ${basedir}/target/test-classes/unit/nested-clean-test/target
- ${basedir}/target/test-classes/unit/nested-clean-test/target/classes
- ${basedir}/target/test-classes/unit/nested-clean-test/target/test-classes
+ ${project.basedir}/buildDirectory
+ ${project.basedir}/buildOutputDirectory
+ ${project.basedir}/buildTestDirectorytruetrue
diff --git a/src/test/resources/unit/empty-clean-test/plugin-pom.xml b/src/test/resources/unit/empty-clean-test/pom.xml
similarity index 100%
rename from src/test/resources/unit/empty-clean-test/plugin-pom.xml
rename to src/test/resources/unit/empty-clean-test/pom.xml
diff --git a/src/test/resources/unit/fileset-clean-test/plugin-pom.xml b/src/test/resources/unit/fileset-clean-test/pom.xml
similarity index 88%
rename from src/test/resources/unit/fileset-clean-test/plugin-pom.xml
rename to src/test/resources/unit/fileset-clean-test/pom.xml
index 5cd3ab34..2457c4ed 100644
--- a/src/test/resources/unit/fileset-clean-test/plugin-pom.xml
+++ b/src/test/resources/unit/fileset-clean-test/pom.xml
@@ -23,9 +23,10 @@
maven-clean-plugin
+ true
- ${basedir}/target/test-classes/unit/fileset-clean-test/target
+ ${project.basedir}/target**/file.txt**/test-classes/**
@@ -35,7 +36,7 @@
- ${basedir}/target/test-classes/unit/fileset-clean-test/buildOutputDirectory
+ ${project.basedir}/buildOutputDirectory**
diff --git a/src/test/resources/unit/locked-file-test/plugin-pom.xml b/src/test/resources/unit/invalid-directory-test/pom.xml
similarity index 91%
rename from src/test/resources/unit/locked-file-test/plugin-pom.xml
rename to src/test/resources/unit/invalid-directory-test/pom.xml
index 4101091f..5d35e78c 100644
--- a/src/test/resources/unit/locked-file-test/plugin-pom.xml
+++ b/src/test/resources/unit/invalid-directory-test/pom.xml
@@ -23,7 +23,7 @@
maven-clean-plugin
- ${basedir}/target/test-classes/unit/locked-file-test/buildDirectory
+ ${project.basedir}/this-is-a-filetruetrue
diff --git a/src/test/resources/unit/missing-directory-test/plugin-pom.xml b/src/test/resources/unit/locked-file-test/pom.xml
similarity index 91%
rename from src/test/resources/unit/missing-directory-test/plugin-pom.xml
rename to src/test/resources/unit/locked-file-test/pom.xml
index 15b3923b..1f2f429a 100644
--- a/src/test/resources/unit/missing-directory-test/plugin-pom.xml
+++ b/src/test/resources/unit/locked-file-test/pom.xml
@@ -23,7 +23,7 @@
maven-clean-plugin
- ${basedir}/target/test-classes/unit/missing-clean-test/does-not-exist
+ ${project.basedir}/buildDirectorytruetrue
diff --git a/src/test/resources/unit/invalid-directory-test/plugin-pom.xml b/src/test/resources/unit/missing-directory-test/pom.xml
similarity index 91%
rename from src/test/resources/unit/invalid-directory-test/plugin-pom.xml
rename to src/test/resources/unit/missing-directory-test/pom.xml
index b5621223..ec695d12 100644
--- a/src/test/resources/unit/invalid-directory-test/plugin-pom.xml
+++ b/src/test/resources/unit/missing-directory-test/pom.xml
@@ -23,7 +23,7 @@
maven-clean-plugin
- ${basedir}/target/test-classes/unit/invalid-directory-test/this-is-a-file
+ ${project.basedir}/does-not-existtruetrue
diff --git a/src/test/resources/unit/nested-clean-test/pom.xml b/src/test/resources/unit/nested-clean-test/pom.xml
new file mode 100644
index 00000000..5f9e755c
--- /dev/null
+++ b/src/test/resources/unit/nested-clean-test/pom.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+ maven-clean-plugin
+
+ ${project.basedir}/target
+ ${project.basedir}/target/classes
+ ${project.basedir}/target/test-classes
+ true
+ true
+
+
+
+
+
From 30b89ca3ac0c8e96c4fe70ba88d5fe35c8526ed9 Mon Sep 17 00:00:00 2001
From: Guillaume Nodet
Date: Wed, 26 Jun 2024 07:52:29 +0200
Subject: [PATCH 02/90] Fix license/notice (#48)
---
LICENSE | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
NOTICE | 5 ++
2 files changed, 207 insertions(+)
create mode 100644 LICENSE
create mode 100644 NOTICE
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed 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.
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 00000000..28b03487
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,5 @@
+Apache Maven Clean Plugin
+Copyright 2007-2024 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
From b9769ef83ff28b82704d4deb5ea27f422e681b70 Mon Sep 17 00:00:00 2001
From: Guillaume Nodet
Date: Wed, 26 Jun 2024 08:22:49 +0200
Subject: [PATCH 03/90] Build with JDK 17 and 21 in Jenkins (#49)
---
Jenkinsfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Jenkinsfile b/Jenkinsfile
index e9f05f7d..5fed9c58 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -17,4 +17,4 @@
* under the License.
*/
-asfMavenTlpPlgnBuild()
+asfMavenTlpPlgnBuild(jdks:[ "17", "21" ])
From 99ac6665df55a90ac8de00b1b06117f6e6460f76 Mon Sep 17 00:00:00 2001
From: Guillaume Nodet
Date: Wed, 26 Jun 2024 08:40:48 +0200
Subject: [PATCH 04/90] Build with Maven 4.0.x on Jenkins
---
Jenkinsfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Jenkinsfile b/Jenkinsfile
index 5fed9c58..217636c5 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -17,4 +17,4 @@
* under the License.
*/
-asfMavenTlpPlgnBuild(jdks:[ "17", "21" ])
+asfMavenTlpPlgnBuild(jdks:[ "17", "21" ], maven: [ "4.0.x" ], siteJdk:[ "17" ], siteMvn:[ "4.0.x" ])
From 8c7ce9aed242acbc985f2201896cd0acfa2ec9ad Mon Sep 17 00:00:00 2001
From: Guillaume Nodet
Date: Wed, 26 Jun 2024 10:15:28 +0200
Subject: [PATCH 05/90] Clean dependencies
---
pom.xml | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/pom.xml b/pom.xml
index a7ef0bb5..dc34bcff 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,12 +63,25 @@ under the License.
4.0.0-beta-317
+ 33.2.1-jre
+ 6.0.0
+ 2.0.133.7.04.0.0-alpha-3-SNAPSHOT4.0.0-SNAPSHOT2024-06-16T10:25:11Z
+
+
+
+ com.google.guava
+ guava
+ ${guavaVersion}
+
+
+
+
org.apache.maven
@@ -115,13 +128,13 @@ under the License.
com.google.injectguice
- 6.0.0
+ ${guiceVersion}testorg.slf4jslf4j-simple
- 2.0.13
+ ${slf4jVersion}test
From e35167df10c12aaa12ec0da8a9466c2d6c5123b1 Mon Sep 17 00:00:00 2001
From: Guillaume Nodet
Date: Wed, 26 Jun 2024 10:17:24 +0200
Subject: [PATCH 06/90] Code formatting on ITs pom.xml
---
pom.xml | 41 ++++++++-----
src/it/dangling-symlinks/pom.xml | 6 +-
src/it/default/pom.xml | 6 +-
src/it/exclude-default-dirs/pom.xml | 6 +-
src/it/fast-delete/pom.xml | 58 +++++++++----------
.../file-sets-absolute-paths/child-a/pom.xml | 8 +--
src/it/file-sets-absolute-paths/pom.xml | 6 +-
src/it/file-sets-includes-excludes/pom.xml | 6 +-
.../file-sets-relative-paths/child-a/pom.xml | 8 +--
.../file-sets-relative-paths/child-b/pom.xml | 8 +--
src/it/file-sets-relative-paths/pom.xml | 6 +-
src/it/non-existent-base-dirs/pom.xml | 6 +-
src/it/only-test-clean/pom.xml | 7 +--
src/it/special-characters/pom.xml | 6 +-
src/it/symlink-dont-follow/pom.xml | 6 +-
.../resources/unit/basic-clean-test/pom.xml | 2 +-
.../resources/unit/empty-clean-test/pom.xml | 2 +-
.../resources/unit/fileset-clean-test/pom.xml | 4 +-
.../unit/invalid-directory-test/pom.xml | 2 +-
.../resources/unit/locked-file-test/pom.xml | 2 +-
.../unit/missing-directory-test/pom.xml | 2 +-
.../resources/unit/nested-clean-test/pom.xml | 2 +-
22 files changed, 78 insertions(+), 122 deletions(-)
diff --git a/pom.xml b/pom.xml
index dc34bcff..c9e5ac37 100644
--- a/pom.xml
+++ b/pom.xml
@@ -140,20 +140,33 @@ under the License.
-
-
- org.apache.maven.plugins
- maven-plugin-plugin
- ${version.maven-plugin-tools}
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
-
- true
-
-
-
+
+
+
+ com.diffplug.spotless
+ spotless-maven-plugin
+
+
+
+ src/**/*.java
+
+
+
+
+ **/pom.xml
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ true
+
+
+
+
diff --git a/src/it/dangling-symlinks/pom.xml b/src/it/dangling-symlinks/pom.xml
index 7e185f52..0431c499 100644
--- a/src/it/dangling-symlinks/pom.xml
+++ b/src/it/dangling-symlinks/pom.xml
@@ -1,5 +1,4 @@
-
-
-
+4.0.0test
diff --git a/src/it/default/pom.xml b/src/it/default/pom.xml
index fa866df4..c6251939 100644
--- a/src/it/default/pom.xml
+++ b/src/it/default/pom.xml
@@ -1,5 +1,4 @@
-
-
-
+4.0.0test
diff --git a/src/it/exclude-default-dirs/pom.xml b/src/it/exclude-default-dirs/pom.xml
index 210817a9..b33c6043 100644
--- a/src/it/exclude-default-dirs/pom.xml
+++ b/src/it/exclude-default-dirs/pom.xml
@@ -1,5 +1,4 @@
-
-
-
+4.0.0test
diff --git a/src/it/fast-delete/pom.xml b/src/it/fast-delete/pom.xml
index 92030564..f501863f 100644
--- a/src/it/fast-delete/pom.xml
+++ b/src/it/fast-delete/pom.xml
@@ -1,5 +1,4 @@
-
-
-
- 4.0.0
-
- test
- fast-delete
- 1.0-SNAPSHOT
-
- Fast delete
- Check that fast delete is invoked.
-
-
- UTF-8
-
-
-
-
-
- org.apache.maven.plugins
- maven-clean-plugin
- @project.version@
-
- true
- ${project.basedir}${file.separator}.fastdir
-
-
-
-
+
+ 4.0.0
+
+ test
+ fast-delete
+ 1.0-SNAPSHOT
+
+ Fast delete
+ Check that fast delete is invoked.
+
+
+ UTF-8
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-clean-plugin
+ @project.version@
+
+ true
+ ${project.basedir}${file.separator}.fastdir
+
+
+
+
diff --git a/src/it/file-sets-absolute-paths/child-a/pom.xml b/src/it/file-sets-absolute-paths/child-a/pom.xml
index ed4536d4..07bfc770 100644
--- a/src/it/file-sets-absolute-paths/child-a/pom.xml
+++ b/src/it/file-sets-absolute-paths/child-a/pom.xml
@@ -1,5 +1,4 @@
-
-
-
+4.0.0
@@ -30,7 +26,7 @@ under the License.
1.0-SNAPSHOT
- child-a
+ child-a1.0-SNAPSHOTChild A
diff --git a/src/it/file-sets-absolute-paths/pom.xml b/src/it/file-sets-absolute-paths/pom.xml
index dfa87f63..ce9e654c 100644
--- a/src/it/file-sets-absolute-paths/pom.xml
+++ b/src/it/file-sets-absolute-paths/pom.xml
@@ -1,5 +1,4 @@
-
-
-
+4.0.0test
diff --git a/src/it/file-sets-includes-excludes/pom.xml b/src/it/file-sets-includes-excludes/pom.xml
index cdae0584..dfee54c0 100644
--- a/src/it/file-sets-includes-excludes/pom.xml
+++ b/src/it/file-sets-includes-excludes/pom.xml
@@ -1,5 +1,4 @@
-
-
-
+4.0.0test
diff --git a/src/it/file-sets-relative-paths/child-a/pom.xml b/src/it/file-sets-relative-paths/child-a/pom.xml
index a1ff584e..53fcca7b 100644
--- a/src/it/file-sets-relative-paths/child-a/pom.xml
+++ b/src/it/file-sets-relative-paths/child-a/pom.xml
@@ -1,5 +1,4 @@
-
-
-
+4.0.0
@@ -30,7 +26,7 @@ under the License.
1.0-SNAPSHOT
- child-a
+ child-a1.0-SNAPSHOTChild A
diff --git a/src/it/file-sets-relative-paths/child-b/pom.xml b/src/it/file-sets-relative-paths/child-b/pom.xml
index 6821d50c..5795647f 100644
--- a/src/it/file-sets-relative-paths/child-b/pom.xml
+++ b/src/it/file-sets-relative-paths/child-b/pom.xml
@@ -1,5 +1,4 @@
-
-
-
+4.0.0
@@ -30,7 +26,7 @@ under the License.
1.0-SNAPSHOT
- child-b
+ child-b1.0-SNAPSHOTChild B
diff --git a/src/it/file-sets-relative-paths/pom.xml b/src/it/file-sets-relative-paths/pom.xml
index 4117bdfd..ab708d4d 100644
--- a/src/it/file-sets-relative-paths/pom.xml
+++ b/src/it/file-sets-relative-paths/pom.xml
@@ -1,5 +1,4 @@
-
-
-
+4.0.0test
diff --git a/src/it/non-existent-base-dirs/pom.xml b/src/it/non-existent-base-dirs/pom.xml
index 16b2242c..e5473574 100644
--- a/src/it/non-existent-base-dirs/pom.xml
+++ b/src/it/non-existent-base-dirs/pom.xml
@@ -1,5 +1,4 @@
-
-
-
+4.0.0test
diff --git a/src/it/only-test-clean/pom.xml b/src/it/only-test-clean/pom.xml
index 716e8de8..a23365fd 100644
--- a/src/it/only-test-clean/pom.xml
+++ b/src/it/only-test-clean/pom.xml
@@ -1,5 +1,4 @@
-
-
-
+4.0.0test
@@ -73,5 +69,4 @@ under the License.
-
diff --git a/src/it/special-characters/pom.xml b/src/it/special-characters/pom.xml
index 35cd8b8b..911e6070 100644
--- a/src/it/special-characters/pom.xml
+++ b/src/it/special-characters/pom.xml
@@ -1,5 +1,4 @@
-
-
-
+4.0.0org.apache.maven.plugins.clean.its
diff --git a/src/it/symlink-dont-follow/pom.xml b/src/it/symlink-dont-follow/pom.xml
index 88e8c077..de0c2d9a 100644
--- a/src/it/symlink-dont-follow/pom.xml
+++ b/src/it/symlink-dont-follow/pom.xml
@@ -1,5 +1,4 @@
-
-
-
+4.0.0test
diff --git a/src/test/resources/unit/basic-clean-test/pom.xml b/src/test/resources/unit/basic-clean-test/pom.xml
index ba901f06..6981ce73 100644
--- a/src/test/resources/unit/basic-clean-test/pom.xml
+++ b/src/test/resources/unit/basic-clean-test/pom.xml
@@ -1,3 +1,4 @@
+
-
diff --git a/src/test/resources/unit/empty-clean-test/pom.xml b/src/test/resources/unit/empty-clean-test/pom.xml
index 3d7000d8..54ae6b06 100644
--- a/src/test/resources/unit/empty-clean-test/pom.xml
+++ b/src/test/resources/unit/empty-clean-test/pom.xml
@@ -1,3 +1,4 @@
+
-
diff --git a/src/test/resources/unit/fileset-clean-test/pom.xml b/src/test/resources/unit/fileset-clean-test/pom.xml
index 2457c4ed..90c09715 100644
--- a/src/test/resources/unit/fileset-clean-test/pom.xml
+++ b/src/test/resources/unit/fileset-clean-test/pom.xml
@@ -1,3 +1,4 @@
+
-
@@ -41,7 +41,7 @@
**
-
+
diff --git a/src/test/resources/unit/invalid-directory-test/pom.xml b/src/test/resources/unit/invalid-directory-test/pom.xml
index 5d35e78c..a278e722 100644
--- a/src/test/resources/unit/invalid-directory-test/pom.xml
+++ b/src/test/resources/unit/invalid-directory-test/pom.xml
@@ -1,3 +1,4 @@
+
-
diff --git a/src/test/resources/unit/locked-file-test/pom.xml b/src/test/resources/unit/locked-file-test/pom.xml
index 1f2f429a..a677da40 100644
--- a/src/test/resources/unit/locked-file-test/pom.xml
+++ b/src/test/resources/unit/locked-file-test/pom.xml
@@ -1,3 +1,4 @@
+
-
diff --git a/src/test/resources/unit/missing-directory-test/pom.xml b/src/test/resources/unit/missing-directory-test/pom.xml
index ec695d12..bcf3a242 100644
--- a/src/test/resources/unit/missing-directory-test/pom.xml
+++ b/src/test/resources/unit/missing-directory-test/pom.xml
@@ -1,3 +1,4 @@
+
-
diff --git a/src/test/resources/unit/nested-clean-test/pom.xml b/src/test/resources/unit/nested-clean-test/pom.xml
index 5f9e755c..77f95d37 100644
--- a/src/test/resources/unit/nested-clean-test/pom.xml
+++ b/src/test/resources/unit/nested-clean-test/pom.xml
@@ -1,3 +1,4 @@
+
-
From bbdee584e46b6603755eeebda9cdf2ae2438b616 Mon Sep 17 00:00:00 2001
From: Guillaume Nodet
Date: Wed, 26 Jun 2024 10:17:48 +0200
Subject: [PATCH 07/90] Upgrade to m-plugin-testing and m-plugin-tools
4.0.0-beta-1
---
pom.xml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pom.xml b/pom.xml
index c9e5ac37..c0b6f3da 100644
--- a/pom.xml
+++ b/pom.xml
@@ -67,8 +67,8 @@ under the License.
6.0.02.0.133.7.0
- 4.0.0-alpha-3-SNAPSHOT
- 4.0.0-SNAPSHOT
+ 4.0.0-beta-1
+ 4.0.0-beta-12024-06-16T10:25:11Z
From 99986b8c0517c8170b708369916962eed7e13a93 Mon Sep 17 00:00:00 2001
From: Guillaume Nodet
Date: Wed, 26 Jun 2024 10:19:17 +0200
Subject: [PATCH 08/90] [maven-release-plugin] prepare release
maven-clean-plugin-4.0.0-beta-1
---
pom.xml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/pom.xml b/pom.xml
index c0b6f3da..818858fa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@ under the License.
maven-clean-plugin
- 4.0.0-SNAPSHOT
+ 4.0.0-beta-1maven-pluginApache Maven Clean Plugin
@@ -42,7 +42,7 @@ under the License.
scm:git:https://gitbox.apache.org/repos/asf/maven-clean-plugin.gitscm:git:https://gitbox.apache.org/repos/asf/maven-clean-plugin.git
- HEAD
+ maven-clean-plugin-4.0.0-beta-1https://github.com/apache/maven-clean-plugin/tree/${project.scm.tag}
@@ -69,7 +69,7 @@ under the License.
3.7.04.0.0-beta-14.0.0-beta-1
- 2024-06-16T10:25:11Z
+ 2024-06-26T08:19:11Z
From 648ca78783d0cfa90df30ad94858fb7f1c0ae4b5 Mon Sep 17 00:00:00 2001
From: Guillaume Nodet
Date: Wed, 26 Jun 2024 10:19:25 +0200
Subject: [PATCH 09/90] [maven-release-plugin] prepare for next development
iteration
---
pom.xml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/pom.xml b/pom.xml
index 818858fa..a716836b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@ under the License.
maven-clean-plugin
- 4.0.0-beta-1
+ 4.0.0-beta-2-SNAPSHOTmaven-pluginApache Maven Clean Plugin
@@ -42,7 +42,7 @@ under the License.
scm:git:https://gitbox.apache.org/repos/asf/maven-clean-plugin.gitscm:git:https://gitbox.apache.org/repos/asf/maven-clean-plugin.git
- maven-clean-plugin-4.0.0-beta-1
+ HEADhttps://github.com/apache/maven-clean-plugin/tree/${project.scm.tag}
@@ -69,7 +69,7 @@ under the License.
3.7.04.0.0-beta-14.0.0-beta-1
- 2024-06-26T08:19:11Z
+ 2024-06-26T08:19:25Z
From 87a7adc5b088e437de1343b23a54528fcbfd790a Mon Sep 17 00:00:00 2001
From: Michael Osipov
Date: Wed, 3 Jul 2024 20:38:41 +0200
Subject: [PATCH 10/90] Inherit site skin from parent
---
src/site/site.xml | 5 -----
1 file changed, 5 deletions(-)
diff --git a/src/site/site.xml b/src/site/site.xml
index d776c3d8..b14b745b 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -21,11 +21,6 @@ under the License.
-
- org.apache.maven.skins
- maven-fluido-skin
- 1.9
-
*
* @author Emmanuel Venisse
+ * @author Martin Desruisseaux
* @see Fileset
* @since 2.0
*/
@Mojo(name = "clean")
public class CleanMojo implements org.apache.maven.api.plugin.Mojo {
-
- public static final String FAST_MODE_BACKGROUND = "background";
-
- public static final String FAST_MODE_AT_END = "at-end";
-
- public static final String FAST_MODE_DEFER = "defer";
-
/**
* The logger where to send information about what the plugin is doing.
*/
@@ -189,19 +183,23 @@ public class CleanMojo implements org.apache.maven.api.plugin.Mojo {
* until all the files have been deleted. If any problem occurs during the atomic move of the directories,
* the plugin will default to the traditional deletion mechanism.
*
+ *
Note that for small projects with few files to delete, the "fast" clean tends to be actually slower.
+ * It is also more at risk that errors occurring during the deletion of a file get unnoticed, or are noticed
+ * late in the build process. This option should be used only when it has been verified to be worth.
+ *
* @since 3.2
*/
@Parameter(property = "maven.clean.fast", defaultValue = "false")
private boolean fast;
/**
- * When fast clean is specified, the {@code fastDir} property will be used as the location where directories
- * to be deleted will be moved prior to background deletion. If not specified, the
- * {@code ${maven.multiModuleProjectDirectory}/target/.clean} directory will be used.
+ * When fast clean is enabled,
+ * the location where directories to be deleted will be moved prior to background deletion.
+ * If not specified, the {@code ${maven.multiModuleProjectDirectory}/target/.clean} directory will be used.
* If the {@code ${build.directory}} has been modified, you'll have to adjust this property explicitly.
* In order for fast clean to work correctly, this directory and the various directories that will be deleted
* should usually reside on the same volume. The exact conditions are system-dependent though, but if an atomic
- * move is not supported, the standard deletion mechanism will be used.
+ * move is not supported, the immediate deletion mechanism will be used.
*
* @since 3.2
* @see #fast
@@ -210,16 +208,16 @@ public class CleanMojo implements org.apache.maven.api.plugin.Mojo {
private Path fastDir;
/**
- * Mode to use when using fast clean. Values are: {@code background} to start deletion immediately and
- * waiting for all files to be deleted when the session ends, {@code at-end} to indicate that the actual
- * deletion should be performed synchronously when the session ends, or {@code defer} to specify that
- * the actual file deletion should be started in the background when the session ends.
+ * Mode to use when using fast clean. Values are:
+ * {@code background} to start deletion immediately and waiting for all files to be deleted when the session ends,
+ * {@code at-end} to indicate that the actual deletion should be performed synchronously when the session ends, or
+ * {@code defer} to specify that the actual file deletion should be started in the background when the session ends.
* This should only be used when maven is embedded in a long-running process.
*
* @since 3.2
* @see #fast
*/
- @Parameter(property = "maven.clean.fastMode", defaultValue = FAST_MODE_BACKGROUND)
+ @Parameter(property = "maven.clean.fastMode", defaultValue = "background")
private String fastMode;
/**
@@ -263,42 +261,27 @@ public void execute() {
logger.info("Clean is skipped.");
return;
}
-
- String multiModuleProjectDirectory =
- session != null ? session.getSystemProperties().get("maven.multiModuleProjectDirectory") : null;
-
- @SuppressWarnings("LocalVariableHidesMemberVariable")
- final Path fastDir;
- if (fast && this.fastDir != null) {
- fastDir = this.fastDir;
- } else if (fast && multiModuleProjectDirectory != null) {
- fastDir = Path.of(multiModuleProjectDirectory, "target", ".clean");
- } else {
- fastDir = null;
- if (fast) {
- logger.warn("Fast clean requires maven 3.3.1 or newer, "
- + "or an explicit directory to be specified with the 'fastDir' configuration of "
- + "this plugin, or the 'maven.clean.fastDir' user property to be set.");
+ Cleaner cleaner;
+ if (fast && session != null) {
+ Path tmpDir = fastDir;
+ if (tmpDir == null) {
+ tmpDir = session.getRootDirectory().resolve("target").resolve(".clean");
}
+ cleaner = new BackgroundCleaner(
+ session,
+ matcherFactory,
+ logger,
+ isVerbose(),
+ tmpDir,
+ FastMode.caseInsensitiveValueOf(fastMode),
+ followSymLinks,
+ force,
+ failOnError,
+ retryOnError);
+ } else {
+ cleaner =
+ new Cleaner(matcherFactory, logger, isVerbose(), followSymLinks, force, failOnError, retryOnError);
}
- if (fast
- && !FAST_MODE_BACKGROUND.equals(fastMode)
- && !FAST_MODE_AT_END.equals(fastMode)
- && !FAST_MODE_DEFER.equals(fastMode)) {
- throw new IllegalArgumentException("Illegal value '" + fastMode + "' for fastMode. Allowed values are '"
- + FAST_MODE_BACKGROUND + "', '" + FAST_MODE_AT_END + "' and '" + FAST_MODE_DEFER + "'.");
- }
- final var cleaner = new Cleaner(
- session,
- matcherFactory,
- logger,
- isVerbose(),
- fastDir,
- fastMode,
- followSymLinks,
- force,
- failOnError,
- retryOnError);
try {
for (Path directoryItem : getDirectories()) {
cleaner.delete(directoryItem);
diff --git a/src/main/java/org/apache/maven/plugins/clean/Cleaner.java b/src/main/java/org/apache/maven/plugins/clean/Cleaner.java
index 2cfce014..bcdff203 100644
--- a/src/main/java/org/apache/maven/plugins/clean/Cleaner.java
+++ b/src/main/java/org/apache/maven/plugins/clean/Cleaner.java
@@ -28,57 +28,44 @@
import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
-import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.DosFileAttributeView;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
-import java.util.ArrayDeque;
import java.util.BitSet;
-import java.util.Deque;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
-import java.util.stream.Stream;
-import org.apache.maven.api.Event;
-import org.apache.maven.api.EventType;
-import org.apache.maven.api.Listener;
-import org.apache.maven.api.Session;
-import org.apache.maven.api.SessionData;
import org.apache.maven.api.annotations.Nonnull;
-import org.apache.maven.api.annotations.Nullable;
import org.apache.maven.api.plugin.Log;
import org.apache.maven.api.services.PathMatcherFactory;
/**
- * Cleans directories.
+ * Cleans directories. This base class deletes the files in the current thread.
+ * The {@link BackgroundCleaner} subclass adds the capability to run in background.
+ *
+ *
Limitations
+ * This class is not thread-safe: each instance shall be be executed in only one thread at a time.
+ * All directories specified as {@link Path} shall be deleted before directories specified as {@link Fileset},
+ * because the {@link Path} deletions rely on default values that are modified by {@link Fileset} deletions.
*
* @author Benjamin Bentmann
* @author Martin Desruisseaux
*/
-final class Cleaner implements FileVisitor {
+class Cleaner implements FileVisitor {
/**
* Whether the host operating system is from the Windows family.
*/
private static final boolean ON_WINDOWS = (File.separatorChar == '\\');
- private static final SessionData.Key LAST_DIRECTORY_TO_DELETE =
- SessionData.key(Path.class, Cleaner.class.getName() + ".lastDirectoryToDelete");
-
- /**
- * The maven session. This is typically non-null in a real run, but it can be during unit tests.
- */
- @Nonnull
- private final Session session;
-
/**
* The logger where to send information or warning messages.
*/
@Nonnull
- private final Log logger;
+ protected final Log logger;
/**
* Whether to send to the logger some information that would normally be at the "debug" level.
@@ -102,12 +89,6 @@ final class Cleaner implements FileVisitor {
*/
private boolean reallyDeletedLastFile;
- @Nonnull
- private final Path fastDir;
-
- @Nonnull
- private final String fastMode;
-
/**
* The service to use for creating include and exclude filters.
* Used for setting a value to {@link #fileMatcher} and {@link #directoryMatcher}.
@@ -181,7 +162,7 @@ final class Cleaner implements FileVisitor {
* A bit is used for each directory level, with {@link #currentDepth}
* telling which bit is for the current directory.
*/
- private final BitSet nonEmptyDirectoryLevels;
+ private final BitSet nonEmptyDirectoryLevels = new BitSet();
/**
* 0 for the base directory, and incremented for each subdirectory.
@@ -194,54 +175,72 @@ final class Cleaner implements FileVisitor {
* does not exclude the base directory and does not follow symbolic links.
* These properties can be modified by {@link #delete(Fileset)}.
*
- * @param session the Maven session to be used
* @param matcherFactory the service to use for creating include and exclude filters.
* @param logger the logger to use
* @param verbose whether to perform verbose logging
- * @param fastDir the explicit configured directory or to be deleted in fast mode
- * @param fastMode the fast deletion mode
* @param followSymlinks whether to follow symlinks
* @param force whether to force the deletion of read-only files
* @param failOnError whether to abort with an exception in case a selected file/directory could not be deleted
* @param retryOnError whether to undertake additional delete attempts in case the first attempt failed
*/
- @SuppressWarnings("checkstyle:ParameterNumber")
Cleaner(
- @Nullable Session session,
@Nonnull PathMatcherFactory matcherFactory,
@Nonnull Log logger,
boolean verbose,
- @Nullable Path fastDir,
- @Nonnull String fastMode,
boolean followSymlinks,
boolean force,
boolean failOnError,
boolean retryOnError) {
- this.session = session;
this.matcherFactory = matcherFactory;
this.logger = logger;
this.verbose = verbose;
- this.fastDir = fastDir;
- this.fastMode = fastMode;
this.followSymlinks = followSymlinks;
this.force = force;
this.failOnError = failOnError;
this.retryOnError = retryOnError;
listDeletedFiles = verbose ? logger.isInfoEnabled() : logger.isDebugEnabled();
- nonEmptyDirectoryLevels = new BitSet();
fileMatcher = matcherFactory.includesAll();
directoryMatcher = fileMatcher;
}
/**
- * Deletes the specified fileset.
+ * Creates a new cleaner with the configuration of the given cleaner as it was a construction time.
+ * An exception is the {@link #followSymlinks} flag which is set to {@code false}. This constructor
+ * is invoked by {@link BackgroundCleaner} before to invoke {@link #delete(Path)} at a moment which
+ * is potentially after some {@link #delete(Fileset)} executions. Because {@link BackgroundCleaner}
+ * can be used only when {@link #followSymlinks} is {@code false}, we know that this flag can be
+ * cleared unconditionally.
+ *
+ * @param other the cleaner from which to copy the configuration
+ */
+ Cleaner(Cleaner other) {
+ // Copy only final fields.
+ matcherFactory = other.matcherFactory;
+ logger = other.logger;
+ verbose = other.verbose;
+ force = other.force;
+ failOnError = other.failOnError;
+ retryOnError = other.retryOnError;
+ listDeletedFiles = other.listDeletedFiles;
+
+ // Non-final fields.
+ fileMatcher = matcherFactory.includesAll();
+ directoryMatcher = fileMatcher;
+ }
+
+ /**
+ * Deletes the specified fileset in the current thread.
* This method modifies the include and exclude filters,
* whether to exclude the base directory and whether to follow symbolic links.
*
+ *
Configuration
+ * {@link Fileset} deletions should be done after all {@link Path} deletions,
+ * because this method modifies this {@code Cleaner} configuration.
+ *
* @param fileset the fileset to delete
* @throws IOException if a file/directory could not be deleted and {@link #failOnError} is {@code true}
*/
- public void delete(@Nonnull Fileset fileset) throws IOException {
+ public final void delete(@Nonnull Fileset fileset) throws IOException {
fileMatcher = matcherFactory.createPathMatcher(
fileset.getDirectory(), fileset.getIncludes(), fileset.getExcludes(), fileset.useDefaultExcludes());
directoryMatcher = matcherFactory.deriveDirectoryMatcher(fileMatcher);
@@ -252,7 +251,7 @@ public void delete(@Nonnull Fileset fileset) throws IOException {
/**
* Deletes the specified directory and its contents using the current configuration.
- * Non-existing directories will be silently ignored.
+ * Non-existing directories will be ignored with a warning logged at the debug level.
*
*
Configuration
* The behavior of this method depends on the {@code Cleaner} configuration.
@@ -264,28 +263,35 @@ public void delete(@Nonnull Fileset fileset) throws IOException {
* @param basedir the directory to delete, must not be {@code null}
* @throws IOException if a file/directory could not be deleted and {@code failOnError} is {@code true}
*/
- public void delete(@Nonnull Path basedir) throws IOException {
+ public final void delete(@Nonnull Path basedir) throws IOException {
if (!Files.isDirectory(basedir)) {
if (Files.notExists(basedir)) {
- if (logger.isDebugEnabled()) {
- logger.debug("Skipping non-existing directory " + basedir);
- }
+ logger.debug("Skipping non-existing directory \"" + basedir + "\".");
return;
}
- throw new NotDirectoryException("Invalid base directory " + basedir);
+ throw new NotDirectoryException("Invalid base directory \"" + basedir + "\".");
}
if (logger.isInfoEnabled()) {
- logger.info("Deleting " + basedir + (isClearAll() ? "" : " (" + fileMatcher + ')'));
+ StringBuilder message =
+ new StringBuilder("Deleting \"").append(basedir).append('"');
+ if (!isClearAll()) {
+ message.append(" (").append(fileMatcher).append(')');
+ }
+ logger.info(message.append('.').toString());
}
var options = EnumSet.noneOf(FileVisitOption.class);
if (followSymlinks) {
options.add(FileVisitOption.FOLLOW_LINKS);
basedir = getCanonicalPath(basedir, null);
}
- if (isClearAll() && !followSymlinks && fastDir != null && session != null) {
+ if (isClearAll() && !followSymlinks) {
// If anything wrong happens, we'll just use the usual deletion mechanism
- if (fastDelete(basedir)) {
- return;
+ try {
+ if (fastDelete(basedir)) {
+ return;
+ }
+ } catch (IOException e) {
+ logger.debug(fastDeleteError(e), e);
}
}
Files.walkFileTree(basedir, options, Integer.MAX_VALUE, this);
@@ -296,56 +302,26 @@ public void delete(@Nonnull Path basedir) throws IOException {
* This is a required condition for allowing the use of {@link #fastDelete(Path)}.
*/
private boolean isClearAll() {
- return fileMatcher == matcherFactory.includesAll();
+ return matcherFactory.isIncludesAll(fileMatcher);
}
- private boolean fastDelete(Path baseDir) {
- // Handle the case where we use ${maven.multiModuleProjectDirectory}/target/.clean for example
- if (fastDir.toAbsolutePath().startsWith(baseDir.toAbsolutePath())) {
- try {
- String prefix = baseDir.getFileName().toString() + '.';
- Path tmpDir = Files.createTempDirectory(baseDir.getParent(), prefix);
- try {
- Files.move(baseDir, tmpDir, StandardCopyOption.REPLACE_EXISTING);
- if (session != null) {
- session.getData().set(LAST_DIRECTORY_TO_DELETE, baseDir);
- }
- baseDir = tmpDir;
- } catch (IOException e) {
- Files.delete(tmpDir);
- throw e;
- }
- } catch (IOException e) {
- logger.debug("Unable to fast delete directory", e);
- return false;
- }
- }
- // Create fastDir and the needed parents if needed
- try {
- if (!Files.isDirectory(fastDir)) {
- Files.createDirectories(fastDir);
- }
- } catch (IOException e) {
- logger.debug(
- "Unable to fast delete directory as the path " + fastDir
- + " does not point to a directory or cannot be created",
- e);
- return false;
- }
- try {
- Path tmpDir = Files.createTempDirectory(fastDir, "");
- Path dstDir = tmpDir.resolve(baseDir.getFileName());
- // Note that by specifying the ATOMIC_MOVE, we expect an exception to be thrown
- // if the path leads to a directory on another mountpoint. If this is the case
- // or any other exception occurs, an exception will be thrown in which case
- // the method will return false and the usual deletion will be performed.
- Files.move(baseDir, dstDir, StandardCopyOption.ATOMIC_MOVE);
- BackgroundCleaner.delete(this, tmpDir, fastMode);
- return true;
- } catch (IOException e) {
- logger.debug("Unable to fast delete directory", e);
- return false;
- }
+ /**
+ * Deletes the specified directory and its contents in a background thread.
+ * The default implementation returns {@code false}.
+ *
+ * @param basedir the directory to delete, must not be {@code null}
+ * @return whether this method was able to register the background task
+ * @throws IOException if an error occurred while preparing the task before execution in a background thread
+ */
+ boolean fastDelete(Path baseDir) throws IOException {
+ return false;
+ }
+
+ /**
+ * Returns an error message to show to user if the fast delete failed.
+ */
+ String fastDeleteError(IOException e) {
+ return e.toString();
}
/**
@@ -611,146 +587,4 @@ private void logDelete(final Path file, final BasicFileAttributes attrs) {
logger.debug(message);
}
}
-
- private static class BackgroundCleaner extends Thread {
-
- private static BackgroundCleaner instance;
-
- private final Deque filesToDelete = new ArrayDeque<>();
-
- private final Cleaner cleaner;
-
- private final String fastMode;
-
- private static final int NEW = 0;
- private static final int RUNNING = 1;
- private static final int STOPPED = 2;
-
- private int status = NEW;
-
- public static void delete(Cleaner cleaner, Path dir, String fastMode) {
- synchronized (BackgroundCleaner.class) {
- if (instance == null || !instance.doDelete(dir)) {
- instance = new BackgroundCleaner(cleaner, dir, fastMode);
- }
- }
- }
-
- static void sessionEnd() {
- synchronized (BackgroundCleaner.class) {
- if (instance != null) {
- instance.doSessionEnd();
- }
- }
- }
-
- private BackgroundCleaner(Cleaner cleaner, Path dir, String fastMode) {
- super("mvn-background-cleaner");
- this.cleaner = cleaner;
- this.fastMode = fastMode;
- init(cleaner.fastDir, dir);
- }
-
- @Override
- public void run() {
- var options = EnumSet.noneOf(FileVisitOption.class);
- if (cleaner.followSymlinks) {
- options.add(FileVisitOption.FOLLOW_LINKS);
- }
- Path basedir;
- while ((basedir = pollNext()) != null) {
- try {
- Files.walkFileTree(basedir, options, Integer.MAX_VALUE, cleaner);
- } catch (IOException e) {
- // do not display errors
- }
- }
- }
-
- synchronized void init(Path fastDir, Path dir) {
- if (Files.isDirectory(fastDir)) {
- try {
- try (Stream children = Files.list(fastDir)) {
- children.forEach(this::doDelete);
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- doDelete(dir);
- }
-
- synchronized Path pollNext() {
- Path basedir = filesToDelete.poll();
- if (basedir == null) {
- if (cleaner.session != null) {
- SessionData data = cleaner.session.getData();
- Path lastDir = data.get(LAST_DIRECTORY_TO_DELETE);
- if (lastDir != null) {
- data.set(LAST_DIRECTORY_TO_DELETE, null);
- return lastDir;
- }
- }
- status = STOPPED;
- notifyAll();
- }
- return basedir;
- }
-
- synchronized boolean doDelete(Path dir) {
- if (status == STOPPED) {
- return false;
- }
- filesToDelete.add(dir);
- if (status == NEW && CleanMojo.FAST_MODE_BACKGROUND.equals(fastMode)) {
- status = RUNNING;
- notifyAll();
- start();
- }
- wrapExecutionListener();
- return true;
- }
-
- /**
- * If this has not been done already, we wrap the ExecutionListener inside a proxy
- * which simply delegates call to the previous listener. When the session ends, it will
- * also call {@link BackgroundCleaner#sessionEnd()}.
- * There's no clean API to do that properly as this is a very unusual use case for a plugin
- * to outlive its main execution.
- */
- private void wrapExecutionListener() {
- synchronized (CleanerListener.class) {
- if (cleaner.session.getListeners().stream().noneMatch(l -> l instanceof CleanerListener)) {
- cleaner.session.registerListener(new CleanerListener());
- }
- }
- }
-
- synchronized void doSessionEnd() {
- if (status != STOPPED) {
- if (status == NEW) {
- start();
- }
- if (!CleanMojo.FAST_MODE_DEFER.equals(fastMode)) {
- try {
- cleaner.logger.info("Waiting for background file deletion");
- while (status != STOPPED) {
- wait();
- }
- } catch (InterruptedException e) {
- // ignore
- }
- }
- }
- }
- }
-
- static class CleanerListener implements Listener {
- @Override
- public void onEvent(Event event) {
- if (event.getType() == EventType.SESSION_ENDED) {
- BackgroundCleaner.sessionEnd();
- }
- }
- }
}
diff --git a/src/main/java/org/apache/maven/plugins/clean/FastMode.java b/src/main/java/org/apache/maven/plugins/clean/FastMode.java
new file mode 100644
index 00000000..86445e11
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/clean/FastMode.java
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+package org.apache.maven.plugins.clean;
+
+import java.util.Locale;
+
+/**
+ * Specifies when to delete files when fast clean is used.
+ * This enumeration defines the value values for {@code maven.clean.fastMode},
+ * in upper case and with {@code '-'} replaced by {@code '_'}.
+ */
+enum FastMode {
+ /**
+ * Start deletion immediately and wait for all files to be deleted when the session ends.
+ * This is the default mode when the deletion of files in a background thread is enabled.
+ */
+ BACKGROUND,
+
+ /**
+ * Actual deletion should be performed synchronously when the session ends.
+ * The plugin waits for all files to be deleted when the session end.
+ */
+ AT_END,
+
+ /**
+ * Actual file deletion should be started in the background when the session ends.
+ * The plugin does not wait for files to be deleted.
+ */
+ DEFER;
+
+ /**
+ * {@return the enumeration value for the given configuration option}
+ *
+ * @param option the configuration option, case insensitive
+ * @throws IllegalArgumentException if the given option is invalid
+ */
+ public static FastMode caseInsensitiveValueOf(String option) {
+ try {
+ return valueOf(option.trim().toUpperCase(Locale.US).replace('-', '_'));
+ } catch (NullPointerException | IllegalArgumentException e) {
+ StringBuilder sb =
+ new StringBuilder("Illegal value '").append(option).append("' for fastMode. Allowed values are '");
+ FastMode[] values = values();
+ int last = values.length;
+ for (int i = 0; i <= last; i++) {
+ sb.append(values[i]).append(i < last ? "', '" : "' and '");
+ }
+ throw new IllegalArgumentException(sb.append("'.").toString(), e);
+ }
+ }
+
+ /**
+ * {@return the lower-case variant of this enumeration value}
+ */
+ @Override
+ public String toString() {
+ return name().replace('_', '-').toLowerCase(Locale.US);
+ }
+}
diff --git a/src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java b/src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java
index c7935dea..a6a138e7 100644
--- a/src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java
+++ b/src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java
@@ -255,7 +255,7 @@ interface LinkCreator {
}
private void testSymlink(LinkCreator linkCreator) throws Exception {
- Cleaner cleaner = new Cleaner(null, matcherFactory, log, false, null, null, false, false, true, false);
+ Cleaner cleaner = new Cleaner(matcherFactory, log, false, false, false, true, false);
Path testDir = Paths.get("target/test-classes/unit/test-dir").toAbsolutePath();
Path dirWithLnk = testDir.resolve("dir");
Path orgDir = testDir.resolve("org-dir");
@@ -281,7 +281,7 @@ private void testSymlink(LinkCreator linkCreator) throws Exception {
Files.write(file, Collections.singleton("Hello world"));
linkCreator.createLink(jctDir, orgDir);
// delete
- cleaner = new Cleaner(null, matcherFactory, log, false, null, null, true, false, true, false);
+ cleaner = new Cleaner(matcherFactory, log, false, true, false, true, false);
cleaner.delete(dirWithLnk);
// verify
assertFalse(Files.exists(file));
diff --git a/src/test/java/org/apache/maven/plugins/clean/CleanerTest.java b/src/test/java/org/apache/maven/plugins/clean/CleanerTest.java
index 8c84cee3..22ac91fc 100644
--- a/src/test/java/org/apache/maven/plugins/clean/CleanerTest.java
+++ b/src/test/java/org/apache/maven/plugins/clean/CleanerTest.java
@@ -70,7 +70,7 @@ class CleanerTest {
void deleteSucceedsDeeply(@TempDir Path tempDir) throws Exception {
final Path basedir = createDirectory(tempDir.resolve("target")).toRealPath();
final Path file = createFile(basedir.resolve("file"));
- final var cleaner = new Cleaner(null, matcherFactory, log, false, null, null, false, false, true, false);
+ final var cleaner = new Cleaner(matcherFactory, log, false, false, false, true, false);
cleaner.delete(basedir);
assertFalse(exists(basedir));
assertFalse(exists(file));
@@ -85,7 +85,7 @@ void deleteFailsWithoutRetryWhenNoPermission(@TempDir Path tempDir) throws Excep
// Remove the executable flag to prevent directory listing, which will result in a DirectoryNotEmptyException.
final Set permissions = PosixFilePermissions.fromString("rw-rw-r--");
setPosixFilePermissions(basedir, permissions);
- final var cleaner = new Cleaner(null, matcherFactory, log, false, null, null, false, false, true, false);
+ final var cleaner = new Cleaner(matcherFactory, log, false, false, false, true, false);
final var exception = assertThrows(AccessDeniedException.class, () -> cleaner.delete(basedir));
verify(log, times(1)).warn(any(CharSequence.class), any(Throwable.class));
assertTrue(exception.getMessage().contains(basedir.toString()));
@@ -99,7 +99,7 @@ void deleteFailsAfterRetryWhenNoPermission(@TempDir Path tempDir) throws Excepti
// Remove the executable flag to prevent directory listing, which will result in a DirectoryNotEmptyException.
final Set permissions = PosixFilePermissions.fromString("rw-rw-r--");
setPosixFilePermissions(basedir, permissions);
- final var cleaner = new Cleaner(null, matcherFactory, log, false, null, null, false, false, true, true);
+ final var cleaner = new Cleaner(matcherFactory, log, false, false, false, true, true);
final var exception = assertThrows(AccessDeniedException.class, () -> cleaner.delete(basedir));
assertTrue(exception.getMessage().contains(basedir.toString()));
}
@@ -113,7 +113,7 @@ void deleteLogsWarningWithoutRetryWhenNoPermission(@TempDir Path tempDir) throws
// Remove the writable flag to prevent deletion of the file, which will result in an AccessDeniedException.
final Set permissions = PosixFilePermissions.fromString("r-xr-xr-x");
setPosixFilePermissions(basedir, permissions);
- final var cleaner = new Cleaner(null, matcherFactory, log, false, null, null, false, false, false, false);
+ final var cleaner = new Cleaner(matcherFactory, log, false, false, false, false, false);
assertDoesNotThrow(() -> cleaner.delete(basedir));
verify(log, times(1)).warn(any(CharSequence.class), any(Throwable.class));
InOrder inOrder = inOrder(log);
@@ -131,7 +131,7 @@ void deleteDoesNotLogAnythingWhenNoPermissionAndWarnDisabled(@TempDir Path tempD
// Remove the writable flag to prevent deletion of the file, which will result in an AccessDeniedException.
final Set permissions = PosixFilePermissions.fromString("r-xr-xr-x");
setPosixFilePermissions(basedir, permissions);
- final var cleaner = new Cleaner(null, matcherFactory, log, false, null, null, false, false, false, false);
+ final var cleaner = new Cleaner(matcherFactory, log, false, false, false, false, false);
assertDoesNotThrow(() -> cleaner.delete(basedir));
verify(log, never()).warn(any(CharSequence.class), any(Throwable.class));
}
From d4baff82141f61818b2cbf38b283d27e82f75998 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 10 Dec 2025 17:19:31 +0100
Subject: [PATCH 79/90] Bump org.mockito:mockito-core from 5.20.0 to 5.21.0
(#288)
Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.20.0 to 5.21.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.20.0...v5.21.0)
---
updated-dependencies:
- dependency-name: org.mockito:mockito-core
dependency-version: 5.21.0
dependency-type: direct:development
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 7edef7da..cb78b5c9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -138,7 +138,7 @@ under the License.
org.mockitomockito-core
- 5.20.0
+ 5.21.0test
From b5698670a449e1316b44ee885bfdcc35ee9b97bd Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 7 Jan 2026 06:34:42 +0100
Subject: [PATCH 80/90] Bump org.apache.maven.plugins:maven-plugins from 45 to
46 (#291)
Bumps [org.apache.maven.plugins:maven-plugins](https://github.com/apache/maven-parent) from 45 to 46.
- [Release notes](https://github.com/apache/maven-parent/releases)
- [Commits](https://github.com/apache/maven-parent/commits)
---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-plugins
dependency-version: '46'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index cb78b5c9..098c9683 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven.pluginsmaven-plugins
- 45
+ 46
From 18df3426ff59b18c9bd45204e0087ebfe070b4a9 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 15 Jan 2026 12:45:42 +0100
Subject: [PATCH 81/90] Bump org.apache.maven.plugins:maven-plugins from 46 to
47 (#294)
Bumps [org.apache.maven.plugins:maven-plugins](https://github.com/apache/maven-parent) from 46 to 47.
- [Release notes](https://github.com/apache/maven-parent/releases)
- [Commits](https://github.com/apache/maven-parent/commits)
---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-plugins
dependency-version: '47'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 098c9683..1a712448 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven.pluginsmaven-plugins
- 46
+ 47
From f29258f233fc8d7d315b2668d15fd01692c1dcc7 Mon Sep 17 00:00:00 2001
From: Sylwester Lachiewicz
Date: Sat, 14 Feb 2026 15:26:31 +0100
Subject: [PATCH 82/90] Mockito improvements (#298)
--
Co-authored-by: Moderne
---
.../java/org/apache/maven/plugins/clean/CleanerTest.java | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/test/java/org/apache/maven/plugins/clean/CleanerTest.java b/src/test/java/org/apache/maven/plugins/clean/CleanerTest.java
index 22ac91fc..df02e83b 100644
--- a/src/test/java/org/apache/maven/plugins/clean/CleanerTest.java
+++ b/src/test/java/org/apache/maven/plugins/clean/CleanerTest.java
@@ -48,7 +48,6 @@
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -87,7 +86,7 @@ void deleteFailsWithoutRetryWhenNoPermission(@TempDir Path tempDir) throws Excep
setPosixFilePermissions(basedir, permissions);
final var cleaner = new Cleaner(matcherFactory, log, false, false, false, true, false);
final var exception = assertThrows(AccessDeniedException.class, () -> cleaner.delete(basedir));
- verify(log, times(1)).warn(any(CharSequence.class), any(Throwable.class));
+ verify(log).warn(any(CharSequence.class), any(Throwable.class));
assertTrue(exception.getMessage().contains(basedir.toString()));
}
@@ -115,7 +114,7 @@ void deleteLogsWarningWithoutRetryWhenNoPermission(@TempDir Path tempDir) throws
setPosixFilePermissions(basedir, permissions);
final var cleaner = new Cleaner(matcherFactory, log, false, false, false, false, false);
assertDoesNotThrow(() -> cleaner.delete(basedir));
- verify(log, times(1)).warn(any(CharSequence.class), any(Throwable.class));
+ verify(log).warn(any(CharSequence.class), any(Throwable.class));
InOrder inOrder = inOrder(log);
ArgumentCaptor cause1 = ArgumentCaptor.forClass(AccessDeniedException.class);
inOrder.verify(log).warn(eq("Failed to delete " + file), cause1.capture());
From 82b0c653878f1eb0aa5a0ce193ba2b6828409ab1 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 18 Mar 2026 17:26:55 +0100
Subject: [PATCH 83/90] Bump org.mockito:mockito-core from 5.21.0 to 5.23.0
(#303)
Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.21.0 to 5.23.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.21.0...v5.23.0)
---
updated-dependencies:
- dependency-name: org.mockito:mockito-core
dependency-version: 5.23.0
dependency-type: direct:development
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 1a712448..bc9de135 100644
--- a/pom.xml
+++ b/pom.xml
@@ -138,7 +138,7 @@ under the License.
org.mockitomockito-core
- 5.21.0
+ 5.23.0test
From bba1733e469621f63c601b4990f6735453d5d9db Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 15 May 2026 22:13:02 +0200
Subject: [PATCH 84/90] Bump org.apache.maven.plugins:maven-plugins from 47 to
48 (#306)
Bumps [org.apache.maven.plugins:maven-plugins](https://github.com/apache/maven-parent) from 47 to 48.
- [Release notes](https://github.com/apache/maven-parent/releases)
- [Commits](https://github.com/apache/maven-parent/commits)
---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-plugins
dependency-version: '48'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index bc9de135..f0e5d530 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven.pluginsmaven-plugins
- 47
+ 48
From 978727570dcfadc418f456e351d63435b469a503 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 15 May 2026 22:14:02 +0200
Subject: [PATCH 85/90] Bump org.slf4j:slf4j-simple from 2.0.17 to 2.0.18
(#308)
Bumps org.slf4j:slf4j-simple from 2.0.17 to 2.0.18.
---
updated-dependencies:
- dependency-name: org.slf4j:slf4j-simple
dependency-version: 2.0.18
dependency-type: direct:development
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index f0e5d530..bbe66c9b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -65,7 +65,7 @@ under the License.
1733.4.6-jre6.0.0
- 2.0.17
+ 2.0.183.9.14.0.0-beta-44.0.0-beta-1
From 250f5f69c862a29577fd2f5be40c086a77a8e75a Mon Sep 17 00:00:00 2001
From: Slawomir Jaranowski
Date: Mon, 22 Jun 2026 23:06:53 +0200
Subject: [PATCH 86/90] Try release-drafter 7.4.0
---
.github/workflows/release-drafter.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml
index 1695d359..01a0c7b3 100644
--- a/.github/workflows/release-drafter.yml
+++ b/.github/workflows/release-drafter.yml
@@ -24,4 +24,4 @@ on:
jobs:
update_release_draft:
- uses: apache/maven-gh-actions-shared/.github/workflows/release-drafter.yml@v4
+ uses: apache/maven-gh-actions-shared/.github/workflows/release-drafter.yml@dependabot/github_actions/v4/release-drafter/release-drafter-7.4.0
From d776151f11b511436c7f0722dc0dcd8b7f1c0163 Mon Sep 17 00:00:00 2001
From: Slawomir Jaranowski
Date: Mon, 22 Jun 2026 23:17:01 +0200
Subject: [PATCH 87/90] Set custom tag-template in release-drafter
configuration
---
.github/release-drafter.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml
index 6d3b73cf..fe965e16 100644
--- a/.github/release-drafter.yml
+++ b/.github/release-drafter.yml
@@ -16,6 +16,7 @@
# under the License.
_extends: maven-gh-actions-shared
+tag-template: maven-clean-plugin-$RESOLVED_VERSION
include-pre-releases: true
prerelease: true
From 24a282d21e5b480ff92caeda0c901d7a276ed413 Mon Sep 17 00:00:00 2001
From: Slawomir Jaranowski
Date: Thu, 25 Jun 2026 18:46:42 +0200
Subject: [PATCH 88/90] Try release-drafter 7.5.1
---
.github/workflows/release-drafter.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml
index 01a0c7b3..745a8c36 100644
--- a/.github/workflows/release-drafter.yml
+++ b/.github/workflows/release-drafter.yml
@@ -24,4 +24,4 @@ on:
jobs:
update_release_draft:
- uses: apache/maven-gh-actions-shared/.github/workflows/release-drafter.yml@dependabot/github_actions/v4/release-drafter/release-drafter-7.4.0
+ uses: apache/maven-gh-actions-shared/.github/workflows/release-drafter.yml@dependabot/github_actions/v4/release-drafter/release-drafter-7.5.1
From c5a9cdbf9722db9e9140972eb57664c00e1d79c1 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 29 Jun 2026 17:12:35 +0200
Subject: [PATCH 89/90] Bump org.apache.maven.plugins:maven-plugins from 48 to
49 (#313)
Bumps [org.apache.maven.plugins:maven-plugins](https://github.com/apache/maven-parent) from 48 to 49.
- [Release notes](https://github.com/apache/maven-parent/releases)
- [Commits](https://github.com/apache/maven-parent/commits)
---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-plugins
dependency-version: '49'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index bbe66c9b..234fcd79 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven.pluginsmaven-plugins
- 48
+ 49
From eaae1b8f269c15ad59b21095bf5a32f8271ce132 Mon Sep 17 00:00:00 2001
From: Jarek Potiuk
Date: Wed, 8 Jul 2026 13:09:38 +0200
Subject: [PATCH 90/90] Add AGENTS.md + SECURITY.md security-model pointer for
scanner discoverability
Generated-by: Claude Code
---
AGENTS.md | 32 ++++++++++++++++++++++++++++++++
SECURITY.md | 32 ++++++++++++++++++++++++++++++++
2 files changed, 64 insertions(+)
create mode 100644 AGENTS.md
create mode 100644 SECURITY.md
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 00000000..50786196
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,32 @@
+
+
+# Agent Guide for maven-clean-plugin
+
+This file is read by automated agents (security scanners, code
+analyzers, AI assistants) operating on this repository.
+
+## Security
+
+Security model: [SECURITY.md](./SECURITY.md)
+
+Agents that scan this repository should consult `SECURITY.md` and the
+threat model it links before reporting issues.
+
+Points at the Apache Maven family umbrella security model.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 00000000..a44c459a
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,32 @@
+
+
+# Security Policy
+
+## Reporting a Vulnerability
+
+`apache/maven-clean-plugin` follows the [Apache Software Foundation security process](https://www.apache.org/security/). Please report suspected
+vulnerabilities privately to `security@apache.org`; do not open public
+GitHub issues or pull requests for security reports.
+
+## Threat Model
+
+What the project treats as in scope and out of scope, the security
+properties it provides and disclaims, the adversary model, and how
+findings are triaged are documented in .