From 340d4f56394ea6f1d8f7831dd252d5a3f9bf0d8e Mon Sep 17 00:00:00 2001
From: Abdullah <89297042+AzazelSensei@users.noreply.github.com>
Date: Thu, 27 Aug 2026 07:33:34 +0000
Subject: [PATCH] Skip creating a JAR when includes match no files
skipIfEmpty only looked at whether the classes directory had any
children. A test-jar with includes like **/junit/* still produced a
manifest-only archive when those patterns matched nothing.
Honor includes/excludes when deciding that the archive is empty.
---
src/it/MJAR-510/pom.xml | 55 +++++++++++++++++++
.../src/main/java/my/org/app1/App1.java | 21 +++++++
.../src/test/java/my/org/app1/App1Test.java | 21 +++++++
src/it/MJAR-510/verify.bsh | 53 ++++++++++++++++++
.../maven/plugins/jar/AbstractJarMojo.java | 47 ++++++++--------
5 files changed, 175 insertions(+), 22 deletions(-)
create mode 100644 src/it/MJAR-510/pom.xml
create mode 100644 src/it/MJAR-510/src/main/java/my/org/app1/App1.java
create mode 100644 src/it/MJAR-510/src/test/java/my/org/app1/App1Test.java
create mode 100644 src/it/MJAR-510/verify.bsh
diff --git a/src/it/MJAR-510/pom.xml b/src/it/MJAR-510/pom.xml
new file mode 100644
index 00000000..6e1fa3cf
--- /dev/null
+++ b/src/it/MJAR-510/pom.xml
@@ -0,0 +1,55 @@
+
+
+
+ 4.0.0
+ org.apache.maven.plugins
+ maven-jar-plugin-test-mjar-510
+ 1.0
+ jar
+ Maven
+ skipIfEmpty should skip when includes match no files
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ @project.version@
+
+
+ junit
+
+ test-jar
+
+
+ junit
+ true
+
+ **/junit/*
+
+
+ **/*Test.class
+
+
+
+
+
+
+
+
diff --git a/src/it/MJAR-510/src/main/java/my/org/app1/App1.java b/src/it/MJAR-510/src/main/java/my/org/app1/App1.java
new file mode 100644
index 00000000..965d4513
--- /dev/null
+++ b/src/it/MJAR-510/src/main/java/my/org/app1/App1.java
@@ -0,0 +1,21 @@
+/*
+ * 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 my.org.app1;
+
+public class App1 {}
diff --git a/src/it/MJAR-510/src/test/java/my/org/app1/App1Test.java b/src/it/MJAR-510/src/test/java/my/org/app1/App1Test.java
new file mode 100644
index 00000000..a25dbff0
--- /dev/null
+++ b/src/it/MJAR-510/src/test/java/my/org/app1/App1Test.java
@@ -0,0 +1,21 @@
+/*
+ * 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 my.org.app1;
+
+public class App1Test {}
diff --git a/src/it/MJAR-510/verify.bsh b/src/it/MJAR-510/verify.bsh
new file mode 100644
index 00000000..dc51ce8e
--- /dev/null
+++ b/src/it/MJAR-510/verify.bsh
@@ -0,0 +1,53 @@
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.*;
+
+try
+{
+ File target = new File( basedir, "target" );
+ if ( !target.exists() || !target.isDirectory() )
+ {
+ System.err.println( "target file is missing or not a directory." );
+ return false;
+ }
+
+ File artifact = new File( target, "maven-jar-plugin-test-mjar-510-1.0.jar" );
+ if ( !artifact.exists() || artifact.isDirectory() )
+ {
+ System.err.println( "main artifact is missing or a directory." );
+ return false;
+ }
+
+ File junitArtifact = new File( target, "maven-jar-plugin-test-mjar-510-1.0-junit.jar" );
+ if ( junitArtifact.exists() )
+ {
+ System.err.println( "junit classifier jar should not exist when includes match no files." );
+ return false;
+ }
+ return true;
+}
+catch( Throwable e )
+{
+ e.printStackTrace();
+ return false;
+}
+
+return false;
diff --git a/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java b/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java
index e3c91dfd..25c26d46 100644
--- a/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java
+++ b/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java
@@ -19,14 +19,12 @@
package org.apache.maven.plugins.jar;
import java.io.File;
-import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.jar.Attributes;
-import java.util.stream.Stream;
import org.apache.maven.api.ProducedArtifact;
import org.apache.maven.api.Project;
@@ -38,7 +36,6 @@
import org.apache.maven.api.services.ProjectManager;
import org.apache.maven.shared.archiver.MavenArchiveConfiguration;
import org.apache.maven.shared.archiver.MavenArchiver;
-import org.apache.maven.shared.archiver.MavenArchiverException;
import org.apache.maven.shared.model.fileset.FileSet;
import org.apache.maven.shared.model.fileset.util.FileSetManager;
import org.codehaus.plexus.archiver.Archiver;
@@ -119,7 +116,7 @@ public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Moj
* This plugin can not detect the post-processing, and so leaves the post-processed JAR file in place.
* This can lead to failures when those plugins do not expect to find their own output as an input.
* Set this parameter to {@code true} to recreate the JAR every time.
- * When {@link #skipIfEmpty} is {@code true} and the classes directory is empty, packaging is skipped even if
+ * When {@link #skipIfEmpty} is {@code true} and no files would be included, packaging is skipped even if
* {@code forceCreation} is true.
*
*
Starting with 3.0.0 the property has been renamed from {@code jar.forceCreation}
@@ -130,8 +127,8 @@ public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Moj
/**
* Skip creating empty archives.
- * When {@code true}, packaging is skipped if the classes directory is empty, even if {@link #forceCreation} is also
- * {@code true}.
+ * When {@code true}, packaging is skipped if the classes directory is empty or if no files match the configured
+ * includes/excludes, even if {@link #forceCreation} is also {@code true}.
*/
@Parameter(defaultValue = "false")
protected boolean skipIfEmpty;
@@ -239,13 +236,7 @@ public Path createArchive() throws MojoException {
finalName != null ? finalName : project.getBuild().getFinalName();
Path jarFile = getJarFile(basedir, resultFinalName, getClassifier());
- FileSetManager fileSetManager = new FileSetManager();
- FileSet jarContentFileSet = new FileSet();
- jarContentFileSet.setDirectory(getClassesDirectory().toAbsolutePath().toString());
- jarContentFileSet.setIncludes(Arrays.asList(getIncludes()));
- jarContentFileSet.setExcludes(Arrays.asList(getExcludes()));
-
- String[] includedFiles = fileSetManager.getIncludedFiles(jarContentFileSet);
+ String[] includedFiles = getIncludedFiles();
if (detectMultiReleaseJar
&& Arrays.stream(includedFiles)
@@ -305,7 +296,7 @@ public Path createArchive() throws MojoException {
*/
@Override
public void execute() throws MojoException {
- if (skipIfEmpty && isEmpty(getClassesDirectory())) {
+ if (skipIfEmpty && hasNoFilesToInclude()) {
getLog().info(String.format("Skipping packaging of the %s.", getType()));
} else {
Path jarFile = createArchive();
@@ -335,15 +326,27 @@ public void execute() throws MojoException {
}
}
- private static boolean isEmpty(Path directory) {
- if (!Files.isDirectory(directory)) {
- return true;
- }
- try (Stream children = Files.list(directory)) {
- return children.findAny().isEmpty();
- } catch (IOException e) {
- throw new MavenArchiverException("Unable to access directory", e);
+ /**
+ * {@return true} if the classes directory is missing or if includes/excludes select no files.
+ */
+ private boolean hasNoFilesToInclude() {
+ return getIncludedFiles().length == 0;
+ }
+
+ /**
+ * Files under the classes directory that match {@link #includes} and {@link #excludes}.
+ */
+ private String[] getIncludedFiles() {
+ Path classesDirectory = getClassesDirectory();
+ if (!Files.isDirectory(classesDirectory)) {
+ return new String[0];
}
+ FileSetManager fileSetManager = new FileSetManager();
+ FileSet jarContentFileSet = new FileSet();
+ jarContentFileSet.setDirectory(classesDirectory.toAbsolutePath().toString());
+ jarContentFileSet.setIncludes(Arrays.asList(getIncludes()));
+ jarContentFileSet.setExcludes(Arrays.asList(getExcludes()));
+ return fileSetManager.getIncludedFiles(jarContentFileSet);
}
private boolean projectHasAlreadySetAnArtifact() {