diff --git a/pom.xml b/pom.xml index 1515933..f80e6c2 100644 --- a/pom.xml +++ b/pom.xml @@ -74,7 +74,7 @@ 17 - 4.0.0-rc-4 + 4.1.0-SNAPSHOT 5.1.0 5.14.4 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 10e00af..1fa0a8c 100644 --- a/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java +++ b/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java @@ -23,6 +23,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.jar.Attributes; @@ -31,6 +32,8 @@ import org.apache.maven.api.ProducedArtifact; import org.apache.maven.api.Project; import org.apache.maven.api.Session; +import org.apache.maven.api.build.context.BuildContext; +import org.apache.maven.api.build.context.InputSet; import org.apache.maven.api.di.Inject; import org.apache.maven.api.plugin.Log; import org.apache.maven.api.plugin.MojoException; @@ -111,6 +114,14 @@ public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Moj @Inject protected ProjectManager projectManager; + /** + * The build context for incremental build support. + * Used to detect whether input files have changed since the last build, + * allowing the plugin to skip JAR creation when nothing has changed. + */ + @Inject + protected BuildContext buildContext; + /** * Require the jar plugin to build a new JAR even if none of the contents appear to have changed. * By default, this plugin looks to see if the output JAR exists and inputs have not changed. @@ -297,38 +308,75 @@ public Path createArchive() throws MojoException { /** * Generates the JAR. * + *

Uses the {@link BuildContext} aggregation pattern to register all class files as + * inputs and associate them with the JAR output. The JAR is only rebuilt when at least + * one input has changed since the last build (unless {@link #forceCreation} is set). + * When inputs are removed, the build context automatically handles stale output cleanup.

+ * * @throws MojoException in case of an error */ @Override public void execute() throws MojoException { if (skipIfEmpty && isEmpty(getClassesDirectory())) { getLog().info(String.format("Skipping packaging of the %s.", getType())); + buildContext.markSkipExecution(); + return; + } + + Path basedir = outputDirectory != null + ? outputDirectory + : Path.of(project.getBuild().getDirectory()); + String resultFinalName = + finalName != null ? finalName : project.getBuild().getFinalName(); + Path jarFile = getJarFile(basedir, resultFinalName, getClassifier()); + + // Register all class files as inputs and aggregate them into the JAR output. + // The aggregate() callback is only invoked when at least one input has changed. + Path classesDir = getClassesDirectory(); + if (!forceCreation && Files.isDirectory(classesDir)) { + InputSet inputSet = buildContext.newInputSet(); + inputSet.registerInputs(classesDir, List.of("**/**"), List.of()); + + boolean rebuilt = inputSet.aggregate(jarFile, (output, inputs) -> { + createArchive(); + }); + + if (!rebuilt) { + getLog().info("Nothing to package - all classes are up to date."); + buildContext.markSkipExecution(); + } } else { - Path jarFile = createArchive(); - - if (attach) { - ProducedArtifact artifact; - String classifier = getClassifier(); - if (hasClassifier(classifier)) { - artifact = session.createProducedArtifact( - project.getGroupId(), - project.getArtifactId(), - project.getVersion(), - classifier, - null, - getType()); - } else { - if (projectHasAlreadySetAnArtifact()) { - throw new MojoException("You have to use a classifier " - + "to attach supplemental artifacts to the project instead of replacing them."); - } - artifact = project.getMainArtifact().get(); - } - projectManager.attachArtifact(project, artifact, jarFile); - } else { - getLog().debug("Skipping attachment of the " + getType() + " artifact to the project."); + // forceCreation is set or no classes directory — always create the JAR + jarFile = createArchive(); + } + + if (attach) { + attachArtifact(jarFile); + } else { + getLog().debug("Skipping attachment of the " + getType() + " artifact to the project."); + } + } + + /** + * Attaches the given JAR file as a project artifact. + * + * @param jarFile the JAR file to attach + * @throws MojoException if the artifact cannot be attached + */ + private void attachArtifact(Path jarFile) { + ProducedArtifact artifact; + String classifier = getClassifier(); + if (hasClassifier(classifier)) { + artifact = session.createProducedArtifact( + project.getGroupId(), project.getArtifactId(), project.getVersion(), classifier, null, getType()); + } else { + if (projectHasAlreadySetAnArtifact()) { + throw new MojoException("You have to use a classifier " + + "to attach supplemental artifacts to the project instead of replacing them."); } + artifact = project.getMainArtifact().get(); } + projectManager.attachArtifact(project, artifact, jarFile); } private static boolean isEmpty(Path directory) { diff --git a/src/main/java/org/apache/maven/plugins/jar/TestJarMojo.java b/src/main/java/org/apache/maven/plugins/jar/TestJarMojo.java index 84de519..da8dda6 100644 --- a/src/main/java/org/apache/maven/plugins/jar/TestJarMojo.java +++ b/src/main/java/org/apache/maven/plugins/jar/TestJarMojo.java @@ -82,6 +82,7 @@ protected Path getClassesDirectory() { public void execute() throws MojoException { if (skip) { getLog().info("Skipping packaging of the test-jar."); + buildContext.markSkipExecution(); } else { super.execute(); } diff --git a/src/test/java/org/apache/maven/plugins/jar/JarMojoTest.java b/src/test/java/org/apache/maven/plugins/jar/JarMojoTest.java index 145eadc..c36e51d 100644 --- a/src/test/java/org/apache/maven/plugins/jar/JarMojoTest.java +++ b/src/test/java/org/apache/maven/plugins/jar/JarMojoTest.java @@ -18,9 +18,18 @@ */ package org.apache.maven.plugins.jar; -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 java.util.HashMap; + +import org.apache.maven.api.build.context.BuildContext; +import org.apache.maven.api.di.Priority; +import org.apache.maven.api.di.Provides; +import org.apache.maven.api.services.PathMatcherFactory; +import org.apache.maven.impl.DefaultPathMatcherFactory; +import org.apache.maven.internal.build.context.impl.DefaultBuildContext; +import org.apache.maven.internal.build.context.impl.FilesystemWorkspace; +import org.apache.maven.testing.plugin.Basedir; +import org.apache.maven.testing.plugin.InjectMojo; +import org.apache.maven.testing.plugin.MojoTest; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -47,4 +56,19 @@ void jarTestEnvironment(JarMojo mojo) throws Exception { assertEquals("foo", mojo.getProject().getGroupId()); } + + @Provides + @Priority(10) + @SuppressWarnings("unused") + private static PathMatcherFactory pathMatcherFactory() { + return new DefaultPathMatcherFactory(); + } + + @Provides + @Priority(10) + @SuppressWarnings("unused") + private static BuildContext buildContext() { + return new DefaultBuildContext( + new FilesystemWorkspace(), null, new HashMap<>(), null, new DefaultPathMatcherFactory()); + } }