Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@

<properties>
<javaVersion>17</javaVersion>
<mavenVersion>4.0.0-rc-4</mavenVersion>
<mavenVersion>4.1.0-SNAPSHOT</mavenVersion>

<guiceVersion>5.1.0</guiceVersion>
<junitVersion>5.14.4</junitVersion>
Expand Down
94 changes: 71 additions & 23 deletions src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -297,38 +308,75 @@ public Path createArchive() throws MojoException {
/**
* Generates the JAR.
*
* <p>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.</p>
*
* @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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
30 changes: 27 additions & 3 deletions src/test/java/org/apache/maven/plugins/jar/JarMojoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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());
}
}
Loading