Date: Mon, 20 Apr 2026 12:54:57 +0200
Subject: [PATCH 20/51] fix: getter gets
---
.../release/plugin/internal/ArtifactUtils.java | 8 ++++----
.../release/plugin/internal/GitUtils.java | 2 +-
.../plugin/mojos/BuildAttestationMojo.java | 18 +++++++++---------
3 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java b/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java
index 7f07244e2..b831edfa9 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java
@@ -36,7 +36,7 @@ private ArtifactUtils() {
}
/**
- * Returns the conventional filename for the given artifact.
+ * Gets the conventional filename for the given artifact.
*
* @param artifact A Maven artifact.
* @return A filename.
@@ -46,7 +46,7 @@ public static String getFileName(Artifact artifact) {
}
/**
- * Returns the filename for the given artifact with a changed extension.
+ * Gets the filename for the given artifact with a changed extension.
*
* @param artifact A Maven artifact.
* @param extension The file name extension.
@@ -63,7 +63,7 @@ public static String getFileName(Artifact artifact, String extension) {
}
/**
- * Returns the Package URL corresponding to this artifact.
+ * Gets the Package URL corresponding to this artifact.
*
* @param artifact A maven artifact.
* @return A PURL for the given artifact.
@@ -81,7 +81,7 @@ public static String getPackageUrl(Artifact artifact) {
}
/**
- * Returns a map of checksum algorithm names to hex-encoded digest values for the given artifact file.
+ * Gets a map of checksum algorithm names to hex-encoded digest values for the given artifact file.
*
* @param artifact A Maven artifact.
* @return A map of checksum algorithm names to hex-encoded digest values.
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java b/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java
index c4d4aecec..dc539bbd2 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java
@@ -67,7 +67,7 @@ public static String scmToDownloadUri(String scmUri, Path repositoryPath) throws
}
/**
- * Returns the current branch name for the given repository path.
+ * Gets the current branch name for the given repository path.
*
* Returns the commit SHA if the repository is in a detached HEAD state.
*
diff --git a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
index a2f586995..d9e0ee65b 100644
--- a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
+++ b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
@@ -225,7 +225,7 @@ void setOutputDirectory(final File outputDirectory) {
}
/**
- * Returns the SCM directory.
+ * Gets the SCM directory.
*
* @return The SCM directory.
*/
@@ -270,7 +270,7 @@ void setSignAttestation(final boolean signAttestation) {
}
/**
- * Overrides the GPG signer used for signing. Intended for testing.
+ * Sets the GPG signer used for signing. Intended for testing.
*
* @param signer the signer to use
*/
@@ -279,7 +279,7 @@ void setSigner(final AbstractGpgSigner signer) {
}
/**
- * Returns the GPG signer, creating and preparing it from plugin parameters if not already set.
+ * Gets the GPG signer, creating and preparing it from plugin parameters if not already set.
*
* @return the prepared signer
* @throws MojoFailureException if signer preparation fails
@@ -421,7 +421,7 @@ private List getSubjects() throws MojoExecutionException {
}
/**
- * Returns resource descriptors for the JVM, Maven installation, SCM source, and project dependencies.
+ * Gets resource descriptors for the JVM, Maven installation, SCM source, and project dependencies.
*
* @return A list of resolved build dependencies.
* @throws MojoExecutionException If any dependency cannot be resolved or hashed.
@@ -441,7 +441,7 @@ private List getBuildDependencies() throws MojoExecutionExce
}
/**
- * Returns resource descriptors for all resolved project dependencies.
+ * Gets resource descriptors for all resolved project dependencies.
*
* @return A list of resource descriptors for the project's resolved artifacts.
* @throws MojoExecutionException If a dependency artifact cannot be described.
@@ -455,7 +455,7 @@ private List getProjectDependencies() throws MojoExecutionEx
}
/**
- * Returns a resource descriptor for the current SCM source, including the URI and Git commit digest.
+ * Gets a resource descriptor for the current SCM source, including the URI and Git commit digest.
*
* @return A resource descriptor for the SCM source.
* @throws IOException If the current branch cannot be determined.
@@ -473,7 +473,7 @@ private ResourceDescriptor getScmDescriptor() throws IOException, MojoExecutionE
}
/**
- * Creates and returns an SCM repository from the configured connection URL.
+ * Gets an SCM repository from the configured connection URL.
*
* @return The SCM repository.
* @throws MojoExecutionException If the SCM repository cannot be created.
@@ -487,7 +487,7 @@ private ScmRepository getScmRepository() throws MojoExecutionException {
}
/**
- * Returns the current SCM revision (commit hash) for the configured SCM directory.
+ * Gets the current SCM revision (commit hash) for the configured SCM directory.
*
* @return The current SCM revision string.
* @throws MojoExecutionException If the revision cannot be retrieved from SCM.
@@ -531,7 +531,7 @@ private String getScmRevision(final InfoScmResult result) throws MojoExecutionEx
}
/**
- * Returns build metadata derived from the current Maven session, including start and finish timestamps.
+ * Gets build metadata derived from the current Maven session, including start and finish timestamps.
*
* @return The build metadata.
*/
From c8855e36dedc68ec2000d9aeebd99be06cc58297 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Mon, 20 Apr 2026 13:04:48 +0200
Subject: [PATCH 21/51] fix: fix `getFileName` Javadoc
---
.../apache/commons/release/plugin/internal/ArtifactUtils.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java b/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java
index b831edfa9..54575f53e 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java
@@ -36,7 +36,7 @@ private ArtifactUtils() {
}
/**
- * Gets the conventional filename for the given artifact.
+ * Gets the filename of an artifact in the default Maven repository layout.
*
* @param artifact A Maven artifact.
* @return A filename.
@@ -46,7 +46,7 @@ public static String getFileName(Artifact artifact) {
}
/**
- * Gets the filename for the given artifact with a changed extension.
+ * Gets the filename of an artifact in the default Maven repository layout, using the specified extension.
*
* @param artifact A Maven artifact.
* @param extension The file name extension.
From 28f0b57811318f6c05ff15cfc45eee4bc211ffaa Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Mon, 20 Apr 2026 13:51:01 +0200
Subject: [PATCH 22/51] fix: add support for multiple checksum algorithms
---
pom.xml | 5 ++
.../plugin/internal/ArtifactUtils.java | 52 ++++++++++++++++---
.../plugin/mojos/BuildAttestationMojo.java | 21 ++++++--
.../mojos/BuildAttestationMojoTest.java | 22 ++++----
.../attestations/commons-text-1.4.intoto.json | 50 ++++++++++++++----
5 files changed, 121 insertions(+), 29 deletions(-)
diff --git a/pom.xml b/pom.xml
index a740bb530..a5001cd27 100644
--- a/pom.xml
+++ b/pom.xml
@@ -199,6 +199,11 @@
commons-compress
1.28.0
+
+ org.apache.commons
+ commons-lang3
+ 3.20.0
+
org.apache.maven.plugins
maven-gpg-plugin
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java b/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java
index 54575f53e..729df6fb3 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java
@@ -17,10 +17,12 @@
package org.apache.commons.release.plugin.internal;
import java.io.IOException;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.commons.release.plugin.slsa.v1_2.ResourceDescriptor;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
@@ -30,6 +32,35 @@
*/
public final class ArtifactUtils {
+ /**
+ * Maps standard JDK {@link java.security.MessageDigest} algorithm names to the in-toto digest names used in SLSA {@link ResourceDescriptor} digest sets.
+ *
+ * JDK algorithms that have no in-toto equivalent (such as {@code MD2}) are omitted.
+ *
+ * @see
+ * JDK standard {@code MessageDigest} algorithm names
+ * @see
+ * in-toto digest set specification
+ */
+ private static final Map IN_TOTO_DIGEST_NAMES;
+
+ static {
+ final Map m = new HashMap<>();
+ m.put("MD5", "md5");
+ m.put("SHA-1", "sha1");
+ m.put("SHA-224", "sha224");
+ m.put("SHA-256", "sha256");
+ m.put("SHA-384", "sha384");
+ m.put("SHA-512", "sha512");
+ m.put("SHA-512/224", "sha512_224");
+ m.put("SHA-512/256", "sha512_256");
+ m.put("SHA3-224", "sha3_224");
+ m.put("SHA3-256", "sha3_256");
+ m.put("SHA3-384", "sha3_384");
+ m.put("SHA3-512", "sha3_512");
+ IN_TOTO_DIGEST_NAMES = Collections.unmodifiableMap(m);
+ }
+
/** No instances. */
private ArtifactUtils() {
// prevent instantiation
@@ -84,14 +115,22 @@ public static String getPackageUrl(Artifact artifact) {
* Gets a map of checksum algorithm names to hex-encoded digest values for the given artifact file.
*
* @param artifact A Maven artifact.
+ * @param algorithms JSSE names of algorithms to use
* @return A map of checksum algorithm names to hex-encoded digest values.
* @throws IOException If an I/O error occurs reading the artifact file.
+ * @throws IllegalArgumentException If any of the algorithms is not supported.
*/
- private static Map getChecksums(Artifact artifact) throws IOException {
+ private static Map getChecksums(Artifact artifact, String... algorithms) throws IOException {
Map checksums = new HashMap<>();
- DigestUtils digest = new DigestUtils(DigestUtils.getSha256Digest());
- String sha256sum = digest.digestAsHex(artifact.getFile());
- checksums.put("sha256", sha256sum);
+ for (String algorithm : algorithms) {
+ String key = IN_TOTO_DIGEST_NAMES.get(algorithm);
+ if (key == null) {
+ throw new IllegalArgumentException("Invalid algorithm name for in-toto attestation: " + algorithm);
+ }
+ DigestUtils digest = new DigestUtils(DigestUtils.getDigest(algorithm));
+ String checksum = digest.digestAsHex(artifact.getFile());
+ checksums.put(key, checksum);
+ }
return checksums;
}
@@ -99,16 +138,17 @@ private static Map getChecksums(Artifact artifact) throws IOExce
* Converts a Maven artifact to a SLSA {@link ResourceDescriptor}.
*
* @param artifact A Maven artifact.
+ * @param algorithms A comma-separated list of checksum algorithms to use.
* @return A SLSA resource descriptor.
* @throws MojoExecutionException If an I/O error occurs retrieving the artifact.
*/
- public static ResourceDescriptor toResourceDescriptor(Artifact artifact) throws MojoExecutionException {
+ public static ResourceDescriptor toResourceDescriptor(Artifact artifact, String algorithms) throws MojoExecutionException {
ResourceDescriptor descriptor = new ResourceDescriptor();
descriptor.setName(getFileName(artifact));
descriptor.setUri(getPackageUrl(artifact));
if (artifact.getFile() != null) {
try {
- descriptor.setDigest(getChecksums(artifact));
+ descriptor.setDigest(getChecksums(artifact, StringUtils.split(algorithms, ",")));
} catch (IOException e) {
throw new MojoExecutionException("Unable to compute hash for artifact file: " + artifact.getFile(), e);
}
diff --git a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
index d9e0ee65b..9ba424459 100644
--- a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
+++ b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
@@ -128,6 +128,12 @@ public class BuildAttestationMojo extends AbstractMojo {
@Parameter(property = "commons.release.signAttestation", defaultValue = "true")
private boolean signAttestation;
+ /**
+ * Checksum algorithms used in the generated attestation.
+ */
+ @Parameter(property = "commons.release.checksums.algorithms", defaultValue = "SHA-512,SHA-256,SHA-1,MD5")
+ private String algorithmNames;
+
/**
* Path to the GPG executable; if not set, {@code gpg} is resolved from {@code PATH}.
*/
@@ -278,6 +284,15 @@ void setSigner(final AbstractGpgSigner signer) {
this.signer = signer;
}
+ /**
+ * Sets the list of checksum algorithms to use.
+ *
+ * @param algorithmNames A comma-separated list of {@link java.security.MessageDigest} algorithm names to use.
+ */
+ void setAlgorithmNames(String algorithmNames) {
+ this.algorithmNames = algorithmNames;
+ }
+
/**
* Gets the GPG signer, creating and preparing it from plugin parameters if not already set.
*
@@ -413,9 +428,9 @@ private void writeAndAttach(final Object value, final Path artifactPath) throws
*/
private List getSubjects() throws MojoExecutionException {
List subjects = new ArrayList<>();
- subjects.add(ArtifactUtils.toResourceDescriptor(project.getArtifact()));
+ subjects.add(ArtifactUtils.toResourceDescriptor(project.getArtifact(), algorithmNames));
for (Artifact artifact : project.getAttachedArtifacts()) {
- subjects.add(ArtifactUtils.toResourceDescriptor(artifact));
+ subjects.add(ArtifactUtils.toResourceDescriptor(artifact, algorithmNames));
}
return subjects;
}
@@ -449,7 +464,7 @@ private List getBuildDependencies() throws MojoExecutionExce
private List getProjectDependencies() throws MojoExecutionException {
List dependencies = new ArrayList<>();
for (Artifact artifact : project.getArtifacts()) {
- dependencies.add(ArtifactUtils.toResourceDescriptor(artifact));
+ dependencies.add(ArtifactUtils.toResourceDescriptor(artifact, algorithmNames));
}
return dependencies;
}
diff --git a/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java b/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
index 51565d368..c9aa84ef5 100644
--- a/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
+++ b/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
@@ -112,6 +112,16 @@ private static BuildAttestationMojo createBuildAttestationMojo(MavenProject proj
createMavenSession(createMavenExecutionRequest(), new DefaultMavenExecutionResult()), projectHelper);
}
+ private static void configureBuildAttestationMojo(BuildAttestationMojo mojo, boolean signAttestation) {
+ mojo.setOutputDirectory(new File("target/attestations"));
+ mojo.setScmDirectory(new File("."));
+ mojo.setScmConnectionUrl("scm:git:https://github.com/apache/commons-text.git");
+ mojo.setMavenHome(new File(System.getProperty("maven.home", ".")));
+ mojo.setAlgorithmNames("SHA-512,SHA-256,SHA-1,MD5");
+ mojo.setSignAttestation(signAttestation);
+ mojo.setSigner(createMockSigner());
+ }
+
private static MavenProject createMavenProject(MavenProjectHelper projectHelper, MavenRepositorySystem repoSystem) throws Exception {
File pomFile = new File(ARTIFACTS_DIR + "commons-text-1.4.pom");
Model model;
@@ -209,10 +219,7 @@ void attestationTest() throws Exception {
MavenProject project = createMavenProject(projectHelper, repoSystem);
BuildAttestationMojo mojo = createBuildAttestationMojo(project, projectHelper);
- mojo.setOutputDirectory(new File("target/attestations"));
- mojo.setScmDirectory(new File("."));
- mojo.setScmConnectionUrl("scm:git:https://github.com/apache/commons-text.git");
- mojo.setMavenHome(new File(System.getProperty("maven.home", ".")));
+ configureBuildAttestationMojo(mojo, false);
mojo.execute();
JsonNode statement = OBJECT_MAPPER.readTree(getAttestation(project).getFile());
@@ -226,12 +233,7 @@ void signingTest() throws Exception {
MavenProject project = createMavenProject(projectHelper, repoSystem);
BuildAttestationMojo mojo = createBuildAttestationMojo(project, projectHelper);
- mojo.setOutputDirectory(new File("target/attestations"));
- mojo.setScmDirectory(new File("."));
- mojo.setScmConnectionUrl("scm:git:https://github.com/apache/commons-text.git");
- mojo.setMavenHome(new File(System.getProperty("maven.home", ".")));
- mojo.setSignAttestation(true);
- mojo.setSigner(createMockSigner());
+ configureBuildAttestationMojo(mojo, true);
mojo.execute();
String envelopeJson = new String(Files.readAllBytes(getAttestation(project).getFile().toPath()), StandardCharsets.UTF_8);
diff --git a/src/test/resources/attestations/commons-text-1.4.intoto.json b/src/test/resources/attestations/commons-text-1.4.intoto.json
index 5c33198d0..37007233b 100644
--- a/src/test/resources/attestations/commons-text-1.4.intoto.json
+++ b/src/test/resources/attestations/commons-text-1.4.intoto.json
@@ -4,70 +4,100 @@
"name": "commons-text-1.4.jar",
"uri": "pkg:maven/commons-text/commons-text@1.4?type=jar",
"digest": {
- "sha256": "ad2d2eacf15ab740c115294afc1192603d8342004a6d7d0ad35446f7dda8a134"
+ "md5": "9cbe22bb0ce86c70779213dfb7f3eb5a",
+ "sha1": "c81f089b3542485d4d09b02aae822906e5d2f209",
+ "sha256": "ad2d2eacf15ab740c115294afc1192603d8342004a6d7d0ad35446f7dda8a134",
+ "sha512": "126302c5f6865733774eb41fecc10ba8d0bb5ba11d14b9562047429abeb13bf8cdcdbfdf5e7d7708e2a40f67f4265cbbce609164f57abcd676067a840aa48e6a"
}
},
{
"name": "commons-text-1.4.pom",
"uri": "pkg:maven/commons-text/commons-text@1.4?type=pom",
"digest": {
- "sha256": "4d6277b1e0720bb054c640620679a9da120f753029342150e714095f48934d76"
+ "md5": "00045f652e3dc8970442ce819806db34",
+ "sha1": "26fa30e496321e74c77ad66781ba53448e6e3a68",
+ "sha256": "4d6277b1e0720bb054c640620679a9da120f753029342150e714095f48934d76",
+ "sha512": "db8934f3062ea9c965ea27cfe4517a25513fb7cebe35ed02bedc1d8287b01c7ba64c93a8a261325fe12ab6957cbd80dbc8c06ec34c9a23c5a5c89ef6bace88fe"
}
},
{
"name": "commons-text-1.4-sources.jar",
"uri": "pkg:maven/commons-text/commons-text@1.4?classifier=sources&type=jar",
"digest": {
- "sha256": "58a95591fe7fc94db94a0a9e64b4a5bcc1c49edf17f2b24d7c0747357d855761"
+ "md5": "21af10902cea10cf54bc9acf956863d4",
+ "sha1": "cadbe9d3980a21e6eaec3aad629bbcdb7714aa3f",
+ "sha256": "58a95591fe7fc94db94a0a9e64b4a5bcc1c49edf17f2b24d7c0747357d855761",
+ "sha512": "c91ed209fa97c5e69e21d3a29d1f2ea90f2f77451762b3c387a8cb94dea167b4d3f04ea1a8635232476d804f40935698b4f8884fd43520bcc79b3a0f9a757716"
}
},
{
"name": "commons-text-1.4-javadoc.jar",
"uri": "pkg:maven/commons-text/commons-text@1.4?classifier=javadoc&type=jar",
"digest": {
- "sha256": "42f5b341d0fbeaa30b06aed90612840bc513fb39792c3d39446510670216e8b1"
+ "md5": "2ad93e1d99c0b80cbf6872d1762d7297",
+ "sha1": "9f06cdf753e1bb512e7640ec5fcce83f5a19ba2c",
+ "sha256": "42f5b341d0fbeaa30b06aed90612840bc513fb39792c3d39446510670216e8b1",
+ "sha512": "052ab4e9facfc64f265a567607d40d37620b616908ce71405cae9cd30ad6ac9f2558663029933f90df67c1e748ac81451a32e28975dc773c2c7476c489146c30"
}
},
{
"name": "commons-text-1.4-tests.jar",
"uri": "pkg:maven/commons-text/commons-text@1.4?classifier=tests&type=jar",
"digest": {
- "sha256": "e4e365d08d601a4bda44be2a31f748b96762504d301742d4a0f7f5953d4c793a"
+ "md5": "9820547734aff2c17c4a696bbd7d8d5e",
+ "sha1": "dbfb945a12375e9fe06558840b43f35374c391ff",
+ "sha256": "e4e365d08d601a4bda44be2a31f748b96762504d301742d4a0f7f5953d4c793a",
+ "sha512": "a8e373cf10a9dc2d3c1cfdd43b23910c9ca9d83dea4e566772dd1ebe5e28419777e0d83b002e8cf950f7bde3218b714e0cc3718464ee34ecda77f603e260ab20"
}
},
{
"name": "commons-text-1.4-test-sources.jar",
"uri": "pkg:maven/commons-text/commons-text@1.4?classifier=test-sources&type=jar",
"digest": {
- "sha256": "9200a2a41b35f2d6d30c1c698308591cf577547ec39514657dff0e2f7dff18ca"
+ "md5": "340231a697e2862c8d820de419a91d3e",
+ "sha1": "5e107fc877c67992948620a8a2d9bb94cab21aa0",
+ "sha256": "9200a2a41b35f2d6d30c1c698308591cf577547ec39514657dff0e2f7dff18ca",
+ "sha512": "02b405eb9aff57959a3e066030745be47b76c71af71f86e06fd5004602a399cdf86b1149554e92e9922d9da2fd2239dcf6e8c783462167320838dd7662405b30"
}
},
{
"name": "commons-text-1.4-bin.tar.gz",
"uri": "pkg:maven/commons-text/commons-text@1.4?classifier=bin&type=tar.gz",
"digest": {
- "sha256": "8b9393f7ddc2efb69d8c2b6f4d85d8711dddfe77009799cf21619fc9b8411897"
+ "md5": "9fe25162590be6fa684a9d9cdc0b505d",
+ "sha1": "6f46fd82d5ccf9644a37bcec6f2158a52ebdbab8",
+ "sha256": "8b9393f7ddc2efb69d8c2b6f4d85d8711dddfe77009799cf21619fc9b8411897",
+ "sha512": "c76f4e5814c0533030fecf0ef7639ce68df54b76ebc1320d9c4e3b8dff0a90c95ce0a425b8df6a0d742f6e9fca8dce6f08632d576c6a99357fdd54b9435fed6c"
}
},
{
"name": "commons-text-1.4-bin.zip",
"uri": "pkg:maven/commons-text/commons-text@1.4?classifier=bin&type=zip",
"digest": {
- "sha256": "ad3732dcb38e510b1dbb1544115d0eb797fab61afe0008fdb187cd4ef1706cd7"
+ "md5": "e29e5290b7420ba1908c418fc5bc7f8a",
+ "sha1": "447a051d6c8292c7e4d7641ca6586b335ef13bd6",
+ "sha256": "ad3732dcb38e510b1dbb1544115d0eb797fab61afe0008fdb187cd4ef1706cd7",
+ "sha512": "040634b27146e2008bf953f1bb63f8ccbb63cdaaaf24beb17acc6782d31452214e755539c2df737e46e7ede5d4e376cb3de5bbf72ceba5409b43f25612c2bf40"
}
},
{
"name": "commons-text-1.4-src.tar.gz",
"uri": "pkg:maven/commons-text/commons-text@1.4?classifier=src&type=tar.gz",
"digest": {
- "sha256": "1cb8536c375c3cff66757fd40c2bf878998254ba0a247866a6536bd48ba2e88a"
+ "md5": "be130c23d3b6630824e2a08530bd4581",
+ "sha1": "0dcee421c4e03d6bc098a61a5cdcc90656856611",
+ "sha256": "1cb8536c375c3cff66757fd40c2bf878998254ba0a247866a6536bd48ba2e88a",
+ "sha512": "8279eb7f45009f11658c256b07cfb06c5a45ef89949e78a67e5d64520d957707f90a9614c95e8aac350a07a54f9160f0802dbd1efc0769a5b1391e52ee4cd51b"
}
},
{
"name": "commons-text-1.4-src.zip",
"uri": "pkg:maven/commons-text/commons-text@1.4?classifier=src&type=zip",
"digest": {
- "sha256": "e4a6c992153faae4f7faff689b899073000364e376736b9746a5d0acb9d8b980"
+ "md5": "fd65603e930f2b0805c809aa2deb1498",
+ "sha1": "ca1cc6fbb4e46b44f8bb09b70c9e3a2ae3c5fce8",
+ "sha256": "e4a6c992153faae4f7faff689b899073000364e376736b9746a5d0acb9d8b980",
+ "sha512": "79ca61ff7b287407428bbb6ae13c6d372dcd0665114c55cd5bc57978a6fa760305e32feabef62cfeb0c4181220a59406239f6cccaa9a25c68773eef0250cb3a9"
}
}
],
From 86a440155f163f6d0460a5bf59a6707468595d9b Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Mon, 20 Apr 2026 14:06:51 +0200
Subject: [PATCH 23/51] fix: improve `BuildDefinitions.commandLine`
---
.../plugin/internal/BuildDefinitions.java | 36 ++++---------------
1 file changed, 7 insertions(+), 29 deletions(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java b/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
index dc51d0730..b6b175a71 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
@@ -20,6 +20,7 @@
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -136,35 +137,12 @@ public static Map externalParameters(final MavenSession session)
* @return a string representation of the Maven command line
*/
static String commandLine(final MavenExecutionRequest request) {
- StringBuilder sb = new StringBuilder();
- for (String goal : request.getGoals()) {
- sb.append(goal).append(" ");
- }
- List activeProfiles = request.getActiveProfiles();
- if (activeProfiles != null && !activeProfiles.isEmpty()) {
- sb.append("-P");
- for (String profile : activeProfiles) {
- sb.append(profile).append(",");
- }
- removeLast(sb);
- sb.append(" ");
- }
- Properties userProperties = request.getUserProperties();
- for (String propertyName : userProperties.stringPropertyNames()) {
- sb.append("-D").append(propertyName).append("=").append(userProperties.get(propertyName)).append(" ");
- }
- removeLast(sb);
- return sb.toString();
- }
-
- /**
- * Removes last character from a {@link StringBuilder}.
- *
- * @param sb The {@link StringBuilder} to use
- */
- private static void removeLast(final StringBuilder sb) {
- if (sb.length() > 0) {
- sb.setLength(sb.length() - 1);
+ List args = new ArrayList<>(request.getGoals());
+ String profiles = String.join(",", request.getActiveProfiles());
+ if (!profiles.isEmpty()) {
+ args.add("-P" + profiles);
}
+ request.getUserProperties().forEach((key, value) -> args.add("-D" + key + "=" + value));
+ return String.join(" ", args);
}
}
From c3cff4d616c2dab5640954530b9ea8f77fcba941 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Mon, 20 Apr 2026 14:14:50 +0200
Subject: [PATCH 24/51] fix: add final everywhere
---
.../plugin/internal/ArtifactUtils.java | 30 +++++-----
.../plugin/internal/BuildDefinitions.java | 36 +++++------
.../release/plugin/internal/GitUtils.java | 20 +++----
.../plugin/mojos/BuildAttestationMojo.java | 60 +++++++++----------
.../plugin/internal/BuildDefinitionsTest.java | 4 +-
.../release/plugin/internal/MojoUtils.java | 10 ++--
.../mojos/BuildAttestationMojoTest.java | 57 +++++++++---------
7 files changed, 109 insertions(+), 108 deletions(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java b/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java
index 729df6fb3..e2e2aa75e 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java
@@ -72,7 +72,7 @@ private ArtifactUtils() {
* @param artifact A Maven artifact.
* @return A filename.
*/
- public static String getFileName(Artifact artifact) {
+ public static String getFileName(final Artifact artifact) {
return getFileName(artifact, artifact.getArtifactHandler().getExtension());
}
@@ -83,8 +83,8 @@ public static String getFileName(Artifact artifact) {
* @param extension The file name extension.
* @return A filename.
*/
- public static String getFileName(Artifact artifact, String extension) {
- StringBuilder fileName = new StringBuilder();
+ public static String getFileName(final Artifact artifact, final String extension) {
+ final StringBuilder fileName = new StringBuilder();
fileName.append(artifact.getArtifactId()).append("-").append(artifact.getVersion());
if (artifact.getClassifier() != null) {
fileName.append("-").append(artifact.getClassifier());
@@ -99,11 +99,11 @@ public static String getFileName(Artifact artifact, String extension) {
* @param artifact A maven artifact.
* @return A PURL for the given artifact.
*/
- public static String getPackageUrl(Artifact artifact) {
- StringBuilder sb = new StringBuilder();
+ public static String getPackageUrl(final Artifact artifact) {
+ final StringBuilder sb = new StringBuilder();
sb.append("pkg:maven/").append(artifact.getGroupId()).append("/").append(artifact.getArtifactId()).append("@").append(artifact.getVersion())
.append("?");
- String classifier = artifact.getClassifier();
+ final String classifier = artifact.getClassifier();
if (classifier != null) {
sb.append("classifier=").append(classifier).append("&");
}
@@ -120,15 +120,15 @@ public static String getPackageUrl(Artifact artifact) {
* @throws IOException If an I/O error occurs reading the artifact file.
* @throws IllegalArgumentException If any of the algorithms is not supported.
*/
- private static Map getChecksums(Artifact artifact, String... algorithms) throws IOException {
- Map checksums = new HashMap<>();
- for (String algorithm : algorithms) {
- String key = IN_TOTO_DIGEST_NAMES.get(algorithm);
+ private static Map getChecksums(final Artifact artifact, final String... algorithms) throws IOException {
+ final Map checksums = new HashMap<>();
+ for (final String algorithm : algorithms) {
+ final String key = IN_TOTO_DIGEST_NAMES.get(algorithm);
if (key == null) {
throw new IllegalArgumentException("Invalid algorithm name for in-toto attestation: " + algorithm);
}
- DigestUtils digest = new DigestUtils(DigestUtils.getDigest(algorithm));
- String checksum = digest.digestAsHex(artifact.getFile());
+ final DigestUtils digest = new DigestUtils(DigestUtils.getDigest(algorithm));
+ final String checksum = digest.digestAsHex(artifact.getFile());
checksums.put(key, checksum);
}
return checksums;
@@ -142,14 +142,14 @@ private static Map getChecksums(Artifact artifact, String... alg
* @return A SLSA resource descriptor.
* @throws MojoExecutionException If an I/O error occurs retrieving the artifact.
*/
- public static ResourceDescriptor toResourceDescriptor(Artifact artifact, String algorithms) throws MojoExecutionException {
- ResourceDescriptor descriptor = new ResourceDescriptor();
+ public static ResourceDescriptor toResourceDescriptor(final Artifact artifact, final String algorithms) throws MojoExecutionException {
+ final ResourceDescriptor descriptor = new ResourceDescriptor();
descriptor.setName(getFileName(artifact));
descriptor.setUri(getPackageUrl(artifact));
if (artifact.getFile() != null) {
try {
descriptor.setDigest(getChecksums(artifact, StringUtils.split(algorithms, ",")));
- } catch (IOException e) {
+ } catch (final IOException e) {
throw new MojoExecutionException("Unable to compute hash for artifact file: " + artifact.getFile(), e);
}
}
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java b/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
index b6b175a71..67b8ea813 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
@@ -48,13 +48,13 @@ private BuildDefinitions() {
* @return a descriptor with digest and annotations populated from system properties
* @throws IOException if hashing the JDK directory fails
*/
- public static ResourceDescriptor jvm(Path javaHome) throws IOException {
- ResourceDescriptor descriptor = new ResourceDescriptor();
+ public static ResourceDescriptor jvm(final Path javaHome) throws IOException {
+ final ResourceDescriptor descriptor = new ResourceDescriptor();
descriptor.setName("JDK");
- Map digest = new HashMap<>();
+ final Map digest = new HashMap<>();
digest.put("gitTree", GitUtils.gitTree(javaHome));
descriptor.setDigest(digest);
- String[] propertyNames = {
+ final String[] propertyNames = {
"java.version", "java.version.date",
"java.vendor", "java.vendor.url", "java.vendor.version",
"java.home",
@@ -63,8 +63,8 @@ public static ResourceDescriptor jvm(Path javaHome) throws IOException {
"java.specification.version", "java.specification.maintenance.version",
"java.specification.vendor", "java.specification.name",
};
- Map annotations = new HashMap<>();
- for (String prop : propertyNames) {
+ final Map annotations = new HashMap<>();
+ for (final String prop : propertyNames) {
annotations.put(prop.substring("java.".length()), System.getProperty(prop));
}
descriptor.setAnnotations(annotations);
@@ -84,21 +84,21 @@ public static ResourceDescriptor jvm(Path javaHome) throws IOException {
* @return a descriptor for the Maven installation
* @throws IOException if hashing the Maven home directory fails
*/
- public static ResourceDescriptor maven(String version, Path mavenHome, ClassLoader coreClassLoader) throws IOException {
- ResourceDescriptor descriptor = new ResourceDescriptor();
+ public static ResourceDescriptor maven(final String version, final Path mavenHome, final ClassLoader coreClassLoader) throws IOException {
+ final ResourceDescriptor descriptor = new ResourceDescriptor();
descriptor.setName("Maven");
descriptor.setUri("pkg:maven/org.apache.maven/apache-maven@" + version);
- Map digest = new HashMap<>();
+ final Map digest = new HashMap<>();
digest.put("gitTree", GitUtils.gitTree(mavenHome));
descriptor.setDigest(digest);
- Properties buildProps = new Properties();
+ final Properties buildProps = new Properties();
try (InputStream in = coreClassLoader.getResourceAsStream("org/apache/maven/messages/build.properties")) {
if (in != null) {
buildProps.load(in);
}
}
if (!buildProps.isEmpty()) {
- Map annotations = new HashMap<>();
+ final Map annotations = new HashMap<>();
buildProps.forEach((key, value) -> annotations.put((String) key, value));
descriptor.setAnnotations(annotations);
}
@@ -112,17 +112,17 @@ public static ResourceDescriptor maven(String version, Path mavenHome, ClassLoad
* @return a map of parameter names to values
*/
public static Map externalParameters(final MavenSession session) {
- Map params = new HashMap<>();
+ final Map params = new HashMap<>();
params.put("jvm.args", ManagementFactory.getRuntimeMXBean().getInputArguments());
- MavenExecutionRequest request = session.getRequest();
+ final MavenExecutionRequest request = session.getRequest();
params.put("maven.goals", request.getGoals());
params.put("maven.profiles", request.getActiveProfiles());
params.put("maven.user.properties", request.getUserProperties());
params.put("maven.cmdline", commandLine(request));
- Map env = new HashMap<>();
+ final Map env = new HashMap<>();
params.put("env", env);
- for (Map.Entry entry : System.getenv().entrySet()) {
- String key = entry.getKey();
+ for (final Map.Entry entry : System.getenv().entrySet()) {
+ final String key = entry.getKey();
if ("TZ".equals(key) || "LANG".equals(key) || key.startsWith("LC_")) {
env.put(key, entry.getValue());
}
@@ -137,8 +137,8 @@ public static Map externalParameters(final MavenSession session)
* @return a string representation of the Maven command line
*/
static String commandLine(final MavenExecutionRequest request) {
- List args = new ArrayList<>(request.getGoals());
- String profiles = String.join(",", request.getActiveProfiles());
+ final List args = new ArrayList<>(request.getGoals());
+ final String profiles = String.join(",", request.getActiveProfiles());
if (!profiles.isEmpty()) {
args.add("-P" + profiles);
}
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java b/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java
index dc539bbd2..acbd7bed1 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java
@@ -42,11 +42,11 @@ public final class GitUtils {
* @return A hex-encoded SHA-1 tree hash.
* @throws IOException If the path is not a directory or an I/O error occurs.
*/
- public static String gitTree(Path path) throws IOException {
+ public static String gitTree(final Path path) throws IOException {
if (!Files.isDirectory(path)) {
throw new IOException("Path is not a directory: " + path);
}
- MessageDigest digest = DigestUtils.getSha1Digest();
+ final MessageDigest digest = DigestUtils.getSha1Digest();
return Hex.encodeHexString(GitIdentifiers.treeId(digest, path));
}
@@ -58,11 +58,11 @@ public static String gitTree(Path path) throws IOException {
* @return A download URI of the form {@code git+@}.
* @throws IOException If the current branch cannot be determined.
*/
- public static String scmToDownloadUri(String scmUri, Path repositoryPath) throws IOException {
+ public static String scmToDownloadUri(final String scmUri, final Path repositoryPath) throws IOException {
if (!scmUri.startsWith(SCM_GIT_PREFIX)) {
throw new IllegalArgumentException("Invalid scmUri: " + scmUri);
}
- String currentBranch = getCurrentBranch(repositoryPath);
+ final String currentBranch = getCurrentBranch(repositoryPath);
return "git+" + scmUri.substring(SCM_GIT_PREFIX.length()) + "@" + currentBranch;
}
@@ -75,9 +75,9 @@ public static String scmToDownloadUri(String scmUri, Path repositoryPath) throws
* @return The current branch name, or the commit SHA for a detached HEAD.
* @throws IOException If the {@code .git} directory cannot be found or read.
*/
- public static String getCurrentBranch(Path repositoryPath) throws IOException {
- Path gitDir = findGitDir(repositoryPath);
- String head = new String(Files.readAllBytes(gitDir.resolve("HEAD")), StandardCharsets.UTF_8).trim();
+ public static String getCurrentBranch(final Path repositoryPath) throws IOException {
+ final Path gitDir = findGitDir(repositoryPath);
+ final String head = new String(Files.readAllBytes(gitDir.resolve("HEAD")), StandardCharsets.UTF_8).trim();
if (head.startsWith("ref: refs/heads/")) {
return head.substring("ref: refs/heads/".length());
}
@@ -92,16 +92,16 @@ public static String getCurrentBranch(Path repositoryPath) throws IOException {
* @return The path to the {@code .git} directory (or file for worktrees).
* @throws IOException If no {@code .git} directory is found.
*/
- private static Path findGitDir(Path path) throws IOException {
+ private static Path findGitDir(final Path path) throws IOException {
Path current = path.toAbsolutePath();
while (current != null) {
- Path candidate = current.resolve(".git");
+ final Path candidate = current.resolve(".git");
if (Files.isDirectory(candidate)) {
return candidate;
}
if (Files.isRegularFile(candidate)) {
// git worktree: .git is a file containing "gitdir: /path/to/real/.git"
- String content = new String(Files.readAllBytes(candidate), StandardCharsets.UTF_8).trim();
+ final String content = new String(Files.readAllBytes(candidate), StandardCharsets.UTF_8).trim();
if (content.startsWith("gitdir: ")) {
return Paths.get(content.substring("gitdir: ".length()));
}
diff --git a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
index 9ba424459..84ec69cfd 100644
--- a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
+++ b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
@@ -212,8 +212,8 @@ public class BuildAttestationMojo extends AbstractMojo {
* @param mavenProjectHelper A helper to attach artifacts to the project.
*/
@Inject
- public BuildAttestationMojo(MavenProject project, ScmManager scmManager, RuntimeInformation runtimeInformation, MavenSession session,
- MavenProjectHelper mavenProjectHelper) {
+ public BuildAttestationMojo(final MavenProject project, final ScmManager scmManager, final RuntimeInformation runtimeInformation,
+ final MavenSession session, final MavenProjectHelper mavenProjectHelper) {
this.project = project;
this.scmManager = scmManager;
this.runtimeInformation = runtimeInformation;
@@ -244,7 +244,7 @@ public File getScmDirectory() {
*
* @param scmDirectory The SCM directory.
*/
- public void setScmDirectory(File scmDirectory) {
+ public void setScmDirectory(final File scmDirectory) {
this.scmDirectory = scmDirectory;
}
@@ -289,7 +289,7 @@ void setSigner(final AbstractGpgSigner signer) {
*
* @param algorithmNames A comma-separated list of {@link java.security.MessageDigest} algorithm names to use.
*/
- void setAlgorithmNames(String algorithmNames) {
+ void setAlgorithmNames(final String algorithmNames) {
this.algorithmNames = algorithmNames;
}
@@ -309,21 +309,21 @@ private AbstractGpgSigner getSigner() throws MojoFailureException {
@Override
public void execute() throws MojoFailureException, MojoExecutionException {
// Build definition
- BuildDefinition buildDefinition = new BuildDefinition();
+ final BuildDefinition buildDefinition = new BuildDefinition();
buildDefinition.setExternalParameters(BuildDefinitions.externalParameters(session));
buildDefinition.setResolvedDependencies(getBuildDependencies());
// Builder
- Builder builder = new Builder();
+ final Builder builder = new Builder();
// RunDetails
- RunDetails runDetails = new RunDetails();
+ final RunDetails runDetails = new RunDetails();
runDetails.setBuilder(builder);
runDetails.setMetadata(getBuildMetadata());
// Provenance
- Provenance provenance = new Provenance();
+ final Provenance provenance = new Provenance();
provenance.setBuildDefinition(buildDefinition);
provenance.setRunDetails(runDetails);
// Statement
- Statement statement = new Statement();
+ final Statement statement = new Statement();
statement.setSubject(getSubjects());
statement.setPredicate(provenance);
@@ -348,7 +348,7 @@ private Path ensureOutputDirectory() throws MojoExecutionException {
if (!Files.exists(outputPath)) {
Files.createDirectories(outputPath);
}
- } catch (IOException e) {
+ } catch (final IOException e) {
throw new MojoExecutionException("Could not create output directory.", e);
}
return outputPath;
@@ -380,7 +380,7 @@ private void signAndWriteStatement(final Statement statement, final Path outputP
final byte[] statementBytes;
try {
statementBytes = OBJECT_MAPPER.writeValueAsBytes(statement);
- } catch (JsonProcessingException e) {
+ } catch (final JsonProcessingException e) {
throw new MojoExecutionException("Failed to serialize attestation statement", e);
}
final AbstractGpgSigner signer = getSigner();
@@ -410,7 +410,7 @@ private void writeAndAttach(final Object value, final Path artifactPath) throws
try (OutputStream os = Files.newOutputStream(artifactPath)) {
OBJECT_MAPPER.writeValue(os, value);
os.write('\n');
- } catch (IOException e) {
+ } catch (final IOException e) {
throw new MojoExecutionException("Could not write attestation to: " + artifactPath, e);
}
if (!skipAttach) {
@@ -427,9 +427,9 @@ private void writeAndAttach(final Object value, final Path artifactPath) throws
* @throws MojoExecutionException If artifact hashing fails.
*/
private List getSubjects() throws MojoExecutionException {
- List subjects = new ArrayList<>();
+ final List subjects = new ArrayList<>();
subjects.add(ArtifactUtils.toResourceDescriptor(project.getArtifact(), algorithmNames));
- for (Artifact artifact : project.getAttachedArtifacts()) {
+ for (final Artifact artifact : project.getAttachedArtifacts()) {
subjects.add(ArtifactUtils.toResourceDescriptor(artifact, algorithmNames));
}
return subjects;
@@ -442,13 +442,13 @@ private List getSubjects() throws MojoExecutionException {
* @throws MojoExecutionException If any dependency cannot be resolved or hashed.
*/
private List getBuildDependencies() throws MojoExecutionException {
- List dependencies = new ArrayList<>();
+ final List dependencies = new ArrayList<>();
try {
dependencies.add(BuildDefinitions.jvm(Paths.get(System.getProperty("java.home"))));
dependencies.add(BuildDefinitions.maven(runtimeInformation.getMavenVersion(), mavenHome.toPath(),
runtimeInformation.getClass().getClassLoader()));
dependencies.add(getScmDescriptor());
- } catch (IOException e) {
+ } catch (final IOException e) {
throw new MojoExecutionException(e);
}
dependencies.addAll(getProjectDependencies());
@@ -462,8 +462,8 @@ private List getBuildDependencies() throws MojoExecutionExce
* @throws MojoExecutionException If a dependency artifact cannot be described.
*/
private List getProjectDependencies() throws MojoExecutionException {
- List dependencies = new ArrayList<>();
- for (Artifact artifact : project.getArtifacts()) {
+ final List dependencies = new ArrayList<>();
+ for (final Artifact artifact : project.getArtifacts()) {
dependencies.add(ArtifactUtils.toResourceDescriptor(artifact, algorithmNames));
}
return dependencies;
@@ -477,11 +477,11 @@ private List getProjectDependencies() throws MojoExecutionEx
* @throws MojoExecutionException If the SCM revision cannot be retrieved.
*/
private ResourceDescriptor getScmDescriptor() throws IOException, MojoExecutionException {
- ResourceDescriptor scmDescriptor = new ResourceDescriptor();
- String scmUri = GitUtils.scmToDownloadUri(scmConnectionUrl, scmDirectory.toPath());
+ final ResourceDescriptor scmDescriptor = new ResourceDescriptor();
+ final String scmUri = GitUtils.scmToDownloadUri(scmConnectionUrl, scmDirectory.toPath());
scmDescriptor.setUri(scmUri);
// Compute the revision
- Map digest = new HashMap<>();
+ final Map digest = new HashMap<>();
digest.put("gitCommit", getScmRevision());
scmDescriptor.setDigest(digest);
return scmDescriptor;
@@ -496,7 +496,7 @@ private ResourceDescriptor getScmDescriptor() throws IOException, MojoExecutionE
private ScmRepository getScmRepository() throws MojoExecutionException {
try {
return scmManager.makeScmRepository(scmConnectionUrl);
- } catch (ScmException e) {
+ } catch (final ScmException e) {
throw new MojoExecutionException("Failed to create SCM repository", e);
}
}
@@ -508,14 +508,14 @@ private ScmRepository getScmRepository() throws MojoExecutionException {
* @throws MojoExecutionException If the revision cannot be retrieved from SCM.
*/
private String getScmRevision() throws MojoExecutionException {
- ScmRepository scmRepository = getScmRepository();
- CommandParameters commandParameters = new CommandParameters();
+ final ScmRepository scmRepository = getScmRepository();
+ final CommandParameters commandParameters = new CommandParameters();
try {
- InfoScmResult result = scmManager.getProviderByRepository(scmRepository).info(scmRepository.getProviderRepository(),
+ final InfoScmResult result = scmManager.getProviderByRepository(scmRepository).info(scmRepository.getProviderRepository(),
new ScmFileSet(scmDirectory), commandParameters);
return getScmRevision(result);
- } catch (ScmException e) {
+ } catch (final ScmException e) {
throw new MojoExecutionException("Failed to retrieve SCM revision", e);
}
}
@@ -536,9 +536,9 @@ private String getScmRevision(final InfoScmResult result) throws MojoExecutionEx
throw new MojoExecutionException("No SCM revision information found for " + scmDirectory);
}
- InfoItem item = result.getInfoItems().get(0);
+ final InfoItem item = result.getInfoItems().get(0);
- String revision = item.getRevision();
+ final String revision = item.getRevision();
if (revision == null) {
throw new MojoExecutionException("Empty SCM revision returned for " + scmDirectory);
}
@@ -551,8 +551,8 @@ private String getScmRevision(final InfoScmResult result) throws MojoExecutionEx
* @return The build metadata.
*/
private BuildMetadata getBuildMetadata() {
- OffsetDateTime startedOn = session.getStartTime().toInstant().atOffset(ZoneOffset.UTC);
- OffsetDateTime finishedOn = OffsetDateTime.now(ZoneOffset.UTC);
+ final OffsetDateTime startedOn = session.getStartTime().toInstant().atOffset(ZoneOffset.UTC);
+ final OffsetDateTime finishedOn = OffsetDateTime.now(ZoneOffset.UTC);
return new BuildMetadata(null, startedOn, finishedOn);
}
}
diff --git a/src/test/java/org/apache/commons/release/plugin/internal/BuildDefinitionsTest.java b/src/test/java/org/apache/commons/release/plugin/internal/BuildDefinitionsTest.java
index 605657ebe..fe58a540e 100644
--- a/src/test/java/org/apache/commons/release/plugin/internal/BuildDefinitionsTest.java
+++ b/src/test/java/org/apache/commons/release/plugin/internal/BuildDefinitionsTest.java
@@ -50,7 +50,7 @@ static Stream commandLineArguments() {
@MethodSource("commandLineArguments")
void commandLineTest(final String description, final List goals, final List profiles,
final Properties userProperties, final String expected) {
- MavenExecutionRequest request = new DefaultMavenExecutionRequest();
+ final MavenExecutionRequest request = new DefaultMavenExecutionRequest();
request.setGoals(goals);
request.setActiveProfiles(profiles);
request.setUserProperties(userProperties);
@@ -58,7 +58,7 @@ void commandLineTest(final String description, final List goals, final L
}
private static Properties singletonProperties(final String key, final String value) {
- Properties p = new Properties();
+ final Properties p = new Properties();
p.setProperty(key, value);
return p;
}
diff --git a/src/test/java/org/apache/commons/release/plugin/internal/MojoUtils.java b/src/test/java/org/apache/commons/release/plugin/internal/MojoUtils.java
index 6a6c3f5bf..3cdef92fc 100644
--- a/src/test/java/org/apache/commons/release/plugin/internal/MojoUtils.java
+++ b/src/test/java/org/apache/commons/release/plugin/internal/MojoUtils.java
@@ -40,7 +40,7 @@
public final class MojoUtils {
private static ContainerConfiguration setupContainerConfiguration() {
- ClassWorld classWorld =
+ final ClassWorld classWorld =
new ClassWorld("plexus.core", Thread.currentThread().getContextClassLoader());
return new DefaultContainerConfiguration()
.setClassWorld(classWorld)
@@ -54,10 +54,10 @@ public static PlexusContainer setupContainer() throws PlexusContainerException {
}
public static RepositorySystemSession createRepositorySystemSession(
- PlexusContainer container, Path localRepositoryPath) throws ComponentLookupException, RepositoryException {
- LocalRepositoryManagerFactory factory = container.lookup(LocalRepositoryManagerFactory.class, "simple");
- DefaultRepositorySystemSession repoSession = new DefaultRepositorySystemSession();
- LocalRepositoryManager manager =
+ final PlexusContainer container, final Path localRepositoryPath) throws ComponentLookupException, RepositoryException {
+ final LocalRepositoryManagerFactory factory = container.lookup(LocalRepositoryManagerFactory.class, "simple");
+ final DefaultRepositorySystemSession repoSession = new DefaultRepositorySystemSession();
+ final LocalRepositoryManager manager =
factory.newInstance(repoSession, new LocalRepository(localRepositoryPath.toFile()));
repoSession.setLocalRepositoryManager(manager);
// Default policies
diff --git a/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java b/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
index c9aa84ef5..309b98fe9 100644
--- a/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
+++ b/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
@@ -90,29 +90,30 @@ static void setup() throws Exception {
}
private static MavenExecutionRequest createMavenExecutionRequest() {
- DefaultMavenExecutionRequest request = new DefaultMavenExecutionRequest();
+ final DefaultMavenExecutionRequest request = new DefaultMavenExecutionRequest();
request.setStartTime(Date.from(Instant.parse("2026-04-20T09:28:44Z")));
request.setActiveProfiles(Collections.singletonList("release"));
request.setGoals(Collections.singletonList("deploy"));
- Properties userProperties = new Properties();
+ final Properties userProperties = new Properties();
userProperties.setProperty("gpg.keyname", "3C8D57E0A2B5C6D7E8F9A0B1C2D3E4F5A6B7C8D9");
request.setUserProperties(userProperties);
return request;
}
@SuppressWarnings("deprecation")
- private static MavenSession createMavenSession(MavenExecutionRequest request, MavenExecutionResult result) {
+ private static MavenSession createMavenSession(final MavenExecutionRequest request, final MavenExecutionResult result) {
return new MavenSession(container, repoSession, request, result);
}
- private static BuildAttestationMojo createBuildAttestationMojo(MavenProject project, MavenProjectHelper projectHelper) throws ComponentLookupException {
- ScmManager scmManager = container.lookup(ScmManager.class);
- RuntimeInformation runtimeInfo = container.lookup(RuntimeInformation.class);
+ private static BuildAttestationMojo createBuildAttestationMojo(final MavenProject project, final MavenProjectHelper projectHelper)
+ throws ComponentLookupException {
+ final ScmManager scmManager = container.lookup(ScmManager.class);
+ final RuntimeInformation runtimeInfo = container.lookup(RuntimeInformation.class);
return new BuildAttestationMojo(project, scmManager, runtimeInfo,
createMavenSession(createMavenExecutionRequest(), new DefaultMavenExecutionResult()), projectHelper);
}
- private static void configureBuildAttestationMojo(BuildAttestationMojo mojo, boolean signAttestation) {
+ private static void configureBuildAttestationMojo(final BuildAttestationMojo mojo, final boolean signAttestation) {
mojo.setOutputDirectory(new File("target/attestations"));
mojo.setScmDirectory(new File("."));
mojo.setScmConnectionUrl("scm:git:https://github.com/apache/commons-text.git");
@@ -122,16 +123,16 @@ private static void configureBuildAttestationMojo(BuildAttestationMojo mojo, boo
mojo.setSigner(createMockSigner());
}
- private static MavenProject createMavenProject(MavenProjectHelper projectHelper, MavenRepositorySystem repoSystem) throws Exception {
- File pomFile = new File(ARTIFACTS_DIR + "commons-text-1.4.pom");
- Model model;
+ private static MavenProject createMavenProject(final MavenProjectHelper projectHelper, final MavenRepositorySystem repoSystem) throws Exception {
+ final File pomFile = new File(ARTIFACTS_DIR + "commons-text-1.4.pom");
+ final Model model;
try (InputStream in = Files.newInputStream(pomFile.toPath())) {
model = new MavenXpp3Reader().read(in);
}
// Group id is inherited from the missing parent, so we override it
model.setGroupId("org.apache.commons");
- MavenProject project = new MavenProject(model);
- Artifact artifact = repoSystem.createArtifact(model.getArtifactId(), model.getArtifactId(), model.getVersion(), null, "jar");
+ final MavenProject project = new MavenProject(model);
+ final Artifact artifact = repoSystem.createArtifact(model.getArtifactId(), model.getArtifactId(), model.getVersion(), null, "jar");
artifact.setFile(new File(ARTIFACTS_DIR + "commons-text-1.4.jar"));
project.setArtifact(artifact);
projectHelper.attachArtifact(project, "pom", null, pomFile);
@@ -193,9 +194,9 @@ private static void assertStatementContent(final JsonNode statement) {
assertJsonEquals(expectedStatement.at("/predicate/buildDefinition/resolvedDependencies"),
statement.at("/predicate/buildDefinition/resolvedDependencies"),
JsonAssert.when(Option.IGNORING_VALUES).whenIgnoringPaths("[0].annotations"));
- Set expectedJdkFields = fieldNames(
+ final Set expectedJdkFields = fieldNames(
expectedStatement.at("/predicate/buildDefinition/resolvedDependencies/0/annotations"));
- Set actualJdkFields = fieldNames(
+ final Set actualJdkFields = fieldNames(
statement.at("/predicate/buildDefinition/resolvedDependencies/0/annotations"));
assertEquals(expectedJdkFields, actualJdkFields);
assertJsonEquals(expectedStatement.at("/predicate/runDetails"),
@@ -204,8 +205,8 @@ private static void assertStatementContent(final JsonNode statement) {
}
private static Set fieldNames(final JsonNode node) {
- Set names = new TreeSet<>();
- Iterator it = node.fieldNames();
+ final Set names = new TreeSet<>();
+ final Iterator it = node.fieldNames();
while (it.hasNext()) {
names.add(it.next());
}
@@ -214,37 +215,37 @@ private static Set fieldNames(final JsonNode node) {
@Test
void attestationTest() throws Exception {
- MavenProjectHelper projectHelper = container.lookup(MavenProjectHelper.class);
- MavenRepositorySystem repoSystem = container.lookup(MavenRepositorySystem.class);
- MavenProject project = createMavenProject(projectHelper, repoSystem);
+ final MavenProjectHelper projectHelper = container.lookup(MavenProjectHelper.class);
+ final MavenRepositorySystem repoSystem = container.lookup(MavenRepositorySystem.class);
+ final MavenProject project = createMavenProject(projectHelper, repoSystem);
- BuildAttestationMojo mojo = createBuildAttestationMojo(project, projectHelper);
+ final BuildAttestationMojo mojo = createBuildAttestationMojo(project, projectHelper);
configureBuildAttestationMojo(mojo, false);
mojo.execute();
- JsonNode statement = OBJECT_MAPPER.readTree(getAttestation(project).getFile());
+ final JsonNode statement = OBJECT_MAPPER.readTree(getAttestation(project).getFile());
assertStatementContent(statement);
}
@Test
void signingTest() throws Exception {
- MavenProjectHelper projectHelper = container.lookup(MavenProjectHelper.class);
- MavenRepositorySystem repoSystem = container.lookup(MavenRepositorySystem.class);
- MavenProject project = createMavenProject(projectHelper, repoSystem);
+ final MavenProjectHelper projectHelper = container.lookup(MavenProjectHelper.class);
+ final MavenRepositorySystem repoSystem = container.lookup(MavenRepositorySystem.class);
+ final MavenProject project = createMavenProject(projectHelper, repoSystem);
- BuildAttestationMojo mojo = createBuildAttestationMojo(project, projectHelper);
+ final BuildAttestationMojo mojo = createBuildAttestationMojo(project, projectHelper);
configureBuildAttestationMojo(mojo, true);
mojo.execute();
- String envelopeJson = new String(Files.readAllBytes(getAttestation(project).getFile().toPath()), StandardCharsets.UTF_8);
+ final String envelopeJson = new String(Files.readAllBytes(getAttestation(project).getFile().toPath()), StandardCharsets.UTF_8);
assertJsonPartEquals(DsseEnvelope.PAYLOAD_TYPE, envelopeJson, "payloadType");
assertJsonNodePresent(envelopeJson, "signatures[0]");
assertJsonNodeAbsent(envelopeJson, "signatures[1]");
assertJsonPartEquals("${json-unit.regex}.+", envelopeJson, "signatures[0].sig");
- DsseEnvelope envelope = OBJECT_MAPPER.readValue(envelopeJson.trim(), DsseEnvelope.class);
- JsonNode statement = OBJECT_MAPPER.readTree(envelope.getPayload());
+ final DsseEnvelope envelope = OBJECT_MAPPER.readValue(envelopeJson.trim(), DsseEnvelope.class);
+ final JsonNode statement = OBJECT_MAPPER.readTree(envelope.getPayload());
assertStatementContent(statement);
}
}
From db99b3c9904ee9127d8469d7d3c0994606fa8ca0 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Mon, 20 Apr 2026 14:18:22 +0200
Subject: [PATCH 25/51] fix: indentation
---
.../release/plugin/slsa/v1_2/RunDetails.java | 212 +++++++++---------
1 file changed, 110 insertions(+), 102 deletions(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/RunDetails.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/RunDetails.java
index 7b20b5a1d..85d8f6b0b 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/RunDetails.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/RunDetails.java
@@ -30,108 +30,116 @@
@JsonInclude(JsonInclude.Include.NON_NULL)
public class RunDetails {
- /** Entity that executed the build. */
- @JsonProperty("builder")
- private Builder builder;
-
- /** Metadata about the build invocation. */
- @JsonProperty("metadata")
- private BuildMetadata metadata;
-
- /** Artifacts produced as a side effect of the build. */
- @JsonProperty("byproducts")
- private List byproducts;
-
- /** Creates a new RunDetails instance. */
- public RunDetails() {
- }
-
- /**
- * Creates a new RunDetails with the given builder and metadata.
- *
- * @param builder entity that executed the build
- * @param metadata metadata about the build invocation
- */
- public RunDetails(Builder builder, BuildMetadata metadata) {
- this.builder = builder;
- this.metadata = metadata;
- }
-
- /**
- * Gets the builder that executed the invocation.
- *
- * Trusted to have correctly performed the operation and populated this provenance.
- *
- * @return the builder, or {@code null} if not set
- */
- public Builder getBuilder() {
- return builder;
- }
-
- /**
- * Sets the builder that executed the invocation.
- *
- * @param builder the builder
- */
- public void setBuilder(Builder builder) {
- this.builder = builder;
- }
-
- /**
- * Gets the metadata about the build invocation, including its identifier and timing.
- *
- * @return the build metadata, or {@code null} if not set
- */
- public BuildMetadata getMetadata() {
- return metadata;
- }
-
- /**
- * Sets the metadata about the build invocation.
- *
- * @param metadata the build metadata
- */
- public void setMetadata(BuildMetadata metadata) {
- this.metadata = metadata;
- }
-
- /**
- * Gets artifacts produced as a side effect of the build that are not the primary output.
- *
- * @return the list of byproduct artifacts, or {@code null} if not set
- */
- public List getByproducts() {
- return byproducts;
- }
-
- /**
- * Sets the artifacts produced as a side effect of the build that are not the primary output.
- *
- * @param byproducts the list of byproduct artifacts
- */
- public void setByproducts(List byproducts) {
- this.byproducts = byproducts;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
+ /**
+ * Entity that executed the build.
+ */
+ @JsonProperty("builder")
+ private Builder builder;
+
+ /**
+ * Metadata about the build invocation.
+ */
+ @JsonProperty("metadata")
+ private BuildMetadata metadata;
+
+ /**
+ * Artifacts produced as a side effect of the build.
+ */
+ @JsonProperty("byproducts")
+ private List byproducts;
+
+ /**
+ * Creates a new RunDetails instance.
+ */
+ public RunDetails() {
}
- if (o == null || getClass() != o.getClass()) {
- return false;
+
+ /**
+ * Creates a new RunDetails with the given builder and metadata.
+ *
+ * @param builder entity that executed the build
+ * @param metadata metadata about the build invocation
+ */
+ public RunDetails(Builder builder, BuildMetadata metadata) {
+ this.builder = builder;
+ this.metadata = metadata;
+ }
+
+ /**
+ * Gets the builder that executed the invocation.
+ *
+ * Trusted to have correctly performed the operation and populated this provenance.
+ *
+ * @return the builder, or {@code null} if not set
+ */
+ public Builder getBuilder() {
+ return builder;
+ }
+
+ /**
+ * Sets the builder that executed the invocation.
+ *
+ * @param builder the builder
+ */
+ public void setBuilder(Builder builder) {
+ this.builder = builder;
+ }
+
+ /**
+ * Gets the metadata about the build invocation, including its identifier and timing.
+ *
+ * @return the build metadata, or {@code null} if not set
+ */
+ public BuildMetadata getMetadata() {
+ return metadata;
+ }
+
+ /**
+ * Sets the metadata about the build invocation.
+ *
+ * @param metadata the build metadata
+ */
+ public void setMetadata(BuildMetadata metadata) {
+ this.metadata = metadata;
+ }
+
+ /**
+ * Gets artifacts produced as a side effect of the build that are not the primary output.
+ *
+ * @return the list of byproduct artifacts, or {@code null} if not set
+ */
+ public List getByproducts() {
+ return byproducts;
+ }
+
+ /**
+ * Sets the artifacts produced as a side effect of the build that are not the primary output.
+ *
+ * @param byproducts the list of byproduct artifacts
+ */
+ public void setByproducts(List byproducts) {
+ this.byproducts = byproducts;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ RunDetails that = (RunDetails) o;
+ return Objects.equals(builder, that.builder) && Objects.equals(metadata, that.metadata) && Objects.equals(byproducts, that.byproducts);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(builder, metadata, byproducts);
+ }
+
+ @Override
+ public String toString() {
+ return "RunDetails{builder=" + builder + ", metadata=" + metadata + ", byproducts=" + byproducts + '}';
}
- RunDetails that = (RunDetails) o;
- return Objects.equals(builder, that.builder) && Objects.equals(metadata, that.metadata) && Objects.equals(byproducts, that.byproducts);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(builder, metadata, byproducts);
- }
-
- @Override
- public String toString() {
- return "RunDetails{builder=" + builder + ", metadata=" + metadata + ", byproducts=" + byproducts + '}';
- }
}
From 717bc2cf93585834136cc4b29e3ea52053e8a656 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Mon, 20 Apr 2026 14:22:37 +0200
Subject: [PATCH 26/51] fix: sort members
---
.../plugin/internal/ArtifactUtils.java | 64 +--
.../plugin/internal/BuildDefinitions.java | 78 +--
.../release/plugin/internal/DsseUtils.java | 104 ++--
.../release/plugin/internal/GitUtils.java | 90 ++--
.../plugin/mojos/BuildAttestationMojo.java | 491 +++++++++---------
.../plugin/slsa/v1_2/BuildDefinition.java | 94 ++--
.../plugin/slsa/v1_2/BuildMetadata.java | 78 ++-
.../release/plugin/slsa/v1_2/Builder.java | 72 ++-
.../plugin/slsa/v1_2/DsseEnvelope.java | 73 ++-
.../release/plugin/slsa/v1_2/Provenance.java | 50 +-
.../plugin/slsa/v1_2/ResourceDescriptor.java | 164 +++---
.../release/plugin/slsa/v1_2/RunDetails.java | 72 ++-
.../release/plugin/slsa/v1_2/Signature.java | 44 +-
.../release/plugin/slsa/v1_2/Statement.java | 79 ++-
.../plugin/internal/BuildDefinitionsTest.java | 12 +-
.../release/plugin/internal/MojoUtils.java | 28 +-
.../mojos/BuildAttestationMojoTest.java | 146 +++---
17 files changed, 851 insertions(+), 888 deletions(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java b/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java
index e2e2aa75e..17a3e8728 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java
@@ -61,19 +61,27 @@ public final class ArtifactUtils {
IN_TOTO_DIGEST_NAMES = Collections.unmodifiableMap(m);
}
- /** No instances. */
- private ArtifactUtils() {
- // prevent instantiation
- }
-
/**
- * Gets the filename of an artifact in the default Maven repository layout.
+ * Gets a map of checksum algorithm names to hex-encoded digest values for the given artifact file.
*
* @param artifact A Maven artifact.
- * @return A filename.
+ * @param algorithms JSSE names of algorithms to use
+ * @return A map of checksum algorithm names to hex-encoded digest values.
+ * @throws IOException If an I/O error occurs reading the artifact file.
+ * @throws IllegalArgumentException If any of the algorithms is not supported.
*/
- public static String getFileName(final Artifact artifact) {
- return getFileName(artifact, artifact.getArtifactHandler().getExtension());
+ private static Map getChecksums(final Artifact artifact, final String... algorithms) throws IOException {
+ final Map checksums = new HashMap<>();
+ for (final String algorithm : algorithms) {
+ final String key = IN_TOTO_DIGEST_NAMES.get(algorithm);
+ if (key == null) {
+ throw new IllegalArgumentException("Invalid algorithm name for in-toto attestation: " + algorithm);
+ }
+ final DigestUtils digest = new DigestUtils(DigestUtils.getDigest(algorithm));
+ final String checksum = digest.digestAsHex(artifact.getFile());
+ checksums.put(key, checksum);
+ }
+ return checksums;
}
/**
@@ -93,6 +101,16 @@ public static String getFileName(final Artifact artifact, final String extension
return fileName.toString();
}
+ /**
+ * Gets the filename of an artifact in the default Maven repository layout.
+ *
+ * @param artifact A Maven artifact.
+ * @return A filename.
+ */
+ public static String getFileName(final Artifact artifact) {
+ return getFileName(artifact, artifact.getArtifactHandler().getExtension());
+ }
+
/**
* Gets the Package URL corresponding to this artifact.
*
@@ -111,29 +129,6 @@ public static String getPackageUrl(final Artifact artifact) {
return sb.toString();
}
- /**
- * Gets a map of checksum algorithm names to hex-encoded digest values for the given artifact file.
- *
- * @param artifact A Maven artifact.
- * @param algorithms JSSE names of algorithms to use
- * @return A map of checksum algorithm names to hex-encoded digest values.
- * @throws IOException If an I/O error occurs reading the artifact file.
- * @throws IllegalArgumentException If any of the algorithms is not supported.
- */
- private static Map getChecksums(final Artifact artifact, final String... algorithms) throws IOException {
- final Map checksums = new HashMap<>();
- for (final String algorithm : algorithms) {
- final String key = IN_TOTO_DIGEST_NAMES.get(algorithm);
- if (key == null) {
- throw new IllegalArgumentException("Invalid algorithm name for in-toto attestation: " + algorithm);
- }
- final DigestUtils digest = new DigestUtils(DigestUtils.getDigest(algorithm));
- final String checksum = digest.digestAsHex(artifact.getFile());
- checksums.put(key, checksum);
- }
- return checksums;
- }
-
/**
* Converts a Maven artifact to a SLSA {@link ResourceDescriptor}.
*
@@ -155,4 +150,9 @@ public static ResourceDescriptor toResourceDescriptor(final Artifact artifact, f
}
return descriptor;
}
+
+ /** No instances. */
+ private ArtifactUtils() {
+ // prevent instantiation
+ }
}
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java b/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
index 67b8ea813..996e57ffe 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
@@ -36,9 +36,44 @@
public final class BuildDefinitions {
/**
- * No instances.
+ * Reconstructs the Maven command line string from the given execution request.
+ *
+ * @param request the Maven execution request
+ * @return a string representation of the Maven command line
*/
- private BuildDefinitions() {
+ static String commandLine(final MavenExecutionRequest request) {
+ final List args = new ArrayList<>(request.getGoals());
+ final String profiles = String.join(",", request.getActiveProfiles());
+ if (!profiles.isEmpty()) {
+ args.add("-P" + profiles);
+ }
+ request.getUserProperties().forEach((key, value) -> args.add("-D" + key + "=" + value));
+ return String.join(" ", args);
+ }
+
+ /**
+ * Returns a map of external build parameters captured from the current JVM and Maven session.
+ *
+ * @param session the current Maven session
+ * @return a map of parameter names to values
+ */
+ public static Map externalParameters(final MavenSession session) {
+ final Map params = new HashMap<>();
+ params.put("jvm.args", ManagementFactory.getRuntimeMXBean().getInputArguments());
+ final MavenExecutionRequest request = session.getRequest();
+ params.put("maven.goals", request.getGoals());
+ params.put("maven.profiles", request.getActiveProfiles());
+ params.put("maven.user.properties", request.getUserProperties());
+ params.put("maven.cmdline", commandLine(request));
+ final Map env = new HashMap<>();
+ params.put("env", env);
+ for (final Map.Entry entry : System.getenv().entrySet()) {
+ final String key = entry.getKey();
+ if ("TZ".equals(key) || "LANG".equals(key) || key.startsWith("LC_")) {
+ env.put(key, entry.getValue());
+ }
+ }
+ return params;
}
/**
@@ -106,43 +141,8 @@ public static ResourceDescriptor maven(final String version, final Path mavenHom
}
/**
- * Returns a map of external build parameters captured from the current JVM and Maven session.
- *
- * @param session the current Maven session
- * @return a map of parameter names to values
- */
- public static Map externalParameters(final MavenSession session) {
- final Map params = new HashMap<>();
- params.put("jvm.args", ManagementFactory.getRuntimeMXBean().getInputArguments());
- final MavenExecutionRequest request = session.getRequest();
- params.put("maven.goals", request.getGoals());
- params.put("maven.profiles", request.getActiveProfiles());
- params.put("maven.user.properties", request.getUserProperties());
- params.put("maven.cmdline", commandLine(request));
- final Map env = new HashMap<>();
- params.put("env", env);
- for (final Map.Entry entry : System.getenv().entrySet()) {
- final String key = entry.getKey();
- if ("TZ".equals(key) || "LANG".equals(key) || key.startsWith("LC_")) {
- env.put(key, entry.getValue());
- }
- }
- return params;
- }
-
- /**
- * Reconstructs the Maven command line string from the given execution request.
- *
- * @param request the Maven execution request
- * @return a string representation of the Maven command line
+ * No instances.
*/
- static String commandLine(final MavenExecutionRequest request) {
- final List args = new ArrayList<>(request.getGoals());
- final String profiles = String.join(",", request.getActiveProfiles());
- if (!profiles.isEmpty()) {
- args.add("-P" + profiles);
- }
- request.getUserProperties().forEach((key, value) -> args.add("-D" + key + "=" + value));
- return String.join(" ", args);
+ private BuildDefinitions() {
}
}
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/DsseUtils.java b/src/main/java/org/apache/commons/release/plugin/internal/DsseUtils.java
index 20267a497..3ad334ff2 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/DsseUtils.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/DsseUtils.java
@@ -47,12 +47,6 @@
*/
public final class DsseUtils {
- /**
- * Not instantiable.
- */
- private DsseUtils() {
- }
-
/**
* Creates and prepares a {@link GpgSigner} from the given configuration.
*
@@ -79,6 +73,56 @@ public static AbstractGpgSigner createGpgSigner(final String executable, final b
return signer;
}
+ /**
+ * Extracts the key identifier from a binary OpenPGP Signature Packet.
+ *
+ * @param sigBytes raw binary OpenPGP Signature Packet bytes
+ * @return uppercase hex-encoded fingerprint or key ID string
+ * @throws MojoExecutionException if {@code sigBytes} cannot be parsed as an OpenPGP signature
+ */
+ public static String getKeyId(final byte[] sigBytes) throws MojoExecutionException {
+ try {
+ final PGPSignatureList sigList = (PGPSignatureList) new BcPGPObjectFactory(sigBytes).nextObject();
+ final PGPSignature sig = sigList.get(0);
+ final PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
+ if (hashed != null) {
+ final IssuerFingerprint fp = hashed.getIssuerFingerprint();
+ if (fp != null) {
+ return Hex.encodeHexString(fp.getFingerprint());
+ }
+ }
+ return Long.toHexString(sig.getKeyID()).toUpperCase(Locale.ROOT);
+ } catch (final IOException e) {
+ throw new MojoExecutionException("Failed to extract key ID from signature", e);
+ }
+ }
+
+ /**
+ * Signs {@code paeFile} and returns the raw OpenPGP signature bytes.
+ *
+ * The signer must already have {@link AbstractGpgSigner#prepare()} called before this method is invoked.
+ *
+ * @param signer the configured, prepared signer
+ * @param path path to the file to sign
+ * @return raw binary PGP signature bytes
+ * @throws MojoExecutionException if signing or signature decoding fails
+ */
+ public static byte[] signFile(final AbstractGpgSigner signer, final Path path) throws MojoExecutionException {
+ final Path signaturePath = signer.generateSignatureForArtifact(path.toFile()).toPath();
+ final byte[] signatureBytes;
+ try (InputStream in = Files.newInputStream(signaturePath); ArmoredInputStream armoredIn = new ArmoredInputStream(in)) {
+ signatureBytes = IOUtils.toByteArray(armoredIn);
+ } catch (final IOException e) {
+ throw new MojoExecutionException("Failed to read signature file: " + signaturePath, e);
+ }
+ try {
+ Files.delete(signaturePath);
+ } catch (final IOException e) {
+ throw new MojoExecutionException("Failed to delete signature file: " + signaturePath, e);
+ }
+ return signatureBytes;
+ }
+
/**
* Serializes {@code statement} to JSON using the DSSE Pre-Authentication Encoding (PAE).
*
@@ -127,52 +171,8 @@ public static Path writePaeFile(final byte[] statementBytes, final Path buildDir
}
/**
- * Signs {@code paeFile} and returns the raw OpenPGP signature bytes.
- *
- * The signer must already have {@link AbstractGpgSigner#prepare()} called before this method is invoked.
- *
- * @param signer the configured, prepared signer
- * @param path path to the file to sign
- * @return raw binary PGP signature bytes
- * @throws MojoExecutionException if signing or signature decoding fails
- */
- public static byte[] signFile(final AbstractGpgSigner signer, final Path path) throws MojoExecutionException {
- final Path signaturePath = signer.generateSignatureForArtifact(path.toFile()).toPath();
- final byte[] signatureBytes;
- try (InputStream in = Files.newInputStream(signaturePath); ArmoredInputStream armoredIn = new ArmoredInputStream(in)) {
- signatureBytes = IOUtils.toByteArray(armoredIn);
- } catch (final IOException e) {
- throw new MojoExecutionException("Failed to read signature file: " + signaturePath, e);
- }
- try {
- Files.delete(signaturePath);
- } catch (final IOException e) {
- throw new MojoExecutionException("Failed to delete signature file: " + signaturePath, e);
- }
- return signatureBytes;
- }
-
- /**
- * Extracts the key identifier from a binary OpenPGP Signature Packet.
- *
- * @param sigBytes raw binary OpenPGP Signature Packet bytes
- * @return uppercase hex-encoded fingerprint or key ID string
- * @throws MojoExecutionException if {@code sigBytes} cannot be parsed as an OpenPGP signature
+ * Not instantiable.
*/
- public static String getKeyId(final byte[] sigBytes) throws MojoExecutionException {
- try {
- final PGPSignatureList sigList = (PGPSignatureList) new BcPGPObjectFactory(sigBytes).nextObject();
- final PGPSignature sig = sigList.get(0);
- final PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
- if (hashed != null) {
- final IssuerFingerprint fp = hashed.getIssuerFingerprint();
- if (fp != null) {
- return Hex.encodeHexString(fp.getFingerprint());
- }
- }
- return Long.toHexString(sig.getKeyID()).toUpperCase(Locale.ROOT);
- } catch (final IOException e) {
- throw new MojoExecutionException("Failed to extract key ID from signature", e);
- }
+ private DsseUtils() {
}
}
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java b/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java
index acbd7bed1..ecaa19f35 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java
@@ -36,34 +36,29 @@ public final class GitUtils {
private static final String SCM_GIT_PREFIX = "scm:git:";
/**
- * Returns the Git tree hash for the given directory.
- *
- * @param path A directory path.
- * @return A hex-encoded SHA-1 tree hash.
- * @throws IOException If the path is not a directory or an I/O error occurs.
- */
- public static String gitTree(final Path path) throws IOException {
- if (!Files.isDirectory(path)) {
- throw new IOException("Path is not a directory: " + path);
- }
- final MessageDigest digest = DigestUtils.getSha1Digest();
- return Hex.encodeHexString(GitIdentifiers.treeId(digest, path));
- }
-
- /**
- * Converts an SCM URI to a download URI suffixed with the current branch name.
+ * Walks up the directory tree from {@code path} to find the {@code .git} directory.
*
- * @param scmUri A Maven SCM URI starting with {@code scm:git}.
- * @param repositoryPath A path inside the Git repository.
- * @return A download URI of the form {@code git+@}.
- * @throws IOException If the current branch cannot be determined.
+ * @param path A path inside the Git repository.
+ * @return The path to the {@code .git} directory (or file for worktrees).
+ * @throws IOException If no {@code .git} directory is found.
*/
- public static String scmToDownloadUri(final String scmUri, final Path repositoryPath) throws IOException {
- if (!scmUri.startsWith(SCM_GIT_PREFIX)) {
- throw new IllegalArgumentException("Invalid scmUri: " + scmUri);
+ private static Path findGitDir(final Path path) throws IOException {
+ Path current = path.toAbsolutePath();
+ while (current != null) {
+ final Path candidate = current.resolve(".git");
+ if (Files.isDirectory(candidate)) {
+ return candidate;
+ }
+ if (Files.isRegularFile(candidate)) {
+ // git worktree: .git is a file containing "gitdir: /path/to/real/.git"
+ final String content = new String(Files.readAllBytes(candidate), StandardCharsets.UTF_8).trim();
+ if (content.startsWith("gitdir: ")) {
+ return Paths.get(content.substring("gitdir: ".length()));
+ }
+ }
+ current = current.getParent();
}
- final String currentBranch = getCurrentBranch(repositoryPath);
- return "git+" + scmUri.substring(SCM_GIT_PREFIX.length()) + "@" + currentBranch;
+ throw new IOException("No .git directory found above: " + path);
}
/**
@@ -86,29 +81,34 @@ public static String getCurrentBranch(final Path repositoryPath) throws IOExcept
}
/**
- * Walks up the directory tree from {@code path} to find the {@code .git} directory.
+ * Returns the Git tree hash for the given directory.
*
- * @param path A path inside the Git repository.
- * @return The path to the {@code .git} directory (or file for worktrees).
- * @throws IOException If no {@code .git} directory is found.
+ * @param path A directory path.
+ * @return A hex-encoded SHA-1 tree hash.
+ * @throws IOException If the path is not a directory or an I/O error occurs.
*/
- private static Path findGitDir(final Path path) throws IOException {
- Path current = path.toAbsolutePath();
- while (current != null) {
- final Path candidate = current.resolve(".git");
- if (Files.isDirectory(candidate)) {
- return candidate;
- }
- if (Files.isRegularFile(candidate)) {
- // git worktree: .git is a file containing "gitdir: /path/to/real/.git"
- final String content = new String(Files.readAllBytes(candidate), StandardCharsets.UTF_8).trim();
- if (content.startsWith("gitdir: ")) {
- return Paths.get(content.substring("gitdir: ".length()));
- }
- }
- current = current.getParent();
+ public static String gitTree(final Path path) throws IOException {
+ if (!Files.isDirectory(path)) {
+ throw new IOException("Path is not a directory: " + path);
}
- throw new IOException("No .git directory found above: " + path);
+ final MessageDigest digest = DigestUtils.getSha1Digest();
+ return Hex.encodeHexString(GitIdentifiers.treeId(digest, path));
+ }
+
+ /**
+ * Converts an SCM URI to a download URI suffixed with the current branch name.
+ *
+ * @param scmUri A Maven SCM URI starting with {@code scm:git}.
+ * @param repositoryPath A path inside the Git repository.
+ * @return A download URI of the form {@code git+@}.
+ * @throws IOException If the current branch cannot be determined.
+ */
+ public static String scmToDownloadUri(final String scmUri, final Path repositoryPath) throws IOException {
+ if (!scmUri.startsWith(SCM_GIT_PREFIX)) {
+ throw new IllegalArgumentException("Invalid scmUri: " + scmUri);
+ }
+ final String currentBranch = getCurrentBranch(repositoryPath);
+ return "git+" + scmUri.substring(SCM_GIT_PREFIX.length()) + "@" + currentBranch;
}
/** No instances. */
diff --git a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
index 84ec69cfd..18b6dd20e 100644
--- a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
+++ b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
@@ -92,54 +92,11 @@ public class BuildAttestationMojo extends AbstractMojo {
OBJECT_MAPPER.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
}
- /**
- * The SCM connection URL for the current project.
- */
- @Parameter(defaultValue = "${project.scm.connection}", readonly = true)
- private String scmConnectionUrl;
-
- /**
- * The Maven home directory.
- */
- @Parameter(defaultValue = "${maven.home}", readonly = true)
- private File mavenHome;
-
- /**
- * Issue SCM actions at this local directory.
- */
- @Parameter(property = "commons.release.scmDirectory", defaultValue = "${basedir}")
- private File scmDirectory;
-
- /**
- * The output directory for the attestation file.
- */
- @Parameter(property = "commons.release.outputDirectory", defaultValue = "${project.build.directory}")
- private File outputDirectory;
-
- /**
- * Whether to skip attaching the attestation artifact to the project.
- */
- @Parameter(property = "commons.release.skipAttach")
- private boolean skipAttach;
-
- /**
- * Whether to sign the attestation envelope with GPG.
- */
- @Parameter(property = "commons.release.signAttestation", defaultValue = "true")
- private boolean signAttestation;
-
/**
* Checksum algorithms used in the generated attestation.
*/
@Parameter(property = "commons.release.checksums.algorithms", defaultValue = "SHA-512,SHA-256,SHA-1,MD5")
private String algorithmNames;
-
- /**
- * Path to the GPG executable; if not set, {@code gpg} is resolved from {@code PATH}.
- */
- @Parameter(property = "gpg.executable")
- private String executable;
-
/**
* Whether to include the default GPG keyring.
*
@@ -147,14 +104,11 @@ public class BuildAttestationMojo extends AbstractMojo {
*/
@Parameter(property = "gpg.defaultKeyring", defaultValue = "true")
private boolean defaultKeyring;
-
/**
- * GPG database lock mode passed via {@code --lock-once}, {@code --lock-multiple}, or
- * {@code --lock-never}; no lock flag is added when not set.
+ * Path to the GPG executable; if not set, {@code gpg} is resolved from {@code PATH}.
*/
- @Parameter(property = "gpg.lockMode")
- private String lockMode;
-
+ @Parameter(property = "gpg.executable")
+ private String executable;
/**
* Name or fingerprint of the GPG key to use for signing.
*
@@ -162,45 +116,74 @@ public class BuildAttestationMojo extends AbstractMojo {
*/
@Parameter(property = "gpg.keyname")
private String keyname;
-
/**
- * Whether to use gpg-agent for passphrase management.
- *
- * For GPG versions before 2.1, passes {@code --use-agent} or {@code --no-use-agent}
- * accordingly; ignored for GPG 2.1 and later where the agent is always used.
+ * GPG database lock mode passed via {@code --lock-once}, {@code --lock-multiple}, or
+ * {@code --lock-never}; no lock flag is added when not set.
*/
- @Parameter(property = "gpg.useagent", defaultValue = "true")
- private boolean useAgent;
-
+ @Parameter(property = "gpg.lockMode")
+ private String lockMode;
/**
- * GPG signer used for signing; lazily initialized from plugin parameters when {@code null}.
+ * The Maven home directory.
*/
- private AbstractGpgSigner signer;
-
+ @Parameter(defaultValue = "${maven.home}", readonly = true)
+ private File mavenHome;
/**
- * The current Maven project.
+ * Helper to attach artifacts to the project.
*/
- private final MavenProject project;
-
+ private final MavenProjectHelper mavenProjectHelper;
/**
- * SCM manager to detect the Git revision.
+ * The output directory for the attestation file.
*/
- private final ScmManager scmManager;
-
+ @Parameter(property = "commons.release.outputDirectory", defaultValue = "${project.build.directory}")
+ private File outputDirectory;
+ /**
+ * The current Maven project.
+ */
+ private final MavenProject project;
/**
* Runtime information.
*/
private final RuntimeInformation runtimeInformation;
-
+ /**
+ * The SCM connection URL for the current project.
+ */
+ @Parameter(defaultValue = "${project.scm.connection}", readonly = true)
+ private String scmConnectionUrl;
+ /**
+ * Issue SCM actions at this local directory.
+ */
+ @Parameter(property = "commons.release.scmDirectory", defaultValue = "${basedir}")
+ private File scmDirectory;
+ /**
+ * SCM manager to detect the Git revision.
+ */
+ private final ScmManager scmManager;
/**
* The current Maven session, used to resolve plugin dependencies.
*/
private final MavenSession session;
-
/**
- * Helper to attach artifacts to the project.
+ * Whether to sign the attestation envelope with GPG.
*/
- private final MavenProjectHelper mavenProjectHelper;
+ @Parameter(property = "commons.release.signAttestation", defaultValue = "true")
+ private boolean signAttestation;
+ /**
+ * GPG signer used for signing; lazily initialized from plugin parameters when {@code null}.
+ */
+ private AbstractGpgSigner signer;
+ /**
+ * Whether to skip attaching the attestation artifact to the project.
+ */
+ @Parameter(property = "commons.release.skipAttach")
+ private boolean skipAttach;
+ /**
+ * Whether to use gpg-agent for passphrase management.
+ *
+ * For GPG versions before 2.1, passes {@code --use-agent} or {@code --no-use-agent}
+ * accordingly; ignored for GPG 2.1 and later where the agent is always used.
+ */
+ @Parameter(property = "gpg.useagent", defaultValue = "true")
+ private boolean useAgent;
/**
* Creates a new instance with the given dependencies.
@@ -222,88 +205,21 @@ public BuildAttestationMojo(final MavenProject project, final ScmManager scmMana
}
/**
- * Sets the output directory for the attestation file.
- *
- * @param outputDirectory The output directory.
- */
- void setOutputDirectory(final File outputDirectory) {
- this.outputDirectory = outputDirectory;
- }
-
- /**
- * Gets the SCM directory.
- *
- * @return The SCM directory.
- */
- public File getScmDirectory() {
- return scmDirectory;
- }
-
- /**
- * Sets the SCM directory.
- *
- * @param scmDirectory The SCM directory.
- */
- public void setScmDirectory(final File scmDirectory) {
- this.scmDirectory = scmDirectory;
- }
-
- /**
- * Sets the public SCM connection URL.
- *
- * @param scmConnectionUrl The SCM connection URL.
- */
- void setScmConnectionUrl(final String scmConnectionUrl) {
- this.scmConnectionUrl = scmConnectionUrl;
- }
-
- /**
- * Sets the Maven home directory.
- *
- * @param mavenHome The Maven home directory.
- */
- void setMavenHome(final File mavenHome) {
- this.mavenHome = mavenHome;
- }
-
- /**
- * Sets whether to sign the attestation envelope.
- *
- * @param signAttestation {@code true} to sign, {@code false} to skip signing
- */
- void setSignAttestation(final boolean signAttestation) {
- this.signAttestation = signAttestation;
- }
-
- /**
- * Sets the GPG signer used for signing. Intended for testing.
- *
- * @param signer the signer to use
- */
- void setSigner(final AbstractGpgSigner signer) {
- this.signer = signer;
- }
-
- /**
- * Sets the list of checksum algorithms to use.
- *
- * @param algorithmNames A comma-separated list of {@link java.security.MessageDigest} algorithm names to use.
- */
- void setAlgorithmNames(final String algorithmNames) {
- this.algorithmNames = algorithmNames;
- }
-
- /**
- * Gets the GPG signer, creating and preparing it from plugin parameters if not already set.
+ * Creates the output directory if it does not already exist and returns its path.
*
- * @return the prepared signer
- * @throws MojoFailureException if signer preparation fails
+ * @return the output directory path
+ * @throws MojoExecutionException if the directory cannot be created
*/
- private AbstractGpgSigner getSigner() throws MojoFailureException {
- if (signer == null) {
- signer = DsseUtils.createGpgSigner(executable, defaultKeyring, lockMode, keyname, useAgent, getLog());
+ private Path ensureOutputDirectory() throws MojoExecutionException {
+ final Path outputPath = outputDirectory.toPath();
+ try {
+ if (!Files.exists(outputPath)) {
+ Files.createDirectories(outputPath);
+ }
+ } catch (final IOException e) {
+ throw new MojoExecutionException("Could not create output directory.", e);
}
- return signer;
+ return outputPath;
}
@Override
@@ -336,105 +252,6 @@ public void execute() throws MojoFailureException, MojoExecutionException {
}
}
- /**
- * Creates the output directory if it does not already exist and returns its path.
- *
- * @return the output directory path
- * @throws MojoExecutionException if the directory cannot be created
- */
- private Path ensureOutputDirectory() throws MojoExecutionException {
- final Path outputPath = outputDirectory.toPath();
- try {
- if (!Files.exists(outputPath)) {
- Files.createDirectories(outputPath);
- }
- } catch (final IOException e) {
- throw new MojoExecutionException("Could not create output directory.", e);
- }
- return outputPath;
- }
-
- /**
- * Serializes the attestation statement as a bare JSON line and writes it to {@code artifactPath}.
- *
- * @param statement the attestation statement to write
- * @param artifactPath the destination file path
- * @throws MojoExecutionException if the file cannot be written
- */
- private void writeStatement(final Statement statement, final Path artifactPath) throws MojoExecutionException {
- getLog().info("Writing attestation statement to: " + artifactPath);
- writeAndAttach(statement, artifactPath);
- }
-
- /**
- * Signs the attestation statement with GPG and writes it to {@code artifactPath}.
- *
- * @param statement the attestation statement to sign and write
- * @param outputPath directory used for intermediate PAE and signature files
- * @param artifactPath the destination file path for the envelope
- * @throws MojoExecutionException if serialization, signing, or file I/O fails
- * @throws MojoFailureException if the GPG signer cannot be prepared
- */
- private void signAndWriteStatement(final Statement statement, final Path outputPath,
- final Path artifactPath) throws MojoExecutionException, MojoFailureException {
- final byte[] statementBytes;
- try {
- statementBytes = OBJECT_MAPPER.writeValueAsBytes(statement);
- } catch (final JsonProcessingException e) {
- throw new MojoExecutionException("Failed to serialize attestation statement", e);
- }
- final AbstractGpgSigner signer = getSigner();
- final Path paeFile = DsseUtils.writePaeFile(statementBytes, outputPath);
- final byte[] sigBytes = DsseUtils.signFile(signer, paeFile);
-
- final Signature sig = new Signature();
- sig.setKeyid(DsseUtils.getKeyId(sigBytes));
- sig.setSig(sigBytes);
-
- final DsseEnvelope envelope = new DsseEnvelope();
- envelope.setPayload(statementBytes);
- envelope.setSignatures(Collections.singletonList(sig));
-
- getLog().info("Writing signed attestation envelope to: " + artifactPath);
- writeAndAttach(envelope, artifactPath);
- }
-
- /**
- * Writes {@code value} as a JSON line to {@code artifactPath} and optionally attaches it to the project.
- *
- * @param value the object to serialize
- * @param artifactPath the destination file path
- * @throws MojoExecutionException if the file cannot be written
- */
- private void writeAndAttach(final Object value, final Path artifactPath) throws MojoExecutionException {
- try (OutputStream os = Files.newOutputStream(artifactPath)) {
- OBJECT_MAPPER.writeValue(os, value);
- os.write('\n');
- } catch (final IOException e) {
- throw new MojoExecutionException("Could not write attestation to: " + artifactPath, e);
- }
- if (!skipAttach) {
- final Artifact mainArtifact = project.getArtifact();
- getLog().info(String.format("Attaching attestation as %s-%s.%s", mainArtifact.getArtifactId(), mainArtifact.getVersion(), ATTESTATION_EXTENSION));
- mavenProjectHelper.attachArtifact(project, ATTESTATION_EXTENSION, null, artifactPath.toFile());
- }
- }
-
- /**
- * Get the artifacts generated by the build.
- *
- * @return A list of resource descriptors for the build artifacts.
- * @throws MojoExecutionException If artifact hashing fails.
- */
- private List getSubjects() throws MojoExecutionException {
- final List subjects = new ArrayList<>();
- subjects.add(ArtifactUtils.toResourceDescriptor(project.getArtifact(), algorithmNames));
- for (final Artifact artifact : project.getAttachedArtifacts()) {
- subjects.add(ArtifactUtils.toResourceDescriptor(artifact, algorithmNames));
- }
- return subjects;
- }
-
/**
* Gets resource descriptors for the JVM, Maven installation, SCM source, and project dependencies.
*
@@ -455,6 +272,17 @@ private List getBuildDependencies() throws MojoExecutionExce
return dependencies;
}
+ /**
+ * Gets build metadata derived from the current Maven session, including start and finish timestamps.
+ *
+ * @return The build metadata.
+ */
+ private BuildMetadata getBuildMetadata() {
+ final OffsetDateTime startedOn = session.getStartTime().toInstant().atOffset(ZoneOffset.UTC);
+ final OffsetDateTime finishedOn = OffsetDateTime.now(ZoneOffset.UTC);
+ return new BuildMetadata(null, startedOn, finishedOn);
+ }
+
/**
* Gets resource descriptors for all resolved project dependencies.
*
@@ -487,6 +315,15 @@ private ResourceDescriptor getScmDescriptor() throws IOException, MojoExecutionE
return scmDescriptor;
}
+ /**
+ * Gets the SCM directory.
+ *
+ * @return The SCM directory.
+ */
+ public File getScmDirectory() {
+ return scmDirectory;
+ }
+
/**
* Gets an SCM repository from the configured connection URL.
*
@@ -546,13 +383,159 @@ private String getScmRevision(final InfoScmResult result) throws MojoExecutionEx
}
/**
- * Gets build metadata derived from the current Maven session, including start and finish timestamps.
+ * Gets the GPG signer, creating and preparing it from plugin parameters if not already set.
*
- * @return The build metadata.
+ * @return the prepared signer
+ * @throws MojoFailureException if signer preparation fails
*/
- private BuildMetadata getBuildMetadata() {
- final OffsetDateTime startedOn = session.getStartTime().toInstant().atOffset(ZoneOffset.UTC);
- final OffsetDateTime finishedOn = OffsetDateTime.now(ZoneOffset.UTC);
- return new BuildMetadata(null, startedOn, finishedOn);
+ private AbstractGpgSigner getSigner() throws MojoFailureException {
+ if (signer == null) {
+ signer = DsseUtils.createGpgSigner(executable, defaultKeyring, lockMode, keyname, useAgent, getLog());
+ }
+ return signer;
+ }
+
+ /**
+ * Get the artifacts generated by the build.
+ *
+ * @return A list of resource descriptors for the build artifacts.
+ * @throws MojoExecutionException If artifact hashing fails.
+ */
+ private List getSubjects() throws MojoExecutionException {
+ final List subjects = new ArrayList<>();
+ subjects.add(ArtifactUtils.toResourceDescriptor(project.getArtifact(), algorithmNames));
+ for (final Artifact artifact : project.getAttachedArtifacts()) {
+ subjects.add(ArtifactUtils.toResourceDescriptor(artifact, algorithmNames));
+ }
+ return subjects;
+ }
+
+ /**
+ * Sets the list of checksum algorithms to use.
+ *
+ * @param algorithmNames A comma-separated list of {@link java.security.MessageDigest} algorithm names to use.
+ */
+ void setAlgorithmNames(final String algorithmNames) {
+ this.algorithmNames = algorithmNames;
+ }
+
+ /**
+ * Sets the Maven home directory.
+ *
+ * @param mavenHome The Maven home directory.
+ */
+ void setMavenHome(final File mavenHome) {
+ this.mavenHome = mavenHome;
+ }
+
+ /**
+ * Sets the output directory for the attestation file.
+ *
+ * @param outputDirectory The output directory.
+ */
+ void setOutputDirectory(final File outputDirectory) {
+ this.outputDirectory = outputDirectory;
+ }
+
+ /**
+ * Sets the public SCM connection URL.
+ *
+ * @param scmConnectionUrl The SCM connection URL.
+ */
+ void setScmConnectionUrl(final String scmConnectionUrl) {
+ this.scmConnectionUrl = scmConnectionUrl;
+ }
+
+ /**
+ * Sets the SCM directory.
+ *
+ * @param scmDirectory The SCM directory.
+ */
+ public void setScmDirectory(final File scmDirectory) {
+ this.scmDirectory = scmDirectory;
+ }
+
+ /**
+ * Sets whether to sign the attestation envelope.
+ *
+ * @param signAttestation {@code true} to sign, {@code false} to skip signing
+ */
+ void setSignAttestation(final boolean signAttestation) {
+ this.signAttestation = signAttestation;
+ }
+
+ /**
+ * Sets the GPG signer used for signing. Intended for testing.
+ *
+ * @param signer the signer to use
+ */
+ void setSigner(final AbstractGpgSigner signer) {
+ this.signer = signer;
+ }
+
+ /**
+ * Signs the attestation statement with GPG and writes it to {@code artifactPath}.
+ *
+ * @param statement the attestation statement to sign and write
+ * @param outputPath directory used for intermediate PAE and signature files
+ * @param artifactPath the destination file path for the envelope
+ * @throws MojoExecutionException if serialization, signing, or file I/O fails
+ * @throws MojoFailureException if the GPG signer cannot be prepared
+ */
+ private void signAndWriteStatement(final Statement statement, final Path outputPath,
+ final Path artifactPath) throws MojoExecutionException, MojoFailureException {
+ final byte[] statementBytes;
+ try {
+ statementBytes = OBJECT_MAPPER.writeValueAsBytes(statement);
+ } catch (final JsonProcessingException e) {
+ throw new MojoExecutionException("Failed to serialize attestation statement", e);
+ }
+ final AbstractGpgSigner signer = getSigner();
+ final Path paeFile = DsseUtils.writePaeFile(statementBytes, outputPath);
+ final byte[] sigBytes = DsseUtils.signFile(signer, paeFile);
+
+ final Signature sig = new Signature();
+ sig.setKeyid(DsseUtils.getKeyId(sigBytes));
+ sig.setSig(sigBytes);
+
+ final DsseEnvelope envelope = new DsseEnvelope();
+ envelope.setPayload(statementBytes);
+ envelope.setSignatures(Collections.singletonList(sig));
+
+ getLog().info("Writing signed attestation envelope to: " + artifactPath);
+ writeAndAttach(envelope, artifactPath);
+ }
+
+ /**
+ * Writes {@code value} as a JSON line to {@code artifactPath} and optionally attaches it to the project.
+ *
+ * @param value the object to serialize
+ * @param artifactPath the destination file path
+ * @throws MojoExecutionException if the file cannot be written
+ */
+ private void writeAndAttach(final Object value, final Path artifactPath) throws MojoExecutionException {
+ try (OutputStream os = Files.newOutputStream(artifactPath)) {
+ OBJECT_MAPPER.writeValue(os, value);
+ os.write('\n');
+ } catch (final IOException e) {
+ throw new MojoExecutionException("Could not write attestation to: " + artifactPath, e);
+ }
+ if (!skipAttach) {
+ final Artifact mainArtifact = project.getArtifact();
+ getLog().info(String.format("Attaching attestation as %s-%s.%s", mainArtifact.getArtifactId(), mainArtifact.getVersion(), ATTESTATION_EXTENSION));
+ mavenProjectHelper.attachArtifact(project, ATTESTATION_EXTENSION, null, artifactPath.toFile());
+ }
+ }
+
+ /**
+ * Serializes the attestation statement as a bare JSON line and writes it to {@code artifactPath}.
+ *
+ * @param statement the attestation statement to write
+ * @param artifactPath the destination file path
+ * @throws MojoExecutionException if the file cannot be written
+ */
+ private void writeStatement(final Statement statement, final Path artifactPath) throws MojoExecutionException {
+ getLog().info("Writing attestation statement to: " + artifactPath);
+ writeAndAttach(statement, artifactPath);
}
}
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildDefinition.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildDefinition.java
index 661d0ccd9..a856e2c9e 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildDefinition.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildDefinition.java
@@ -64,6 +64,21 @@ public BuildDefinition(String buildType, Map externalParameters)
this.externalParameters = externalParameters;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ BuildDefinition that = (BuildDefinition) o;
+ return Objects.equals(buildType, that.buildType)
+ && Objects.equals(externalParameters, that.externalParameters)
+ && Objects.equals(internalParameters, that.internalParameters)
+ && Objects.equals(resolvedDependencies, that.resolvedDependencies);
+ }
+
/**
* Gets the URI indicating what type of build was performed.
*
@@ -75,15 +90,6 @@ public String getBuildType() {
return buildType;
}
- /**
- * Sets the URI indicating what type of build was performed.
- *
- * @param buildType the build type URI
- */
- public void setBuildType(String buildType) {
- this.buildType = buildType;
- }
-
/**
* Gets the inputs passed to the build, such as command-line arguments or environment variables.
*
@@ -93,15 +99,6 @@ public Map getExternalParameters() {
return externalParameters;
}
- /**
- * Sets the inputs passed to the build.
- *
- * @param externalParameters the external parameters map
- */
- public void setExternalParameters(Map externalParameters) {
- this.externalParameters = externalParameters;
- }
-
/**
* Gets the artifacts the build depends on, such as sources, dependencies, build tools, and base images,
* specified by URI and digest.
@@ -112,15 +109,6 @@ public Map getInternalParameters() {
return internalParameters;
}
- /**
- * Sets the artifacts the build depends on.
- *
- * @param internalParameters the internal parameters map
- */
- public void setInternalParameters(Map internalParameters) {
- this.internalParameters = internalParameters;
- }
-
/**
* Gets the materials that influenced the build.
*
@@ -132,33 +120,45 @@ public List getResolvedDependencies() {
return resolvedDependencies;
}
+ @Override
+ public int hashCode() {
+ return Objects.hash(buildType, externalParameters, internalParameters, resolvedDependencies);
+ }
+
/**
- * Sets the materials that influenced the build.
+ * Sets the URI indicating what type of build was performed.
*
- * @param resolvedDependencies the list of resolved dependencies
+ * @param buildType the build type URI
*/
- public void setResolvedDependencies(List resolvedDependencies) {
- this.resolvedDependencies = resolvedDependencies;
+ public void setBuildType(String buildType) {
+ this.buildType = buildType;
}
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- BuildDefinition that = (BuildDefinition) o;
- return Objects.equals(buildType, that.buildType)
- && Objects.equals(externalParameters, that.externalParameters)
- && Objects.equals(internalParameters, that.internalParameters)
- && Objects.equals(resolvedDependencies, that.resolvedDependencies);
+ /**
+ * Sets the inputs passed to the build.
+ *
+ * @param externalParameters the external parameters map
+ */
+ public void setExternalParameters(Map externalParameters) {
+ this.externalParameters = externalParameters;
}
- @Override
- public int hashCode() {
- return Objects.hash(buildType, externalParameters, internalParameters, resolvedDependencies);
+ /**
+ * Sets the artifacts the build depends on.
+ *
+ * @param internalParameters the internal parameters map
+ */
+ public void setInternalParameters(Map internalParameters) {
+ this.internalParameters = internalParameters;
+ }
+
+ /**
+ * Sets the materials that influenced the build.
+ *
+ * @param resolvedDependencies the list of resolved dependencies
+ */
+ public void setResolvedDependencies(List resolvedDependencies) {
+ this.resolvedDependencies = resolvedDependencies;
}
@Override
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildMetadata.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildMetadata.java
index 35d04e412..6f6316961 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildMetadata.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildMetadata.java
@@ -31,20 +31,18 @@
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BuildMetadata {
+ /** Timestamp when the build completed. */
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")
+ @JsonProperty("finishedOn")
+ private OffsetDateTime finishedOn;
/** Identifier for this build invocation. */
@JsonProperty("invocationId")
private String invocationId;
-
/** Timestamp when the build started. */
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")
@JsonProperty("startedOn")
private OffsetDateTime startedOn;
- /** Timestamp when the build completed. */
- @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")
- @JsonProperty("finishedOn")
- private OffsetDateTime finishedOn;
-
/** Creates a new BuildMetadata instance. */
public BuildMetadata() {
}
@@ -62,22 +60,31 @@ public BuildMetadata(String invocationId, OffsetDateTime startedOn, OffsetDateTi
this.finishedOn = finishedOn;
}
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof BuildMetadata)) {
+ return false;
+ }
+ BuildMetadata that = (BuildMetadata) o;
+ return Objects.equals(invocationId, that.invocationId) && Objects.equals(startedOn, that.startedOn) && Objects.equals(finishedOn, that.finishedOn);
+ }
+
/**
- * Gets the identifier for this build invocation.
+ * Gets the timestamp of when the build completed, serialized as RFC 3339 in UTC ({@code "Z"} suffix).
*
- * @return the invocation identifier, or {@code null} if not set
+ * @return the completion timestamp, or {@code null} if not set
*/
- public String getInvocationId() {
- return invocationId;
+ public OffsetDateTime getFinishedOn() {
+ return finishedOn;
}
/**
- * Sets the identifier for this build invocation.
+ * Gets the identifier for this build invocation.
*
- * @param invocationId the invocation identifier
+ * @return the invocation identifier, or {@code null} if not set
*/
- public void setInvocationId(String invocationId) {
- this.invocationId = invocationId;
+ public String getInvocationId() {
+ return invocationId;
}
/**
@@ -89,22 +96,9 @@ public OffsetDateTime getStartedOn() {
return startedOn;
}
- /**
- * Sets the timestamp of when the build started.
- *
- * @param startedOn the start timestamp
- */
- public void setStartedOn(OffsetDateTime startedOn) {
- this.startedOn = startedOn;
- }
-
- /**
- * Gets the timestamp of when the build completed, serialized as RFC 3339 in UTC ({@code "Z"} suffix).
- *
- * @return the completion timestamp, or {@code null} if not set
- */
- public OffsetDateTime getFinishedOn() {
- return finishedOn;
+ @Override
+ public int hashCode() {
+ return Objects.hash(invocationId, startedOn, finishedOn);
}
/**
@@ -116,18 +110,22 @@ public void setFinishedOn(OffsetDateTime finishedOn) {
this.finishedOn = finishedOn;
}
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof BuildMetadata)) {
- return false;
- }
- BuildMetadata that = (BuildMetadata) o;
- return Objects.equals(invocationId, that.invocationId) && Objects.equals(startedOn, that.startedOn) && Objects.equals(finishedOn, that.finishedOn);
+ /**
+ * Sets the identifier for this build invocation.
+ *
+ * @param invocationId the invocation identifier
+ */
+ public void setInvocationId(String invocationId) {
+ this.invocationId = invocationId;
}
- @Override
- public int hashCode() {
- return Objects.hash(invocationId, startedOn, finishedOn);
+ /**
+ * Sets the timestamp of when the build started.
+ *
+ * @param startedOn the start timestamp
+ */
+ public void setStartedOn(OffsetDateTime startedOn) {
+ this.startedOn = startedOn;
}
@Override
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Builder.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Builder.java
index 635e75cfb..31102295a 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Builder.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Builder.java
@@ -31,14 +31,12 @@
*/
public class Builder {
- /** Identifier URI of the builder. */
- @JsonProperty("id")
- private String id = "https://commons.apache.org/builds/0.1.0";
-
/** Orchestrator dependencies that may affect provenance generation. */
@JsonProperty("builderDependencies")
private List builderDependencies = new ArrayList<>();
-
+ /** Identifier URI of the builder. */
+ @JsonProperty("id")
+ private String id = "https://commons.apache.org/builds/0.1.0";
/** Map of build platform component names to their versions. */
@JsonProperty("version")
private Map version = new HashMap<>();
@@ -47,6 +45,27 @@ public class Builder {
public Builder() {
}
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Builder)) {
+ return false;
+ }
+ Builder that = (Builder) o;
+ return Objects.equals(id, that.id)
+ && Objects.equals(builderDependencies, that.builderDependencies)
+ && Objects.equals(version, that.version);
+ }
+
+ /**
+ * Gets orchestrator dependencies that do not run within the build workload and do not affect the build output,
+ * but may affect provenance generation or security guarantees.
+ *
+ * @return the list of builder dependencies, or {@code null} if not set
+ */
+ public List getBuilderDependencies() {
+ return builderDependencies;
+ }
+
/**
* Gets the identifier of the builder.
*
@@ -57,22 +76,17 @@ public String getId() {
}
/**
- * Sets the identifier of the builder.
+ * Gets a map of build platform component names to their versions.
*
- * @param id the builder identifier URI
+ * @return the version map, or {@code null} if not set
*/
- public void setId(String id) {
- this.id = id;
+ public Map getVersion() {
+ return version;
}
- /**
- * Gets orchestrator dependencies that do not run within the build workload and do not affect the build output,
- * but may affect provenance generation or security guarantees.
- *
- * @return the list of builder dependencies, or {@code null} if not set
- */
- public List getBuilderDependencies() {
- return builderDependencies;
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, builderDependencies, version);
}
/**
@@ -85,12 +99,12 @@ public void setBuilderDependencies(List builderDependencies)
}
/**
- * Gets a map of build platform component names to their versions.
+ * Sets the identifier of the builder.
*
- * @return the version map, or {@code null} if not set
+ * @param id the builder identifier URI
*/
- public Map getVersion() {
- return version;
+ public void setId(String id) {
+ this.id = id;
}
/**
@@ -102,22 +116,6 @@ public void setVersion(Map version) {
this.version = version;
}
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof Builder)) {
- return false;
- }
- Builder that = (Builder) o;
- return Objects.equals(id, that.id)
- && Objects.equals(builderDependencies, that.builderDependencies)
- && Objects.equals(version, that.version);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(id, builderDependencies, version);
- }
-
@Override
public String toString() {
return "Builder{id='" + id + "', builderDependencies=" + builderDependencies + ", version=" + version + '}';
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/DsseEnvelope.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/DsseEnvelope.java
index fdb2353f3..2c9bc601a 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/DsseEnvelope.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/DsseEnvelope.java
@@ -31,15 +31,12 @@ public class DsseEnvelope {
/** The payload type URI for in-toto attestation statements. */
public static final String PAYLOAD_TYPE = "application/vnd.in-toto+json";
-
- /** Content type identifying the format of {@link #payload}. */
- @JsonProperty("payloadType")
- private String payloadType = PAYLOAD_TYPE;
-
/** Serialized statement bytes, Base64-encoded in JSON. */
@JsonProperty("payload")
private byte[] payload;
-
+ /** Content type identifying the format of {@link #payload}. */
+ @JsonProperty("payloadType")
+ private String payloadType = PAYLOAD_TYPE;
/** One or more signatures over the PAE-encoded payload. */
@JsonProperty("signatures")
private List signatures;
@@ -48,6 +45,27 @@ public class DsseEnvelope {
public DsseEnvelope() {
}
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof DsseEnvelope)) {
+ return false;
+ }
+ DsseEnvelope envelope = (DsseEnvelope) o;
+ return Objects.equals(payloadType, envelope.payloadType) && Arrays.equals(payload, envelope.payload)
+ && Objects.equals(signatures, envelope.signatures);
+ }
+
+ /**
+ * Gets the serialized payload bytes.
+ *
+ * When serialized to JSON the bytes are Base64-encoded.
+ *
+ * @return the payload bytes, or {@code null} if not set
+ */
+ public byte[] getPayload() {
+ return payload;
+ }
+
/**
* Gets the payload type URI.
*
@@ -58,23 +76,17 @@ public String getPayloadType() {
}
/**
- * Sets the payload type URI.
+ * Gets the list of signatures over the PAE-encoded payload.
*
- * @param payloadType the payload type URI
+ * @return the signatures, or {@code null} if not set
*/
- public void setPayloadType(String payloadType) {
- this.payloadType = payloadType;
+ public List getSignatures() {
+ return signatures;
}
- /**
- * Gets the serialized payload bytes.
- *
- * When serialized to JSON the bytes are Base64-encoded.
- *
- * @return the payload bytes, or {@code null} if not set
- */
- public byte[] getPayload() {
- return payload;
+ @Override
+ public int hashCode() {
+ return Objects.hash(payloadType, Arrays.hashCode(payload), signatures);
}
/**
@@ -87,12 +99,12 @@ public void setPayload(byte[] payload) {
}
/**
- * Gets the list of signatures over the PAE-encoded payload.
+ * Sets the payload type URI.
*
- * @return the signatures, or {@code null} if not set
+ * @param payloadType the payload type URI
*/
- public List getSignatures() {
- return signatures;
+ public void setPayloadType(String payloadType) {
+ this.payloadType = payloadType;
}
/**
@@ -104,21 +116,6 @@ public void setSignatures(List signatures) {
this.signatures = signatures;
}
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof DsseEnvelope)) {
- return false;
- }
- DsseEnvelope envelope = (DsseEnvelope) o;
- return Objects.equals(payloadType, envelope.payloadType) && Arrays.equals(payload, envelope.payload)
- && Objects.equals(signatures, envelope.signatures);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(payloadType, Arrays.hashCode(payload), signatures);
- }
-
@Override
public String toString() {
return "DsseEnvelope{payloadType='" + payloadType + "', payload=<" + (payload != null ? payload.length : 0)
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Provenance.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Provenance.java
index c9dfd2e28..1b7361e7d 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Provenance.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Provenance.java
@@ -58,6 +58,18 @@ public Provenance(BuildDefinition buildDefinition, RunDetails runDetails) {
this.runDetails = runDetails;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Provenance that = (Provenance) o;
+ return Objects.equals(buildDefinition, that.buildDefinition) && Objects.equals(runDetails, that.runDetails);
+ }
+
/**
* Gets the build definition describing all inputs that produced the build output.
*
@@ -70,21 +82,26 @@ public BuildDefinition getBuildDefinition() {
}
/**
- * Sets the build definition describing all inputs that produced the build output.
+ * Gets the details about the invocation of the build tool and the environment in which it was run.
*
- * @param buildDefinition the build definition
+ * @return the run details, or {@code null} if not set
*/
- public void setBuildDefinition(BuildDefinition buildDefinition) {
- this.buildDefinition = buildDefinition;
+ public RunDetails getRunDetails() {
+ return runDetails;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(buildDefinition, runDetails);
}
/**
- * Gets the details about the invocation of the build tool and the environment in which it was run.
+ * Sets the build definition describing all inputs that produced the build output.
*
- * @return the run details, or {@code null} if not set
+ * @param buildDefinition the build definition
*/
- public RunDetails getRunDetails() {
- return runDetails;
+ public void setBuildDefinition(BuildDefinition buildDefinition) {
+ this.buildDefinition = buildDefinition;
}
/**
@@ -96,23 +113,6 @@ public void setRunDetails(RunDetails runDetails) {
this.runDetails = runDetails;
}
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- Provenance that = (Provenance) o;
- return Objects.equals(buildDefinition, that.buildDefinition) && Objects.equals(runDetails, that.runDetails);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(buildDefinition, runDetails);
- }
-
@Override
public String toString() {
return "Provenance{buildDefinition=" + buildDefinition + ", runDetails=" + runDetails + '}';
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/ResourceDescriptor.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/ResourceDescriptor.java
index 2ce42ce25..79210d33d 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/ResourceDescriptor.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/ResourceDescriptor.java
@@ -32,33 +32,27 @@
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResourceDescriptor {
- /** Human-readable name of the resource. */
- @JsonProperty("name")
- private String name;
-
- /** URI identifying the resource. */
- @JsonProperty("uri")
- private String uri;
-
- /** Map of digest algorithm names to hex-encoded values. */
- @JsonProperty("digest")
- private Map digest;
-
+ /** Additional key-value metadata about the resource. */
+ @JsonProperty("annotations")
+ private Map annotations;
/** Raw contents of the resource, base64-encoded in JSON. */
@JsonProperty("content")
private byte[] content;
-
+ /** Map of digest algorithm names to hex-encoded values. */
+ @JsonProperty("digest")
+ private Map digest;
/** Download URI for the resource, if different from {@link #uri}. */
@JsonProperty("downloadLocation")
private String downloadLocation;
-
/** Media type of the resource. */
@JsonProperty("mediaType")
private String mediaType;
-
- /** Additional key-value metadata about the resource. */
- @JsonProperty("annotations")
- private Map annotations;
+ /** Human-readable name of the resource. */
+ @JsonProperty("name")
+ private String name;
+ /** URI identifying the resource. */
+ @JsonProperty("uri")
+ private String uri;
/** Creates a new ResourceDescriptor instance. */
public ResourceDescriptor() {
@@ -75,69 +69,95 @@ public ResourceDescriptor(String uri, Map digest) {
this.digest = digest;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ResourceDescriptor that = (ResourceDescriptor) o;
+ return Objects.equals(uri, that.uri) && Objects.equals(digest, that.digest);
+ }
+
/**
- * Gets the name of the resource.
+ * Gets additional key-value metadata about the resource, such as filename, size, or builder-specific attributes.
*
- * @return the resource name, or {@code null} if not set
+ * @return the annotations map, or {@code null} if not set
*/
- public String getName() {
- return name;
+ public Map getAnnotations() {
+ return annotations;
}
/**
- * Sets the name of the resource.
+ * Gets the raw contents of the resource, base64-encoded when serialized to JSON.
*
- * @param name the resource name
+ * @return the resource content, or {@code null} if not set
*/
- public void setName(String name) {
- this.name = name;
+ public byte[] getContent() {
+ return content;
}
/**
- * Gets the URI identifying the resource.
+ * Gets the map of cryptographic digest algorithms to their corresponding hex-encoded values for this resource.
*
- * @return the resource URI, or {@code null} if not set
+ * Common keys include {@code "sha256"} and {@code "sha512"}.
+ *
+ * @return the digest map, or {@code null} if not set
*/
- public String getUri() {
- return uri;
+ public Map getDigest() {
+ return digest;
}
/**
- * Sets the URI identifying the resource.
+ * Gets the download URI for the resource, if different from {@link #getUri()}.
*
- * @param uri the resource URI
+ * @return the download location URI, or {@code null} if not set
*/
- public void setUri(String uri) {
- this.uri = uri;
+ public String getDownloadLocation() {
+ return downloadLocation;
}
/**
- * Gets the map of cryptographic digest algorithms to their corresponding hex-encoded values for this resource.
+ * Gets the media type of the resource (e.g., {@code "application/octet-stream"}).
*
- * Common keys include {@code "sha256"} and {@code "sha512"}.
+ * @return the media type, or {@code null} if not set
+ */
+ public String getMediaType() {
+ return mediaType;
+ }
+
+ /**
+ * Gets the name of the resource.
*
- * @return the digest map, or {@code null} if not set
+ * @return the resource name, or {@code null} if not set
*/
- public Map getDigest() {
- return digest;
+ public String getName() {
+ return name;
}
/**
- * Sets the map of cryptographic digest algorithms to their hex-encoded values.
+ * Gets the URI identifying the resource.
*
- * @param digest the digest map
+ * @return the resource URI, or {@code null} if not set
*/
- public void setDigest(Map digest) {
- this.digest = digest;
+ public String getUri() {
+ return uri;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(uri, digest);
}
/**
- * Gets the raw contents of the resource, base64-encoded when serialized to JSON.
+ * Sets additional key-value metadata about the resource.
*
- * @return the resource content, or {@code null} if not set
+ * @param annotations the annotations map
*/
- public byte[] getContent() {
- return content;
+ public void setAnnotations(Map annotations) {
+ this.annotations = annotations;
}
/**
@@ -150,12 +170,12 @@ public void setContent(byte[] content) {
}
/**
- * Gets the download URI for the resource, if different from {@link #getUri()}.
+ * Sets the map of cryptographic digest algorithms to their hex-encoded values.
*
- * @return the download location URI, or {@code null} if not set
+ * @param digest the digest map
*/
- public String getDownloadLocation() {
- return downloadLocation;
+ public void setDigest(Map digest) {
+ this.digest = digest;
}
/**
@@ -167,15 +187,6 @@ public void setDownloadLocation(String downloadLocation) {
this.downloadLocation = downloadLocation;
}
- /**
- * Gets the media type of the resource (e.g., {@code "application/octet-stream"}).
- *
- * @return the media type, or {@code null} if not set
- */
- public String getMediaType() {
- return mediaType;
- }
-
/**
* Sets the media type of the resource.
*
@@ -186,38 +197,21 @@ public void setMediaType(String mediaType) {
}
/**
- * Gets additional key-value metadata about the resource, such as filename, size, or builder-specific attributes.
+ * Sets the name of the resource.
*
- * @return the annotations map, or {@code null} if not set
+ * @param name the resource name
*/
- public Map getAnnotations() {
- return annotations;
+ public void setName(String name) {
+ this.name = name;
}
/**
- * Sets additional key-value metadata about the resource.
+ * Sets the URI identifying the resource.
*
- * @param annotations the annotations map
+ * @param uri the resource URI
*/
- public void setAnnotations(Map annotations) {
- this.annotations = annotations;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- ResourceDescriptor that = (ResourceDescriptor) o;
- return Objects.equals(uri, that.uri) && Objects.equals(digest, that.digest);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(uri, digest);
+ public void setUri(String uri) {
+ this.uri = uri;
}
@Override
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/RunDetails.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/RunDetails.java
index 85d8f6b0b..90aae318c 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/RunDetails.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/RunDetails.java
@@ -35,18 +35,16 @@ public class RunDetails {
*/
@JsonProperty("builder")
private Builder builder;
-
- /**
- * Metadata about the build invocation.
- */
- @JsonProperty("metadata")
- private BuildMetadata metadata;
-
/**
* Artifacts produced as a side effect of the build.
*/
@JsonProperty("byproducts")
private List byproducts;
+ /**
+ * Metadata about the build invocation.
+ */
+ @JsonProperty("metadata")
+ private BuildMetadata metadata;
/**
* Creates a new RunDetails instance.
@@ -65,6 +63,18 @@ public RunDetails(Builder builder, BuildMetadata metadata) {
this.metadata = metadata;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ RunDetails that = (RunDetails) o;
+ return Objects.equals(builder, that.builder) && Objects.equals(metadata, that.metadata) && Objects.equals(byproducts, that.byproducts);
+ }
+
/**
* Gets the builder that executed the invocation.
*
@@ -77,12 +87,12 @@ public Builder getBuilder() {
}
/**
- * Sets the builder that executed the invocation.
+ * Gets artifacts produced as a side effect of the build that are not the primary output.
*
- * @param builder the builder
+ * @return the list of byproduct artifacts, or {@code null} if not set
*/
- public void setBuilder(Builder builder) {
- this.builder = builder;
+ public List getByproducts() {
+ return byproducts;
}
/**
@@ -94,22 +104,18 @@ public BuildMetadata getMetadata() {
return metadata;
}
- /**
- * Sets the metadata about the build invocation.
- *
- * @param metadata the build metadata
- */
- public void setMetadata(BuildMetadata metadata) {
- this.metadata = metadata;
+ @Override
+ public int hashCode() {
+ return Objects.hash(builder, metadata, byproducts);
}
/**
- * Gets artifacts produced as a side effect of the build that are not the primary output.
+ * Sets the builder that executed the invocation.
*
- * @return the list of byproduct artifacts, or {@code null} if not set
+ * @param builder the builder
*/
- public List getByproducts() {
- return byproducts;
+ public void setBuilder(Builder builder) {
+ this.builder = builder;
}
/**
@@ -121,21 +127,13 @@ public void setByproducts(List byproducts) {
this.byproducts = byproducts;
}
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- RunDetails that = (RunDetails) o;
- return Objects.equals(builder, that.builder) && Objects.equals(metadata, that.metadata) && Objects.equals(byproducts, that.byproducts);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(builder, metadata, byproducts);
+ /**
+ * Sets the metadata about the build invocation.
+ *
+ * @param metadata the build metadata
+ */
+ public void setMetadata(BuildMetadata metadata) {
+ this.metadata = metadata;
}
@Override
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Signature.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Signature.java
index c2caf8000..d514608e4 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Signature.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Signature.java
@@ -46,6 +46,15 @@ public class Signature {
public Signature() {
}
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Signature)) {
+ return false;
+ }
+ Signature signature = (Signature) o;
+ return Objects.equals(keyid, signature.keyid) && Arrays.equals(sig, signature.sig);
+ }
+
/**
* Gets the key identifier hint, or {@code null} if not set.
*
@@ -56,21 +65,26 @@ public String getKeyid() {
}
/**
- * Sets the key identifier hint.
+ * Gets the raw signature bytes.
*
- * @param keyid the key identifier, or {@code null} to leave unset
+ * @return the signature bytes, or {@code null} if not set
*/
- public void setKeyid(String keyid) {
- this.keyid = keyid;
+ public byte[] getSig() {
+ return sig;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(keyid, Arrays.hashCode(sig));
}
/**
- * Gets the raw signature bytes.
+ * Sets the key identifier hint.
*
- * @return the signature bytes, or {@code null} if not set
+ * @param keyid the key identifier, or {@code null} to leave unset
*/
- public byte[] getSig() {
- return sig;
+ public void setKeyid(String keyid) {
+ this.keyid = keyid;
}
/**
@@ -82,20 +96,6 @@ public void setSig(byte[] sig) {
this.sig = sig;
}
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof Signature)) {
- return false;
- }
- Signature signature = (Signature) o;
- return Objects.equals(keyid, signature.keyid) && Arrays.equals(sig, signature.sig);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(keyid, Arrays.hashCode(sig));
- }
-
@Override
public String toString() {
return "Signature{keyid='" + keyid + "', sig=<" + (sig != null ? sig.length : 0) + " bytes>}";
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Statement.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Statement.java
index 4f3506e0b..eefb90dc7 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Statement.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Statement.java
@@ -31,41 +31,40 @@ public class Statement {
/** The in-toto statement schema URI. */
@JsonProperty("_type")
public static final String TYPE = "https://in-toto.io/Statement/v1";
-
- /** Software artifacts that the attestation applies to. */
- @JsonProperty("subject")
- private List subject;
-
- /** URI identifying the type of the predicate. */
- @JsonProperty("predicateType")
- private String predicateType;
-
/** The provenance predicate. */
@JsonProperty("predicate")
private Provenance predicate;
+ /** URI identifying the type of the predicate. */
+ @JsonProperty("predicateType")
+ private String predicateType;
+ /** Software artifacts that the attestation applies to. */
+ @JsonProperty("subject")
+ private List subject;
/** Creates a new Statement instance. */
public Statement() {
}
- /**
- * Gets the set of software artifacts that the attestation applies to.
- *
- * Each element represents a single artifact. Artifacts are matched purely by digest, regardless of content type.
- *
- * @return the list of subject artifacts, or {@code null} if not set
- */
- public List getSubject() {
- return subject;
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Statement)) {
+ return false;
+ }
+ Statement statement = (Statement) o;
+ return Objects.equals(subject, statement.subject) && Objects.equals(predicateType, statement.predicateType) && Objects.equals(predicate,
+ statement.predicate);
}
/**
- * Sets the set of software artifacts that the attestation applies to.
+ * Gets the provenance predicate.
*
- * @param subject the list of subject artifacts
+ * Unset is treated the same as set-but-empty. May be omitted if {@code predicateType} fully describes the
+ * predicate.
+ *
+ * @return the provenance predicate, or {@code null} if not set
*/
- public void setSubject(List subject) {
- this.subject = subject;
+ public Provenance getPredicate() {
+ return predicate;
}
/**
@@ -78,15 +77,19 @@ public String getPredicateType() {
}
/**
- * Gets the provenance predicate.
+ * Gets the set of software artifacts that the attestation applies to.
*
- * Unset is treated the same as set-but-empty. May be omitted if {@code predicateType} fully describes the
- * predicate.
+ * Each element represents a single artifact. Artifacts are matched purely by digest, regardless of content type.
*
- * @return the provenance predicate, or {@code null} if not set
+ * @return the list of subject artifacts, or {@code null} if not set
*/
- public Provenance getPredicate() {
- return predicate;
+ public List getSubject() {
+ return subject;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(subject, predicateType, predicate);
}
/**
@@ -99,19 +102,13 @@ public void setPredicate(Provenance predicate) {
this.predicateType = Provenance.PREDICATE_TYPE;
}
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof Statement)) {
- return false;
- }
- Statement statement = (Statement) o;
- return Objects.equals(subject, statement.subject) && Objects.equals(predicateType, statement.predicateType) && Objects.equals(predicate,
- statement.predicate);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(subject, predicateType, predicate);
+ /**
+ * Sets the set of software artifacts that the attestation applies to.
+ *
+ * @param subject the list of subject artifacts
+ */
+ public void setSubject(List subject) {
+ this.subject = subject;
}
@Override
diff --git a/src/test/java/org/apache/commons/release/plugin/internal/BuildDefinitionsTest.java b/src/test/java/org/apache/commons/release/plugin/internal/BuildDefinitionsTest.java
index fe58a540e..6c7a4d7a0 100644
--- a/src/test/java/org/apache/commons/release/plugin/internal/BuildDefinitionsTest.java
+++ b/src/test/java/org/apache/commons/release/plugin/internal/BuildDefinitionsTest.java
@@ -46,6 +46,12 @@ static Stream commandLineArguments() {
);
}
+ private static Properties singletonProperties(final String key, final String value) {
+ final Properties p = new Properties();
+ p.setProperty(key, value);
+ return p;
+ }
+
@ParameterizedTest(name = "{0}")
@MethodSource("commandLineArguments")
void commandLineTest(final String description, final List goals, final List profiles,
@@ -56,10 +62,4 @@ void commandLineTest(final String description, final List goals, final L
request.setUserProperties(userProperties);
assertEquals(expected, BuildDefinitions.commandLine(request));
}
-
- private static Properties singletonProperties(final String key, final String value) {
- final Properties p = new Properties();
- p.setProperty(key, value);
- return p;
- }
}
diff --git a/src/test/java/org/apache/commons/release/plugin/internal/MojoUtils.java b/src/test/java/org/apache/commons/release/plugin/internal/MojoUtils.java
index 3cdef92fc..f751b114b 100644
--- a/src/test/java/org/apache/commons/release/plugin/internal/MojoUtils.java
+++ b/src/test/java/org/apache/commons/release/plugin/internal/MojoUtils.java
@@ -39,20 +39,6 @@
*/
public final class MojoUtils {
- private static ContainerConfiguration setupContainerConfiguration() {
- final ClassWorld classWorld =
- new ClassWorld("plexus.core", Thread.currentThread().getContextClassLoader());
- return new DefaultContainerConfiguration()
- .setClassWorld(classWorld)
- .setClassPathScanning(PlexusConstants.SCANNING_INDEX)
- .setAutoWiring(true)
- .setName("maven");
- }
-
- public static PlexusContainer setupContainer() throws PlexusContainerException {
- return new DefaultPlexusContainer(setupContainerConfiguration());
- }
-
public static RepositorySystemSession createRepositorySystemSession(
final PlexusContainer container, final Path localRepositoryPath) throws ComponentLookupException, RepositoryException {
final LocalRepositoryManagerFactory factory = container.lookup(LocalRepositoryManagerFactory.class, "simple");
@@ -66,6 +52,20 @@ public static RepositorySystemSession createRepositorySystemSession(
return repoSession;
}
+ public static PlexusContainer setupContainer() throws PlexusContainerException {
+ return new DefaultPlexusContainer(setupContainerConfiguration());
+ }
+
+ private static ContainerConfiguration setupContainerConfiguration() {
+ final ClassWorld classWorld =
+ new ClassWorld("plexus.core", Thread.currentThread().getContextClassLoader());
+ return new DefaultContainerConfiguration()
+ .setClassWorld(classWorld)
+ .setClassPathScanning(PlexusConstants.SCANNING_INDEX)
+ .setAutoWiring(true)
+ .setName("maven");
+ }
+
private MojoUtils() {
}
}
diff --git a/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java b/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
index 309b98fe9..89df57072 100644
--- a/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
+++ b/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
@@ -72,37 +72,47 @@ public class BuildAttestationMojoTest {
private static final String ARTIFACTS_DIR = "src/test/resources/mojos/detach-distributions/target/";
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
-
+ private static PlexusContainer container;
+ private static JsonNode expectedStatement;
@TempDir
private static Path localRepositoryPath;
-
- private static PlexusContainer container;
private static RepositorySystemSession repoSession;
- private static JsonNode expectedStatement;
- @BeforeAll
- static void setup() throws Exception {
- container = MojoUtils.setupContainer();
- repoSession = MojoUtils.createRepositorySystemSession(container, localRepositoryPath);
- try (InputStream in = BuildAttestationMojoTest.class.getResourceAsStream("/attestations/commons-text-1.4.intoto.json")) {
- expectedStatement = OBJECT_MAPPER.readTree(in);
- }
- }
-
- private static MavenExecutionRequest createMavenExecutionRequest() {
- final DefaultMavenExecutionRequest request = new DefaultMavenExecutionRequest();
- request.setStartTime(Date.from(Instant.parse("2026-04-20T09:28:44Z")));
- request.setActiveProfiles(Collections.singletonList("release"));
- request.setGoals(Collections.singletonList("deploy"));
- final Properties userProperties = new Properties();
- userProperties.setProperty("gpg.keyname", "3C8D57E0A2B5C6D7E8F9A0B1C2D3E4F5A6B7C8D9");
- request.setUserProperties(userProperties);
- return request;
+ private static void assertStatementContent(final JsonNode statement) {
+ assertJsonEquals(expectedStatement.get("subject"), statement.get("subject"),
+ JsonAssert.when(Option.IGNORING_ARRAY_ORDER));
+ assertJsonEquals(expectedStatement.get("predicateType"), statement.get("predicateType"));
+ assertJsonEquals(expectedStatement.at("/predicate/buildDefinition/buildType"),
+ statement.at("/predicate/buildDefinition/buildType"));
+ assertJsonEquals(expectedStatement.at("/predicate/buildDefinition/externalParameters"),
+ statement.at("/predicate/buildDefinition/externalParameters"),
+ JsonAssert.when(Option.IGNORING_VALUES).whenIgnoringPaths("jvm.args", "env"));
+ assertJsonEquals(expectedStatement.at("/predicate/buildDefinition/internalParameters"),
+ statement.at("/predicate/buildDefinition/internalParameters"));
+ // `[0].annotations` holds JVM system properties;
+ // Not all properties are available on all JDKs, so they are either null or strings, which json-unit treats as a structural mismatch.
+ // We will check them below
+ assertJsonEquals(expectedStatement.at("/predicate/buildDefinition/resolvedDependencies"),
+ statement.at("/predicate/buildDefinition/resolvedDependencies"),
+ JsonAssert.when(Option.IGNORING_VALUES).whenIgnoringPaths("[0].annotations"));
+ final Set expectedJdkFields = fieldNames(
+ expectedStatement.at("/predicate/buildDefinition/resolvedDependencies/0/annotations"));
+ final Set actualJdkFields = fieldNames(
+ statement.at("/predicate/buildDefinition/resolvedDependencies/0/annotations"));
+ assertEquals(expectedJdkFields, actualJdkFields);
+ assertJsonEquals(expectedStatement.at("/predicate/runDetails"),
+ statement.at("/predicate/runDetails"),
+ whenIgnoringPaths("metadata.finishedOn"));
}
- @SuppressWarnings("deprecation")
- private static MavenSession createMavenSession(final MavenExecutionRequest request, final MavenExecutionResult result) {
- return new MavenSession(container, repoSession, request, result);
+ private static void configureBuildAttestationMojo(final BuildAttestationMojo mojo, final boolean signAttestation) {
+ mojo.setOutputDirectory(new File("target/attestations"));
+ mojo.setScmDirectory(new File("."));
+ mojo.setScmConnectionUrl("scm:git:https://github.com/apache/commons-text.git");
+ mojo.setMavenHome(new File(System.getProperty("maven.home", ".")));
+ mojo.setAlgorithmNames("SHA-512,SHA-256,SHA-1,MD5");
+ mojo.setSignAttestation(signAttestation);
+ mojo.setSigner(createMockSigner());
}
private static BuildAttestationMojo createBuildAttestationMojo(final MavenProject project, final MavenProjectHelper projectHelper)
@@ -113,14 +123,15 @@ private static BuildAttestationMojo createBuildAttestationMojo(final MavenProjec
createMavenSession(createMavenExecutionRequest(), new DefaultMavenExecutionResult()), projectHelper);
}
- private static void configureBuildAttestationMojo(final BuildAttestationMojo mojo, final boolean signAttestation) {
- mojo.setOutputDirectory(new File("target/attestations"));
- mojo.setScmDirectory(new File("."));
- mojo.setScmConnectionUrl("scm:git:https://github.com/apache/commons-text.git");
- mojo.setMavenHome(new File(System.getProperty("maven.home", ".")));
- mojo.setAlgorithmNames("SHA-512,SHA-256,SHA-1,MD5");
- mojo.setSignAttestation(signAttestation);
- mojo.setSigner(createMockSigner());
+ private static MavenExecutionRequest createMavenExecutionRequest() {
+ final DefaultMavenExecutionRequest request = new DefaultMavenExecutionRequest();
+ request.setStartTime(Date.from(Instant.parse("2026-04-20T09:28:44Z")));
+ request.setActiveProfiles(Collections.singletonList("release"));
+ request.setGoals(Collections.singletonList("deploy"));
+ final Properties userProperties = new Properties();
+ userProperties.setProperty("gpg.keyname", "3C8D57E0A2B5C6D7E8F9A0B1C2D3E4F5A6B7C8D9");
+ request.setUserProperties(userProperties);
+ return request;
}
private static MavenProject createMavenProject(final MavenProjectHelper projectHelper, final MavenRepositorySystem repoSystem) throws Exception {
@@ -147,11 +158,20 @@ private static MavenProject createMavenProject(final MavenProjectHelper projectH
return project;
}
+ @SuppressWarnings("deprecation")
+ private static MavenSession createMavenSession(final MavenExecutionRequest request, final MavenExecutionResult result) {
+ return new MavenSession(container, repoSession, request, result);
+ }
+
private static AbstractGpgSigner createMockSigner() {
return new AbstractGpgSigner() {
@Override
- public String signerName() {
- return "mock";
+ protected void generateSignatureForFile(final File file, final File signature) throws MojoExecutionException {
+ try {
+ Files.copy(Paths.get(ARTIFACTS_DIR + "commons-text-1.4.jar.asc"), signature.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ } catch (final IOException e) {
+ throw new MojoExecutionException("Failed to copy mock signature", e);
+ }
}
@Override
@@ -160,16 +180,21 @@ public String getKeyInfo() {
}
@Override
- protected void generateSignatureForFile(final File file, final File signature) throws MojoExecutionException {
- try {
- Files.copy(Paths.get(ARTIFACTS_DIR + "commons-text-1.4.jar.asc"), signature.toPath(), StandardCopyOption.REPLACE_EXISTING);
- } catch (final IOException e) {
- throw new MojoExecutionException("Failed to copy mock signature", e);
- }
+ public String signerName() {
+ return "mock";
}
};
}
+ private static Set fieldNames(final JsonNode node) {
+ final Set names = new TreeSet<>();
+ final Iterator it = node.fieldNames();
+ while (it.hasNext()) {
+ names.add(it.next());
+ }
+ return names;
+ }
+
private static Artifact getAttestation(final MavenProject project) {
return project.getAttachedArtifacts().stream()
.filter(a -> "intoto.jsonl".equals(a.getType()))
@@ -177,40 +202,13 @@ private static Artifact getAttestation(final MavenProject project) {
.orElseThrow(() -> new AssertionError("No intoto.jsonl artifact attached to project"));
}
- private static void assertStatementContent(final JsonNode statement) {
- assertJsonEquals(expectedStatement.get("subject"), statement.get("subject"),
- JsonAssert.when(Option.IGNORING_ARRAY_ORDER));
- assertJsonEquals(expectedStatement.get("predicateType"), statement.get("predicateType"));
- assertJsonEquals(expectedStatement.at("/predicate/buildDefinition/buildType"),
- statement.at("/predicate/buildDefinition/buildType"));
- assertJsonEquals(expectedStatement.at("/predicate/buildDefinition/externalParameters"),
- statement.at("/predicate/buildDefinition/externalParameters"),
- JsonAssert.when(Option.IGNORING_VALUES).whenIgnoringPaths("jvm.args", "env"));
- assertJsonEquals(expectedStatement.at("/predicate/buildDefinition/internalParameters"),
- statement.at("/predicate/buildDefinition/internalParameters"));
- // `[0].annotations` holds JVM system properties;
- // Not all properties are available on all JDKs, so they are either null or strings, which json-unit treats as a structural mismatch.
- // We will check them below
- assertJsonEquals(expectedStatement.at("/predicate/buildDefinition/resolvedDependencies"),
- statement.at("/predicate/buildDefinition/resolvedDependencies"),
- JsonAssert.when(Option.IGNORING_VALUES).whenIgnoringPaths("[0].annotations"));
- final Set expectedJdkFields = fieldNames(
- expectedStatement.at("/predicate/buildDefinition/resolvedDependencies/0/annotations"));
- final Set actualJdkFields = fieldNames(
- statement.at("/predicate/buildDefinition/resolvedDependencies/0/annotations"));
- assertEquals(expectedJdkFields, actualJdkFields);
- assertJsonEquals(expectedStatement.at("/predicate/runDetails"),
- statement.at("/predicate/runDetails"),
- whenIgnoringPaths("metadata.finishedOn"));
- }
-
- private static Set fieldNames(final JsonNode node) {
- final Set names = new TreeSet<>();
- final Iterator it = node.fieldNames();
- while (it.hasNext()) {
- names.add(it.next());
+ @BeforeAll
+ static void setup() throws Exception {
+ container = MojoUtils.setupContainer();
+ repoSession = MojoUtils.createRepositorySystemSession(container, localRepositoryPath);
+ try (InputStream in = BuildAttestationMojoTest.class.getResourceAsStream("/attestations/commons-text-1.4.intoto.json")) {
+ expectedStatement = OBJECT_MAPPER.readTree(in);
}
- return names;
}
@Test
From b457710c7689987b165b58dd3a100af7e4a05186 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Mon, 20 Apr 2026 14:33:30 +0200
Subject: [PATCH 27/51] fix: allow for setter chaining
---
.../plugin/internal/ArtifactUtils.java | 6 +-
.../plugin/internal/BuildDefinitions.java | 22 +-
.../plugin/mojos/BuildAttestationMojo.java | 56 ++--
.../plugin/slsa/v1_2/BuildDefinition.java | 284 +++++++++---------
.../plugin/slsa/v1_2/BuildMetadata.java | 12 +-
.../release/plugin/slsa/v1_2/Builder.java | 12 +-
.../plugin/slsa/v1_2/DsseEnvelope.java | 12 +-
.../release/plugin/slsa/v1_2/Provenance.java | 8 +-
.../plugin/slsa/v1_2/ResourceDescriptor.java | 28 +-
.../release/plugin/slsa/v1_2/RunDetails.java | 12 +-
.../release/plugin/slsa/v1_2/Signature.java | 8 +-
.../release/plugin/slsa/v1_2/Statement.java | 8 +-
12 files changed, 257 insertions(+), 211 deletions(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java b/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java
index 17a3e8728..20cee902d 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/ArtifactUtils.java
@@ -138,9 +138,9 @@ public static String getPackageUrl(final Artifact artifact) {
* @throws MojoExecutionException If an I/O error occurs retrieving the artifact.
*/
public static ResourceDescriptor toResourceDescriptor(final Artifact artifact, final String algorithms) throws MojoExecutionException {
- final ResourceDescriptor descriptor = new ResourceDescriptor();
- descriptor.setName(getFileName(artifact));
- descriptor.setUri(getPackageUrl(artifact));
+ final ResourceDescriptor descriptor = new ResourceDescriptor()
+ .setName(getFileName(artifact))
+ .setUri(getPackageUrl(artifact));
if (artifact.getFile() != null) {
try {
descriptor.setDigest(getChecksums(artifact, StringUtils.split(algorithms, ",")));
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java b/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
index 996e57ffe..f74549a63 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
@@ -21,6 +21,7 @@
import java.lang.management.ManagementFactory;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -84,11 +85,6 @@ public static Map externalParameters(final MavenSession session)
* @throws IOException if hashing the JDK directory fails
*/
public static ResourceDescriptor jvm(final Path javaHome) throws IOException {
- final ResourceDescriptor descriptor = new ResourceDescriptor();
- descriptor.setName("JDK");
- final Map digest = new HashMap<>();
- digest.put("gitTree", GitUtils.gitTree(javaHome));
- descriptor.setDigest(digest);
final String[] propertyNames = {
"java.version", "java.version.date",
"java.vendor", "java.vendor.url", "java.vendor.version",
@@ -102,8 +98,10 @@ public static ResourceDescriptor jvm(final Path javaHome) throws IOException {
for (final String prop : propertyNames) {
annotations.put(prop.substring("java.".length()), System.getProperty(prop));
}
- descriptor.setAnnotations(annotations);
- return descriptor;
+ return new ResourceDescriptor()
+ .setName("JDK")
+ .setDigest(Collections.singletonMap("gitTree", GitUtils.gitTree(javaHome)))
+ .setAnnotations(annotations);
}
/**
@@ -120,12 +118,10 @@ public static ResourceDescriptor jvm(final Path javaHome) throws IOException {
* @throws IOException if hashing the Maven home directory fails
*/
public static ResourceDescriptor maven(final String version, final Path mavenHome, final ClassLoader coreClassLoader) throws IOException {
- final ResourceDescriptor descriptor = new ResourceDescriptor();
- descriptor.setName("Maven");
- descriptor.setUri("pkg:maven/org.apache.maven/apache-maven@" + version);
- final Map digest = new HashMap<>();
- digest.put("gitTree", GitUtils.gitTree(mavenHome));
- descriptor.setDigest(digest);
+ final ResourceDescriptor descriptor = new ResourceDescriptor()
+ .setName("Maven")
+ .setUri("pkg:maven/org.apache.maven/apache-maven@" + version)
+ .setDigest(Collections.singletonMap("gitTree", GitUtils.gitTree(mavenHome)));
final Properties buildProps = new Properties();
try (InputStream in = coreClassLoader.getResourceAsStream("org/apache/maven/messages/build.properties")) {
if (in != null) {
diff --git a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
index 18b6dd20e..15fde7811 100644
--- a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
+++ b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
@@ -26,9 +26,7 @@
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import javax.inject.Inject;
@@ -224,24 +222,18 @@ private Path ensureOutputDirectory() throws MojoExecutionException {
@Override
public void execute() throws MojoFailureException, MojoExecutionException {
- // Build definition
- final BuildDefinition buildDefinition = new BuildDefinition();
- buildDefinition.setExternalParameters(BuildDefinitions.externalParameters(session));
- buildDefinition.setResolvedDependencies(getBuildDependencies());
- // Builder
- final Builder builder = new Builder();
- // RunDetails
- final RunDetails runDetails = new RunDetails();
- runDetails.setBuilder(builder);
- runDetails.setMetadata(getBuildMetadata());
- // Provenance
- final Provenance provenance = new Provenance();
- provenance.setBuildDefinition(buildDefinition);
- provenance.setRunDetails(runDetails);
- // Statement
- final Statement statement = new Statement();
- statement.setSubject(getSubjects());
- statement.setPredicate(provenance);
+ final BuildDefinition buildDefinition = new BuildDefinition()
+ .setExternalParameters(BuildDefinitions.externalParameters(session))
+ .setResolvedDependencies(getBuildDependencies());
+ final RunDetails runDetails = new RunDetails()
+ .setBuilder(new Builder())
+ .setMetadata(getBuildMetadata());
+ final Provenance provenance = new Provenance()
+ .setBuildDefinition(buildDefinition)
+ .setRunDetails(runDetails);
+ final Statement statement = new Statement()
+ .setSubject(getSubjects())
+ .setPredicate(provenance);
final Path outputPath = ensureOutputDirectory();
final Path artifactPath = outputPath.resolve(ArtifactUtils.getFileName(project.getArtifact(), ATTESTATION_EXTENSION));
@@ -305,14 +297,9 @@ private List getProjectDependencies() throws MojoExecutionEx
* @throws MojoExecutionException If the SCM revision cannot be retrieved.
*/
private ResourceDescriptor getScmDescriptor() throws IOException, MojoExecutionException {
- final ResourceDescriptor scmDescriptor = new ResourceDescriptor();
- final String scmUri = GitUtils.scmToDownloadUri(scmConnectionUrl, scmDirectory.toPath());
- scmDescriptor.setUri(scmUri);
- // Compute the revision
- final Map digest = new HashMap<>();
- digest.put("gitCommit", getScmRevision());
- scmDescriptor.setDigest(digest);
- return scmDescriptor;
+ return new ResourceDescriptor()
+ .setUri(GitUtils.scmToDownloadUri(scmConnectionUrl, scmDirectory.toPath()))
+ .setDigest(Collections.singletonMap("gitCommit", getScmRevision()));
}
/**
@@ -494,13 +481,12 @@ private void signAndWriteStatement(final Statement statement, final Path outputP
final Path paeFile = DsseUtils.writePaeFile(statementBytes, outputPath);
final byte[] sigBytes = DsseUtils.signFile(signer, paeFile);
- final Signature sig = new Signature();
- sig.setKeyid(DsseUtils.getKeyId(sigBytes));
- sig.setSig(sigBytes);
-
- final DsseEnvelope envelope = new DsseEnvelope();
- envelope.setPayload(statementBytes);
- envelope.setSignatures(Collections.singletonList(sig));
+ final Signature sig = new Signature()
+ .setKeyid(DsseUtils.getKeyId(sigBytes))
+ .setSig(sigBytes);
+ final DsseEnvelope envelope = new DsseEnvelope()
+ .setPayload(statementBytes)
+ .setSignatures(Collections.singletonList(sig));
getLog().info("Writing signed attestation envelope to: " + artifactPath);
writeAndAttach(envelope, artifactPath);
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildDefinition.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildDefinition.java
index a856e2c9e..b24f60cbc 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildDefinition.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildDefinition.java
@@ -33,141 +33,155 @@
*/
public class BuildDefinition {
- /** URI indicating what type of build was performed. */
- @JsonProperty("buildType")
- private String buildType = "https://commons.apache.org/builds/0.1.0";
-
- /** Inputs passed to the build. */
- @JsonProperty("externalParameters")
- private Map externalParameters = new HashMap<>();
-
- /** Parameters set by the build platform. */
- @JsonProperty("internalParameters")
- private Map internalParameters = new HashMap<>();
-
- /** Artifacts the build depends on, specified by URI and digest. */
- @JsonProperty("resolvedDependencies")
- private List resolvedDependencies;
-
- /** Creates a new BuildDefinition instance with the default build type. */
- public BuildDefinition() {
- }
-
- /**
- * Creates a new BuildDefinition with the given build type and external parameters.
- *
- * @param buildType URI indicating what type of build was performed
- * @param externalParameters inputs passed to the build
- */
- public BuildDefinition(String buildType, Map externalParameters) {
- this.buildType = buildType;
- this.externalParameters = externalParameters;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
+ /**
+ * URI indicating what type of build was performed.
+ */
+ @JsonProperty("buildType")
+ private String buildType = "https://commons.apache.org/builds/0.1.0";
+
+ /**
+ * Inputs passed to the build.
+ */
+ @JsonProperty("externalParameters")
+ private Map externalParameters = new HashMap<>();
+
+ /**
+ * Parameters set by the build platform.
+ */
+ @JsonProperty("internalParameters")
+ private Map internalParameters = new HashMap<>();
+
+ /**
+ * Artifacts the build depends on, specified by URI and digest.
+ */
+ @JsonProperty("resolvedDependencies")
+ private List resolvedDependencies;
+
+ /**
+ * Creates a new BuildDefinition instance with the default build type.
+ */
+ public BuildDefinition() {
}
- if (o == null || getClass() != o.getClass()) {
- return false;
+
+ /**
+ * Creates a new BuildDefinition with the given build type and external parameters.
+ *
+ * @param buildType URI indicating what type of build was performed
+ * @param externalParameters inputs passed to the build
+ */
+ public BuildDefinition(String buildType, Map externalParameters) {
+ this.buildType = buildType;
+ this.externalParameters = externalParameters;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ BuildDefinition that = (BuildDefinition) o;
+ return Objects.equals(buildType, that.buildType) && Objects.equals(externalParameters, that.externalParameters) && Objects.equals(internalParameters,
+ that.internalParameters) && Objects.equals(resolvedDependencies, that.resolvedDependencies);
+ }
+
+ /**
+ * Gets the URI indicating what type of build was performed.
+ *
+ * Determines the meaning of {@code externalParameters} and {@code internalParameters}.
+ *
+ * @return the build type URI
+ */
+ public String getBuildType() {
+ return buildType;
+ }
+
+ /**
+ * Gets the inputs passed to the build, such as command-line arguments or environment variables.
+ *
+ * @return the external parameters map, or {@code null} if not set
+ */
+ public Map getExternalParameters() {
+ return externalParameters;
+ }
+
+ /**
+ * Gets the artifacts the build depends on, such as sources, dependencies, build tools, and base images,
+ * specified by URI and digest.
+ *
+ * @return the internal parameters map, or {@code null} if not set
+ */
+ public Map getInternalParameters() {
+ return internalParameters;
+ }
+
+ /**
+ * Gets the materials that influenced the build.
+ *
+ * Considered incomplete unless resolved materials are present.
+ *
+ * @return the list of resolved dependencies, or {@code null} if not set
+ */
+ public List getResolvedDependencies() {
+ return resolvedDependencies;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(buildType, externalParameters, internalParameters, resolvedDependencies);
+ }
+
+ /**
+ * Sets the URI indicating what type of build was performed.
+ *
+ * @param buildType the build type URI
+ * @return this for chaining
+ */
+ public BuildDefinition setBuildType(String buildType) {
+ this.buildType = buildType;
+ return this;
+ }
+
+ /**
+ * Sets the inputs passed to the build.
+ *
+ * @param externalParameters the external parameters map
+ * @return this for chaining
+ */
+ public BuildDefinition setExternalParameters(Map externalParameters) {
+ this.externalParameters = externalParameters;
+ return this;
+ }
+
+ /**
+ * Sets the artifacts the build depends on.
+ *
+ * @param internalParameters the internal parameters map
+ * @return this for chaining
+ */
+ public BuildDefinition setInternalParameters(Map internalParameters) {
+ this.internalParameters = internalParameters;
+ return this;
+ }
+
+ /**
+ * Sets the materials that influenced the build.
+ *
+ * @param resolvedDependencies the list of resolved dependencies
+ * @return this for chaining
+ */
+ public BuildDefinition setResolvedDependencies(List resolvedDependencies) {
+ this.resolvedDependencies = resolvedDependencies;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "BuildDefinition{buildType='" + buildType + '\''
+ + ", externalParameters=" + externalParameters
+ + ", internalParameters=" + internalParameters
+ + ", resolvedDependencies=" + resolvedDependencies + '}';
}
- BuildDefinition that = (BuildDefinition) o;
- return Objects.equals(buildType, that.buildType)
- && Objects.equals(externalParameters, that.externalParameters)
- && Objects.equals(internalParameters, that.internalParameters)
- && Objects.equals(resolvedDependencies, that.resolvedDependencies);
- }
-
- /**
- * Gets the URI indicating what type of build was performed.
- *
- * Determines the meaning of {@code externalParameters} and {@code internalParameters}.
- *
- * @return the build type URI
- */
- public String getBuildType() {
- return buildType;
- }
-
- /**
- * Gets the inputs passed to the build, such as command-line arguments or environment variables.
- *
- * @return the external parameters map, or {@code null} if not set
- */
- public Map getExternalParameters() {
- return externalParameters;
- }
-
- /**
- * Gets the artifacts the build depends on, such as sources, dependencies, build tools, and base images,
- * specified by URI and digest.
- *
- * @return the internal parameters map, or {@code null} if not set
- */
- public Map getInternalParameters() {
- return internalParameters;
- }
-
- /**
- * Gets the materials that influenced the build.
- *
- * Considered incomplete unless resolved materials are present.
- *
- * @return the list of resolved dependencies, or {@code null} if not set
- */
- public List getResolvedDependencies() {
- return resolvedDependencies;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(buildType, externalParameters, internalParameters, resolvedDependencies);
- }
-
- /**
- * Sets the URI indicating what type of build was performed.
- *
- * @param buildType the build type URI
- */
- public void setBuildType(String buildType) {
- this.buildType = buildType;
- }
-
- /**
- * Sets the inputs passed to the build.
- *
- * @param externalParameters the external parameters map
- */
- public void setExternalParameters(Map externalParameters) {
- this.externalParameters = externalParameters;
- }
-
- /**
- * Sets the artifacts the build depends on.
- *
- * @param internalParameters the internal parameters map
- */
- public void setInternalParameters(Map internalParameters) {
- this.internalParameters = internalParameters;
- }
-
- /**
- * Sets the materials that influenced the build.
- *
- * @param resolvedDependencies the list of resolved dependencies
- */
- public void setResolvedDependencies(List resolvedDependencies) {
- this.resolvedDependencies = resolvedDependencies;
- }
-
- @Override
- public String toString() {
- return "BuildDefinition{"
- + "buildType='" + buildType + '\''
- + ", externalParameters=" + externalParameters
- + ", internalParameters=" + internalParameters
- + ", resolvedDependencies=" + resolvedDependencies
- + '}';
- }
}
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildMetadata.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildMetadata.java
index 6f6316961..595e0f714 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildMetadata.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildMetadata.java
@@ -105,27 +105,33 @@ public int hashCode() {
* Sets the timestamp of when the build completed.
*
* @param finishedOn the completion timestamp
+ * @return this for chaining
*/
- public void setFinishedOn(OffsetDateTime finishedOn) {
+ public BuildMetadata setFinishedOn(OffsetDateTime finishedOn) {
this.finishedOn = finishedOn;
+ return this;
}
/**
* Sets the identifier for this build invocation.
*
* @param invocationId the invocation identifier
+ * @return this for chaining
*/
- public void setInvocationId(String invocationId) {
+ public BuildMetadata setInvocationId(String invocationId) {
this.invocationId = invocationId;
+ return this;
}
/**
* Sets the timestamp of when the build started.
*
* @param startedOn the start timestamp
+ * @return this for chaining
*/
- public void setStartedOn(OffsetDateTime startedOn) {
+ public BuildMetadata setStartedOn(OffsetDateTime startedOn) {
this.startedOn = startedOn;
+ return this;
}
@Override
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Builder.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Builder.java
index 31102295a..135f9c999 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Builder.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Builder.java
@@ -93,27 +93,33 @@ public int hashCode() {
* Sets the orchestrator dependencies that may affect provenance generation or security guarantees.
*
* @param builderDependencies the list of builder dependencies
+ * @return this for chaining
*/
- public void setBuilderDependencies(List builderDependencies) {
+ public Builder setBuilderDependencies(List builderDependencies) {
this.builderDependencies = builderDependencies;
+ return this;
}
/**
* Sets the identifier of the builder.
*
* @param id the builder identifier URI
+ * @return this for chaining
*/
- public void setId(String id) {
+ public Builder setId(String id) {
this.id = id;
+ return this;
}
/**
* Sets the map of build platform component names to their versions.
*
* @param version the version map
+ * @return this for chaining
*/
- public void setVersion(Map version) {
+ public Builder setVersion(Map version) {
this.version = version;
+ return this;
}
@Override
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/DsseEnvelope.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/DsseEnvelope.java
index 2c9bc601a..e68e5757c 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/DsseEnvelope.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/DsseEnvelope.java
@@ -93,27 +93,33 @@ public int hashCode() {
* Sets the serialized payload bytes.
*
* @param payload the payload bytes
+ * @return this for chaining
*/
- public void setPayload(byte[] payload) {
+ public DsseEnvelope setPayload(byte[] payload) {
this.payload = payload;
+ return this;
}
/**
* Sets the payload type URI.
*
* @param payloadType the payload type URI
+ * @return this for chaining
*/
- public void setPayloadType(String payloadType) {
+ public DsseEnvelope setPayloadType(String payloadType) {
this.payloadType = payloadType;
+ return this;
}
/**
* Sets the list of signatures over the PAE-encoded payload.
*
* @param signatures the signatures
+ * @return this for chaining
*/
- public void setSignatures(List signatures) {
+ public DsseEnvelope setSignatures(List signatures) {
this.signatures = signatures;
+ return this;
}
@Override
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Provenance.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Provenance.java
index 1b7361e7d..6002dce7e 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Provenance.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Provenance.java
@@ -99,18 +99,22 @@ public int hashCode() {
* Sets the build definition describing all inputs that produced the build output.
*
* @param buildDefinition the build definition
+ * @return this for chaining
*/
- public void setBuildDefinition(BuildDefinition buildDefinition) {
+ public Provenance setBuildDefinition(BuildDefinition buildDefinition) {
this.buildDefinition = buildDefinition;
+ return this;
}
/**
* Sets the details about the invocation of the build tool and the environment in which it was run.
*
* @param runDetails the run details
+ * @return this for chaining
*/
- public void setRunDetails(RunDetails runDetails) {
+ public Provenance setRunDetails(RunDetails runDetails) {
this.runDetails = runDetails;
+ return this;
}
@Override
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/ResourceDescriptor.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/ResourceDescriptor.java
index 79210d33d..cb3510ed1 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/ResourceDescriptor.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/ResourceDescriptor.java
@@ -155,63 +155,77 @@ public int hashCode() {
* Sets additional key-value metadata about the resource.
*
* @param annotations the annotations map
+ * @return this for chaining
*/
- public void setAnnotations(Map annotations) {
+ public ResourceDescriptor setAnnotations(Map annotations) {
this.annotations = annotations;
+ return this;
}
/**
* Sets the raw contents of the resource.
*
* @param content the resource content
+ * @return this for chaining
*/
- public void setContent(byte[] content) {
+ public ResourceDescriptor setContent(byte[] content) {
this.content = content;
+ return this;
}
/**
* Sets the map of cryptographic digest algorithms to their hex-encoded values.
*
* @param digest the digest map
+ * @return this for chaining
*/
- public void setDigest(Map digest) {
+ public ResourceDescriptor setDigest(Map digest) {
this.digest = digest;
+ return this;
}
/**
* Sets the download URI for the resource.
*
* @param downloadLocation the download location URI
+ * @return this for chaining
*/
- public void setDownloadLocation(String downloadLocation) {
+ public ResourceDescriptor setDownloadLocation(String downloadLocation) {
this.downloadLocation = downloadLocation;
+ return this;
}
/**
* Sets the media type of the resource.
*
* @param mediaType the media type
+ * @return this for chaining
*/
- public void setMediaType(String mediaType) {
+ public ResourceDescriptor setMediaType(String mediaType) {
this.mediaType = mediaType;
+ return this;
}
/**
* Sets the name of the resource.
*
* @param name the resource name
+ * @return this for chaining
*/
- public void setName(String name) {
+ public ResourceDescriptor setName(String name) {
this.name = name;
+ return this;
}
/**
* Sets the URI identifying the resource.
*
* @param uri the resource URI
+ * @return this for chaining
*/
- public void setUri(String uri) {
+ public ResourceDescriptor setUri(String uri) {
this.uri = uri;
+ return this;
}
@Override
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/RunDetails.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/RunDetails.java
index 90aae318c..da14aefc4 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/RunDetails.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/RunDetails.java
@@ -113,27 +113,33 @@ public int hashCode() {
* Sets the builder that executed the invocation.
*
* @param builder the builder
+ * @return this for chaining
*/
- public void setBuilder(Builder builder) {
+ public RunDetails setBuilder(Builder builder) {
this.builder = builder;
+ return this;
}
/**
* Sets the artifacts produced as a side effect of the build that are not the primary output.
*
* @param byproducts the list of byproduct artifacts
+ * @return this for chaining
*/
- public void setByproducts(List byproducts) {
+ public RunDetails setByproducts(List byproducts) {
this.byproducts = byproducts;
+ return this;
}
/**
* Sets the metadata about the build invocation.
*
* @param metadata the build metadata
+ * @return this for chaining
*/
- public void setMetadata(BuildMetadata metadata) {
+ public RunDetails setMetadata(BuildMetadata metadata) {
this.metadata = metadata;
+ return this;
}
@Override
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Signature.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Signature.java
index d514608e4..77e769805 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Signature.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Signature.java
@@ -82,18 +82,22 @@ public int hashCode() {
* Sets the key identifier hint.
*
* @param keyid the key identifier, or {@code null} to leave unset
+ * @return this for chaining
*/
- public void setKeyid(String keyid) {
+ public Signature setKeyid(String keyid) {
this.keyid = keyid;
+ return this;
}
/**
* Sets the raw signature bytes.
*
* @param sig the signature bytes
+ * @return this for chaining
*/
- public void setSig(byte[] sig) {
+ public Signature setSig(byte[] sig) {
this.sig = sig;
+ return this;
}
@Override
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Statement.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Statement.java
index eefb90dc7..b44dfc03c 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Statement.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Statement.java
@@ -96,19 +96,23 @@ public int hashCode() {
* Sets the provenance predicate and automatically assigns {@code predicateType} to the SLSA provenance v1 URI.
*
* @param predicate the provenance predicate
+ * @return this for chaining
*/
- public void setPredicate(Provenance predicate) {
+ public Statement setPredicate(Provenance predicate) {
this.predicate = predicate;
this.predicateType = Provenance.PREDICATE_TYPE;
+ return this;
}
/**
* Sets the set of software artifacts that the attestation applies to.
*
* @param subject the list of subject artifacts
+ * @return this for chaining
*/
- public void setSubject(List subject) {
+ public Statement setSubject(List subject) {
this.subject = subject;
+ return this;
}
@Override
From ad63bc775e915471004b73dca60a97c91e09acb5 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Mon, 20 Apr 2026 14:37:39 +0200
Subject: [PATCH 28/51] fix: use `jackson-bom`
---
pom.xml | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/pom.xml b/pom.xml
index a5001cd27..ba61b018b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -115,8 +115,7 @@
true
true
- 2.21.1
- 2.21
+ 2.21.2
2.0.17
@@ -128,6 +127,14 @@
pom
import
+
+
+ com.fasterxml.jackson
+ jackson-bom
+ ${commons.jackson.version}
+ pom
+ import
+
@@ -212,17 +219,14 @@
com.fasterxml.jackson.core
jackson-databind
- ${commons.jackson.version}
com.fasterxml.jackson.core
jackson-annotations
- ${commons.jackson.annotations.version}
com.fasterxml.jackson.datatype
jackson-datatype-jsr310
- ${commons.jackson.version}
runtime
From 89d61d277a202380377f306d746bb0b80e26d7f4 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Mon, 20 Apr 2026 14:40:51 +0200
Subject: [PATCH 29/51] fix: `internal` Javadoc
---
.../apache/commons/release/plugin/internal/package-info.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/package-info.java b/src/main/java/org/apache/commons/release/plugin/internal/package-info.java
index 9218ebff4..44c988052 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/package-info.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/package-info.java
@@ -16,8 +16,8 @@
*/
/**
- * Internal utilities for the commons-release-plugin.
+ * Internal utilities
*
- * Should not be referenced by external artifacts.
+ * Should not be referenced by external artifacts. Their API can change at any moment
*/
package org.apache.commons.release.plugin.internal;
From d64965bf39428e762e1ed13caa257a2ab45052ad Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Mon, 20 Apr 2026 15:16:25 +0200
Subject: [PATCH 30/51] fix: improve documentation
---
src/site/markdown/slsa/v0.1.0.md | 155 ++++++++++++++++++++++++++++---
1 file changed, 144 insertions(+), 11 deletions(-)
diff --git a/src/site/markdown/slsa/v0.1.0.md b/src/site/markdown/slsa/v0.1.0.md
index b9a569ef2..a5287f72d 100644
--- a/src/site/markdown/slsa/v0.1.0.md
+++ b/src/site/markdown/slsa/v0.1.0.md
@@ -6,8 +6,32 @@
"buildType": "https://commons.apache.org/proper/commons-release-plugin/slsa/v0.1.0"
```
-This is a [SLSA Build Provenance](https://slsa.dev/spec/v1.2/build-provenance) build type
-that describes releases produced by Apache Commons PMC release managers running Maven on their own equipment.
+This document defines a [SLSA v1.2 Build Provenance](https://slsa.dev/spec/v1.2/build-provenance) **build type** for
+releases of Apache Commons components.
+
+Apache Commons releases are cut on a PMC release manager's workstation by invoking Maven against a checkout of the
+project's Git repository. The `commons-release-plugin` captures the build inputs and emits the result as an in-toto
+attestation covering every artifact attached to the project.
+
+Because the build runs on the release manager's own hardware rather than on a hosted build service, the provenance
+corresponds to [SLSA Build Level 1](https://slsa.dev/spec/v1.2/levels): it is generated by the same process that
+produces the artifacts and is signed with the release manager's OpenPGP key, but the build platform itself is not
+separately attested.
+
+The OpenPGP keys used to sign past and present artifacts are available at: https://downloads.apache.org/commons/KEYS
+
+Attestations are published to Maven Central under the released artifact's coordinates, distinguished by an
+`intoto.jsonl` type:
+
+```xml
+
+
+ org.apache.commons
+ ${artifactId}
+ intoto.jsonl
+ ${version}
+
+```
## Build definition
@@ -84,7 +108,7 @@ They are only present if the resource is accessible from Maven's Core Classloade
|-------------------------|--------------------------------------------------------------|
| `distributionId` | The ID of the Maven distribution. |
| `distributionName` | The full name of the Maven distribution. |
-| `distributionShortName` | The short name of the Mavendistribution. |
+| `distributionShortName` | The short name of the Maven distribution. |
| `buildNumber` | The Git commit hash from which this Maven release was built. |
| `version` | The Maven version string. |
@@ -115,14 +139,123 @@ It represents the commons-release-plugin acting as the build platform.
## Subjects
-The attestation covers all artifacts attached to the Maven project at the time the `verify` phase runs:
-the primary artifact (e.g. the JAR) and any attached artifacts (e.g. sources JAR, javadoc JAR, POM).
-
-| Field | Value |
-|-----------------|------------------------------------------|
-| `name` | Artifact filename. |
-| `uri` | Package URL. |
-| `digest.sha256` | SHA-256 hex digest of the artifact file. |
+The [`subject`](https://github.com/in-toto/attestation/blob/main/spec/v1/statement.md#fields) array
+lists every artifact produces by the build. It has the following properties
+
+| Field | Value |
+|----------|-------------------------------------------------------------------------------------------------------------------------------------|
+| `name` | Artifact filename in the default Maven repository layout, e.g. `commons-text-1.4-sources.jar`. |
+| `uri` | [Package URL](https://github.com/package-url/purl-spec) identifying the artifact in the `maven` namespace. |
+| `digest` | Map of [in-toto digest names](https://github.com/in-toto/attestation/blob/main/spec/v1/digest_set.md) to hex-encoded digest values. |
+
+By default, every subject carries `md5`, `sha1`, `sha256` and `sha512` digests.
+
+## Example
+
+The following is the bare attestation statement produced for the `commons-text` 1.4 release
+(abridged: most subjects are elided, and the JDK annotations trimmed). The full fixture lives at
+[`src/test/resources/attestations/commons-text-1.4.intoto.json`](https://github.com/apache/commons-release-plugin/blob/main/src/test/resources/attestations/commons-text-1.4.intoto.json)
+in the plugin source tree.
+
+The statement shown below is wrapped in a [DSSE envelope](https://github.com/secure-systems-lab/dsse/blob/master/envelope.md)
+signed with the release manager's OpenPGP key, and the `.intoto.jsonl` file deployed to Maven Central
+contains that envelope.
+
+```json5
+{
+ "subject": [
+ {
+ "name": "commons-text-1.4.jar",
+ "uri": "pkg:maven/commons-text/commons-text@1.4?type=jar",
+ "digest": {
+ "md5": "9cbe22bb0ce86c70779213dfb7f3eb5a",
+ "sha1": "c81f089b3542485d4d09b02aae822906e5d2f209",
+ "sha256": "ad2d2eacf15ab740c115294afc1192603d8342004a6d7d0ad35446f7dda8a134",
+ "sha512": "126302c5f6865733774eb41fecc10ba8d0bb5ba11d14b9562047429abeb13bf8cdcdbfdf5e7d7708e2a40f67f4265cbbce609164f57abcd676067a840aa48e6a"
+ }
+ },
+ // … one entry per attached artifact (POM, sources, javadoc, tests, and distribution archives) …
+ {
+ "name": "commons-text-1.4-src.zip",
+ "uri": "pkg:maven/commons-text/commons-text@1.4?classifier=src&type=zip",
+ "digest": {
+ "md5": "fd65603e930f2b0805c809aa2deb1498",
+ "sha1": "ca1cc6fbb4e46b44f8bb09b70c9e3a2ae3c5fce8",
+ "sha256": "e4a6c992153faae4f7faff689b899073000364e376736b9746a5d0acb9d8b980",
+ "sha512": "79ca61ff7b287407428bbb6ae13c6d372dcd0665114c55cd5bc57978a6fa760305e32feabef62cfeb0c4181220a59406239f6cccaa9a25c68773eef0250cb3a9"
+ }
+ }
+ ],
+ "predicateType": "https://slsa.dev/provenance/v1",
+ "predicate": {
+ "buildDefinition": {
+ "buildType": "https://commons.apache.org/builds/0.1.0",
+ "externalParameters": {
+ "maven.goals": ["deploy"],
+ "maven.profiles": ["release"],
+ "maven.user.properties": {
+ "gpg.keyname": "3C8D57E0A2B5C6D7E8F9A0B1C2D3E4F5A6B7C8D9"
+ },
+ "maven.cmdline": "deploy -Prelease -Dgpg.keyname=3C8D57E0A2B5C6D7E8F9A0B1C2D3E4F5A6B7C8D9",
+ "jvm.args": [
+ "-Dfile.encoding=UTF-8",
+ "-Dsun.stdout.encoding=UTF-8",
+ "-Dsun.stderr.encoding=UTF-8"
+ ],
+ "env": {
+ "LANG": "pl_PL.UTF-8",
+ "TZ": "UTC"
+ }
+ },
+ "internalParameters": {},
+ "resolvedDependencies": [
+ // JDK that ran the build
+ {
+ "name": "JDK",
+ "digest": { "gitTree": "bdb67e47c1b7df9c35ae045f29a348bb5bd32dc3" },
+ "annotations": {
+ "vendor": "Eclipse Adoptium",
+ "vendor.version": "Temurin-25.0.2+10",
+ "version": "25.0.2",
+ "vm.name": "OpenJDK 64-Bit Server VM",
+ "vm.version": "25.0.2+10-LTS"
+ // … remaining java.* system properties elided …
+ }
+ },
+ // Maven installation
+ {
+ "name": "Maven",
+ "uri": "pkg:maven/org.apache.maven/apache-maven@3.9.12",
+ "digest": { "gitTree": "3cdb4a67690dc18373f70ead98dc86567cc5ad67" },
+ "annotations": {
+ "distributionId": "apache-maven",
+ "distributionName": "Apache Maven",
+ "distributionShortName": "Maven",
+ "buildNumber": "848fbb4bf2d427b72bdb2471c22fced7ebd9a7a1",
+ "version": "3.9.12"
+ }
+ },
+ // Source revision (branch or tag at release time)
+ {
+ "uri": "git+https://github.com/apache/commons-text.git@rel/commons-text-1.4",
+ "digest": { "gitCommit": "f519b3670795da3fb4f43b6af1f727eadf8e6800" }
+ }
+ ]
+ },
+ "runDetails": {
+ "builder": {
+ "id": "https://commons.apache.org/builds/0.1.0",
+ "builderDependencies": [],
+ "version": {}
+ },
+ "metadata": {
+ "startedOn": "2026-04-20T09:28:44Z",
+ "finishedOn": "2026-04-20T09:38:12Z"
+ }
+ }
+ }
+}
+```
## Version history
From 2cac4bd5ed23921f5915dc27387d4fdcab55b7ab Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Mon, 20 Apr 2026 15:22:31 +0200
Subject: [PATCH 31/51] fix: sort Java properties
---
.../plugin/internal/BuildDefinitions.java | 13 ++---
src/site/markdown/slsa/v0.1.0.md | 58 +++++++++++--------
.../attestations/commons-text-1.4.intoto.json | 20 +++----
3 files changed, 50 insertions(+), 41 deletions(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java b/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
index f74549a63..537e3ad4d 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
@@ -26,6 +26,7 @@
import java.util.List;
import java.util.Map;
import java.util.Properties;
+import java.util.TreeMap;
import org.apache.commons.release.plugin.slsa.v1_2.ResourceDescriptor;
import org.apache.maven.execution.MavenExecutionRequest;
@@ -86,15 +87,11 @@ public static Map externalParameters(final MavenSession session)
*/
public static ResourceDescriptor jvm(final Path javaHome) throws IOException {
final String[] propertyNames = {
- "java.version", "java.version.date",
- "java.vendor", "java.vendor.url", "java.vendor.version",
- "java.home",
- "java.vm.specification.version", "java.vm.specification.vendor", "java.vm.specification.name",
- "java.vm.version", "java.vm.vendor", "java.vm.name",
- "java.specification.version", "java.specification.maintenance.version",
- "java.specification.vendor", "java.specification.name",
+ "java.home", "java.specification.maintenance.version", "java.specification.name", "java.specification.vendor", "java.specification.version",
+ "java.vendor", "java.vendor.url", "java.vendor.version", "java.version", "java.version.date", "java.vm.name", "java.vm.specification.name",
+ "java.vm.specification.vendor", "java.vm.specification.version", "java.vm.vendor", "java.vm.version"
};
- final Map annotations = new HashMap<>();
+ final Map annotations = new TreeMap<>();
for (final String prop : propertyNames) {
annotations.put(prop.substring("java.".length()), System.getProperty(prop));
}
diff --git a/src/site/markdown/slsa/v0.1.0.md b/src/site/markdown/slsa/v0.1.0.md
index a5287f72d..8dbee91d8 100644
--- a/src/site/markdown/slsa/v0.1.0.md
+++ b/src/site/markdown/slsa/v0.1.0.md
@@ -77,22 +77,22 @@ The following annotations are recorded from [
| Annotation key | System property | Description |
|-------------------------------------|------------------------------------------|--------------------------------------------------------------------------|
-| `version` | `java.version` | Java Runtime Environment version. |
-| `version.date` | `java.version.date` | Java Runtime Environment version date, in ISO-8601 YYYY-MM-DD format. |
+| `home` | `java.home` | Java installation directory. |
+| `specification.maintenance.version` | `java.specification.maintenance.version` | Java Runtime Environment specification maintenance version _(optional)_. |
+| `specification.name` | `java.specification.name` | Java Runtime Environment specification name. |
+| `specification.vendor` | `java.specification.vendor` | Java Runtime Environment specification vendor. |
+| `specification.version` | `java.specification.version` | Java Runtime Environment specification version. |
| `vendor` | `java.vendor` | Java Runtime Environment vendor. |
| `vendor.url` | `java.vendor.url` | Java vendor URL. |
| `vendor.version` | `java.vendor.version` | Java vendor version _(optional)_. |
-| `home` | `java.home` | Java installation directory. |
-| `vm.specification.version` | `java.vm.specification.version` | Java Virtual Machine specification version. |
-| `vm.specification.vendor` | `java.vm.specification.vendor` | Java Virtual Machine specification vendor. |
+| `version` | `java.version` | Java Runtime Environment version. |
+| `version.date` | `java.version.date` | Java Runtime Environment version date, in ISO-8601 YYYY-MM-DD format. |
+| `vm.name` | `java.vm.name` | Java Virtual Machine implementation name. |
| `vm.specification.name` | `java.vm.specification.name` | Java Virtual Machine specification name. |
-| `vm.version` | `java.vm.version` | Java Virtual Machine implementation version. |
+| `vm.specification.vendor` | `java.vm.specification.vendor` | Java Virtual Machine specification vendor. |
+| `vm.specification.version` | `java.vm.specification.version` | Java Virtual Machine specification version. |
| `vm.vendor` | `java.vm.vendor` | Java Virtual Machine implementation vendor. |
-| `vm.name` | `java.vm.name` | Java Virtual Machine implementation name. |
-| `specification.version` | `java.specification.version` | Java Runtime Environment specification version. |
-| `specification.maintenance.version` | `java.specification.maintenance.version` | Java Runtime Environment specification maintenance version _(optional)_. |
-| `specification.vendor` | `java.specification.vendor` | Java Runtime Environment specification vendor. |
-| `specification.name` | `java.specification.name` | Java Runtime Environment specification name. |
+| `vm.version` | `java.vm.version` | Java Virtual Machine implementation version. |
#### Maven
@@ -154,10 +154,12 @@ By default, every subject carries `md5`, `sha1`, `sha256` and `sha512` digests.
The following is the bare attestation statement produced for the `commons-text` 1.4 release
(abridged: most subjects are elided, and the JDK annotations trimmed). The full fixture lives at
-[`src/test/resources/attestations/commons-text-1.4.intoto.json`](https://github.com/apache/commons-release-plugin/blob/main/src/test/resources/attestations/commons-text-1.4.intoto.json)
+[
+`src/test/resources/attestations/commons-text-1.4.intoto.json`](https://github.com/apache/commons-release-plugin/blob/main/src/test/resources/attestations/commons-text-1.4.intoto.json)
in the plugin source tree.
-The statement shown below is wrapped in a [DSSE envelope](https://github.com/secure-systems-lab/dsse/blob/master/envelope.md)
+The statement shown below is wrapped in
+a [DSSE envelope](https://github.com/secure-systems-lab/dsse/blob/master/envelope.md)
signed with the release manager's OpenPGP key, and the `.intoto.jsonl` file deployed to Maven Central
contains that envelope.
@@ -191,8 +193,12 @@ contains that envelope.
"buildDefinition": {
"buildType": "https://commons.apache.org/builds/0.1.0",
"externalParameters": {
- "maven.goals": ["deploy"],
- "maven.profiles": ["release"],
+ "maven.goals": [
+ "deploy"
+ ],
+ "maven.profiles": [
+ "release"
+ ],
"maven.user.properties": {
"gpg.keyname": "3C8D57E0A2B5C6D7E8F9A0B1C2D3E4F5A6B7C8D9"
},
@@ -212,13 +218,15 @@ contains that envelope.
// JDK that ran the build
{
"name": "JDK",
- "digest": { "gitTree": "bdb67e47c1b7df9c35ae045f29a348bb5bd32dc3" },
+ "digest": {
+ "gitTree": "bdb67e47c1b7df9c35ae045f29a348bb5bd32dc3"
+ },
"annotations": {
- "vendor": "Eclipse Adoptium",
- "vendor.version": "Temurin-25.0.2+10",
- "version": "25.0.2",
- "vm.name": "OpenJDK 64-Bit Server VM",
- "vm.version": "25.0.2+10-LTS"
+ "home": "/usr/lib/jvm/temurin-25-jdk-amd64",
+ "specification.maintenance.version": null,
+ "specification.name": "Java Platform API Specification",
+ "specification.vendor": "Oracle Corporation",
+ "specification.version": "25",
// … remaining java.* system properties elided …
}
},
@@ -226,7 +234,9 @@ contains that envelope.
{
"name": "Maven",
"uri": "pkg:maven/org.apache.maven/apache-maven@3.9.12",
- "digest": { "gitTree": "3cdb4a67690dc18373f70ead98dc86567cc5ad67" },
+ "digest": {
+ "gitTree": "3cdb4a67690dc18373f70ead98dc86567cc5ad67"
+ },
"annotations": {
"distributionId": "apache-maven",
"distributionName": "Apache Maven",
@@ -238,7 +248,9 @@ contains that envelope.
// Source revision (branch or tag at release time)
{
"uri": "git+https://github.com/apache/commons-text.git@rel/commons-text-1.4",
- "digest": { "gitCommit": "f519b3670795da3fb4f43b6af1f727eadf8e6800" }
+ "digest": {
+ "gitCommit": "f519b3670795da3fb4f43b6af1f727eadf8e6800"
+ }
}
]
},
diff --git a/src/test/resources/attestations/commons-text-1.4.intoto.json b/src/test/resources/attestations/commons-text-1.4.intoto.json
index 37007233b..314f033fe 100644
--- a/src/test/resources/attestations/commons-text-1.4.intoto.json
+++ b/src/test/resources/attestations/commons-text-1.4.intoto.json
@@ -133,22 +133,22 @@
"gitTree": "bdb67e47c1b7df9c35ae045f29a348bb5bd32dc3"
},
"annotations": {
- "vendor.version": "Temurin-25.0.2+10",
+ "home": "/usr/lib/jvm/temurin-25-jdk-amd64",
+ "specification.maintenance.version": null,
"specification.name": "Java Platform API Specification",
"specification.vendor": "Oracle Corporation",
- "vm.version": "25.0.2+10-LTS",
- "version": "25.0.2",
- "version.date": "2026-01-20",
- "vm.specification.vendor": "Oracle Corporation",
- "home": "/usr/lib/jvm/temurin-25-jdk-amd64",
+ "specification.version": "25",
"vendor": "Eclipse Adoptium",
- "vm.vendor": "Eclipse Adoptium",
"vendor.url": "https://adoptium.net/",
- "specification.maintenance.version": null,
- "vm.specification.version": "25",
+ "vendor.version": "Temurin-25.0.2+10",
+ "version": "25.0.2",
+ "version.date": "2026-01-20",
"vm.name": "OpenJDK 64-Bit Server VM",
"vm.specification.name": "Java Virtual Machine Specification",
- "specification.version": "25"
+ "vm.specification.vendor": "Oracle Corporation",
+ "vm.specification.version": "25",
+ "vm.vendor": "Eclipse Adoptium",
+ "vm.version": "25.0.2+10-LTS"
}
},
{
From 92c9d69636495e83e504b66cf31d433141afb1f8 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Mon, 20 Apr 2026 15:31:26 +0200
Subject: [PATCH 32/51] fix: buildType URL
---
.../commons/release/plugin/slsa/v1_2/BuildDefinition.java | 2 +-
src/site/markdown/slsa/v0.1.0.md | 2 +-
src/test/resources/attestations/commons-text-1.4.intoto.json | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildDefinition.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildDefinition.java
index b24f60cbc..43bcb94d5 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildDefinition.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/BuildDefinition.java
@@ -37,7 +37,7 @@ public class BuildDefinition {
* URI indicating what type of build was performed.
*/
@JsonProperty("buildType")
- private String buildType = "https://commons.apache.org/builds/0.1.0";
+ private String buildType = "https://commons.apache.org/proper/commons-release-plugin/slsa/v0.1.0";
/**
* Inputs passed to the build.
diff --git a/src/site/markdown/slsa/v0.1.0.md b/src/site/markdown/slsa/v0.1.0.md
index 8dbee91d8..a52924296 100644
--- a/src/site/markdown/slsa/v0.1.0.md
+++ b/src/site/markdown/slsa/v0.1.0.md
@@ -191,7 +191,7 @@ contains that envelope.
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
- "buildType": "https://commons.apache.org/builds/0.1.0",
+ "buildType": "https://commons.apache.org/proper/commons-release-plugin/slsa/v0.1.0",
"externalParameters": {
"maven.goals": [
"deploy"
diff --git a/src/test/resources/attestations/commons-text-1.4.intoto.json b/src/test/resources/attestations/commons-text-1.4.intoto.json
index 314f033fe..9063f5338 100644
--- a/src/test/resources/attestations/commons-text-1.4.intoto.json
+++ b/src/test/resources/attestations/commons-text-1.4.intoto.json
@@ -104,7 +104,7 @@
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
- "buildType": "https://commons.apache.org/builds/0.1.0",
+ "buildType": "https://commons.apache.org/proper/commons-release-plugin/slsa/v0.1.0",
"externalParameters": {
"maven.profiles": [
"release"
From 2cf85f176a99c0c7665d2cb4eed42f28a370a8b6 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Mon, 20 Apr 2026 15:41:15 +0200
Subject: [PATCH 33/51] fix: builder.id resolves to commons-release-plugin
version
---
pom.xml | 19 ++++++++++++++++++
.../plugin/mojos/BuildAttestationMojo.java | 20 ++++++++++++++++++-
.../release/plugin/slsa/v1_2/Builder.java | 2 +-
src/site/markdown/slsa/v0.1.0.md | 9 ++++++---
.../mojos/BuildAttestationMojoTest.java | 11 ++++++++++
.../attestations/commons-text-1.4.intoto.json | 2 +-
src/test/resources/plugin.properties | 18 +++++++++++++++++
7 files changed, 75 insertions(+), 6 deletions(-)
create mode 100644 src/test/resources/plugin.properties
diff --git a/pom.xml b/pom.xml
index ba61b018b..5b914750a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -320,6 +320,25 @@
+
+
+
+ src/test/resources
+ true
+
+ attestations/**
+ plugin.properties
+
+
+
+ src/test/resources
+ false
+
+ attestations/**
+ plugin.properties
+
+
+
diff --git a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
index 15fde7811..54f20682c 100644
--- a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
+++ b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
@@ -52,6 +52,7 @@
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
@@ -165,6 +166,12 @@ public class BuildAttestationMojo extends AbstractMojo {
*/
@Parameter(property = "commons.release.signAttestation", defaultValue = "true")
private boolean signAttestation;
+ /**
+ * Descriptor of this plugin; used to fill in {@code builder.id} with the plugin's own
+ * Package URL so that consumers can resolve the exact code that produced the provenance.
+ */
+ @Parameter(defaultValue = "${plugin}", readonly = true)
+ private PluginDescriptor pluginDescriptor;
/**
* GPG signer used for signing; lazily initialized from plugin parameters when {@code null}.
*/
@@ -225,8 +232,10 @@ public void execute() throws MojoFailureException, MojoExecutionException {
final BuildDefinition buildDefinition = new BuildDefinition()
.setExternalParameters(BuildDefinitions.externalParameters(session))
.setResolvedDependencies(getBuildDependencies());
+ final String builderId = String.format("pkg:maven/%s/%s@%s",
+ pluginDescriptor.getGroupId(), pluginDescriptor.getArtifactId(), pluginDescriptor.getVersion());
final RunDetails runDetails = new RunDetails()
- .setBuilder(new Builder())
+ .setBuilder(new Builder().setId(builderId))
.setMetadata(getBuildMetadata());
final Provenance provenance = new Provenance()
.setBuildDefinition(buildDefinition)
@@ -451,6 +460,15 @@ void setSignAttestation(final boolean signAttestation) {
this.signAttestation = signAttestation;
}
+ /**
+ * Sets the plugin descriptor. Intended for testing.
+ *
+ * @param pluginDescriptor the plugin descriptor
+ */
+ void setPluginDescriptor(final PluginDescriptor pluginDescriptor) {
+ this.pluginDescriptor = pluginDescriptor;
+ }
+
/**
* Sets the GPG signer used for signing. Intended for testing.
*
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Builder.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Builder.java
index 135f9c999..508d622a8 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Builder.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Builder.java
@@ -36,7 +36,7 @@ public class Builder {
private List builderDependencies = new ArrayList<>();
/** Identifier URI of the builder. */
@JsonProperty("id")
- private String id = "https://commons.apache.org/builds/0.1.0";
+ private String id;
/** Map of build platform component names to their versions. */
@JsonProperty("version")
private Map version = new HashMap<>();
diff --git a/src/site/markdown/slsa/v0.1.0.md b/src/site/markdown/slsa/v0.1.0.md
index a52924296..cff196464 100644
--- a/src/site/markdown/slsa/v0.1.0.md
+++ b/src/site/markdown/slsa/v0.1.0.md
@@ -134,8 +134,11 @@ These are appended after the build tool entries above.
### Builder
-The `builder.id` is always `https://commons.apache.org/builds/0.1.0`.
-It represents the commons-release-plugin acting as the build platform.
+The `builder.id` is the [Package URL](https://github.com/package-url/purl-spec) of the
+`commons-release-plugin` release that produced the attestation, e.g.
+`pkg:maven/org.apache.commons/commons-release-plugin@1.9.3`. It identifies the trust boundary of
+the "build platform": the exact plugin code that emitted the provenance. Verifiers can resolve the
+PURL to the signed artifact on Maven Central to inspect the builder.
## Subjects
@@ -256,7 +259,7 @@ contains that envelope.
},
"runDetails": {
"builder": {
- "id": "https://commons.apache.org/builds/0.1.0",
+ "id": "pkg:maven/org.apache.commons/commons-release-plugin@1.9.3",
"builderDependencies": [],
"version": {}
},
diff --git a/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java b/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
index 89df57072..a079a85a4 100644
--- a/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
+++ b/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
@@ -55,6 +55,7 @@
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugins.gpg.AbstractGpgSigner;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
@@ -76,6 +77,7 @@ public class BuildAttestationMojoTest {
private static JsonNode expectedStatement;
@TempDir
private static Path localRepositoryPath;
+ private static PluginDescriptor pluginDescriptor;
private static RepositorySystemSession repoSession;
private static void assertStatementContent(final JsonNode statement) {
@@ -111,6 +113,7 @@ private static void configureBuildAttestationMojo(final BuildAttestationMojo moj
mojo.setScmConnectionUrl("scm:git:https://github.com/apache/commons-text.git");
mojo.setMavenHome(new File(System.getProperty("maven.home", ".")));
mojo.setAlgorithmNames("SHA-512,SHA-256,SHA-1,MD5");
+ mojo.setPluginDescriptor(pluginDescriptor);
mojo.setSignAttestation(signAttestation);
mojo.setSigner(createMockSigner());
}
@@ -209,6 +212,14 @@ static void setup() throws Exception {
try (InputStream in = BuildAttestationMojoTest.class.getResourceAsStream("/attestations/commons-text-1.4.intoto.json")) {
expectedStatement = OBJECT_MAPPER.readTree(in);
}
+ final Properties pluginProps = new Properties();
+ try (InputStream in = BuildAttestationMojoTest.class.getResourceAsStream("/plugin.properties")) {
+ pluginProps.load(in);
+ }
+ pluginDescriptor = new PluginDescriptor();
+ pluginDescriptor.setGroupId(pluginProps.getProperty("plugin.groupId"));
+ pluginDescriptor.setArtifactId(pluginProps.getProperty("plugin.artifactId"));
+ pluginDescriptor.setVersion(pluginProps.getProperty("plugin.version"));
}
@Test
diff --git a/src/test/resources/attestations/commons-text-1.4.intoto.json b/src/test/resources/attestations/commons-text-1.4.intoto.json
index 9063f5338..ca76d35a2 100644
--- a/src/test/resources/attestations/commons-text-1.4.intoto.json
+++ b/src/test/resources/attestations/commons-text-1.4.intoto.json
@@ -175,7 +175,7 @@
},
"runDetails": {
"builder": {
- "id": "https://commons.apache.org/builds/0.1.0",
+ "id": "pkg:maven/${project.groupId}/${project.artifactId}@${project.version}",
"builderDependencies": [],
"version": {}
},
diff --git a/src/test/resources/plugin.properties b/src/test/resources/plugin.properties
new file mode 100644
index 000000000..7e61707a5
--- /dev/null
+++ b/src/test/resources/plugin.properties
@@ -0,0 +1,18 @@
+# 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
+#
+# https://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.
+
+plugin.groupId=${project.groupId}
+plugin.artifactId=${project.artifactId}
+plugin.version=${project.version}
From 9d8dc45cf01fcfdb0660de89725d3b7df310ad75 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Mon, 20 Apr 2026 15:43:57 +0200
Subject: [PATCH 34/51] fix: latin abbreviations
---
src/site/markdown/slsa/v0.1.0.md | 30 +++++++++++++++---------------
1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/src/site/markdown/slsa/v0.1.0.md b/src/site/markdown/slsa/v0.1.0.md
index cff196464..2914b3141 100644
--- a/src/site/markdown/slsa/v0.1.0.md
+++ b/src/site/markdown/slsa/v0.1.0.md
@@ -49,14 +49,14 @@ The provenance is recorded by the `build-attestation` goal of the
External parameters capture everything supplied by the release manager at invocation time.
All parameters are captured from the running Maven session.
-| Parameter | Type | Description |
-|-------------------------|----------|-------------------------------------------------------------------------|
-| `maven.goals` | string[] | The list of Maven goals passed on the command line (e.g. `["deploy"]`). |
-| `maven.profiles` | string[] | The list of active profiles passed via `-P` (e.g. `["release"]`). |
-| `maven.user.properties` | object | User-defined properties passed via `-D` flags. |
-| `maven.cmdline` | string | The reconstructed Maven command line. |
-| `jvm.args` | string[] | JVM input arguments. |
-| `env` | object | A filtered subset of environment variables: `TZ` and locale variables. |
+| Parameter | Type | Description |
+|-------------------------|----------|--------------------------------------------------------------------------------|
+| `maven.goals` | string[] | The list of Maven goals passed on the command line (for example `["deploy"]`). |
+| `maven.profiles` | string[] | The list of active profiles passed via `-P` (for example `["release"]`). |
+| `maven.user.properties` | object | User-defined properties passed via `-D` flags. |
+| `maven.cmdline` | string | The reconstructed Maven command line. |
+| `jvm.args` | string[] | JVM input arguments. |
+| `env` | object | A filtered subset of environment variables: `TZ` and locale variables. |
### Internal parameters
@@ -124,18 +124,18 @@ format.
One entry per resolved Maven dependency (compile + runtime scope), as declared in the project's POM.
These are appended after the build tool entries above.
-| Field | Value |
-|-----------------|-----------------------------------------------------|
-| `name` | Artifact filename, e.g. `commons-lang3-3.14.0.jar`. |
-| `uri` | Package URL. |
-| `digest.sha256` | SHA-256 hex digest of the artifact file on disk. |
+| Field | Value |
+|-----------------|------------------------------------------------------------|
+| `name` | Artifact filename, for example `commons-lang3-3.14.0.jar`. |
+| `uri` | Package URL. |
+| `digest.sha256` | SHA-256 hex digest of the artifact file on disk. |
## Run details
### Builder
The `builder.id` is the [Package URL](https://github.com/package-url/purl-spec) of the
-`commons-release-plugin` release that produced the attestation, e.g.
+`commons-release-plugin` release that produced the attestation, for example
`pkg:maven/org.apache.commons/commons-release-plugin@1.9.3`. It identifies the trust boundary of
the "build platform": the exact plugin code that emitted the provenance. Verifiers can resolve the
PURL to the signed artifact on Maven Central to inspect the builder.
@@ -147,7 +147,7 @@ lists every artifact produces by the build. It has the following properties
| Field | Value |
|----------|-------------------------------------------------------------------------------------------------------------------------------------|
-| `name` | Artifact filename in the default Maven repository layout, e.g. `commons-text-1.4-sources.jar`. |
+| `name` | Artifact filename in the default Maven repository layout, for example `commons-text-1.4-sources.jar`. |
| `uri` | [Package URL](https://github.com/package-url/purl-spec) identifying the artifact in the `maven` namespace. |
| `digest` | Map of [in-toto digest names](https://github.com/in-toto/attestation/blob/main/spec/v1/digest_set.md) to hex-encoded digest values. |
From 8118ebbaa2a11f9d98319bba8a469a2c11b61819 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Mon, 20 Apr 2026 15:50:38 +0200
Subject: [PATCH 35/51] fix: clarify usage of JSON
---
.../commons/release/plugin/mojos/BuildAttestationMojo.java | 7 ++++++-
src/site/markdown/slsa/v0.1.0.md | 5 +++--
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
index 54f20682c..03b7c9687 100644
--- a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
+++ b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
@@ -81,7 +81,12 @@ public class BuildAttestationMojo extends AbstractMojo {
private static final String ATTESTATION_EXTENSION = "intoto.jsonl";
/**
- * Shared Jackson object mapper for serializing attestation statements.
+ * Shared Jackson object mapper used to serialize SLSA statements and DSSE envelopes to JSON.
+ *
+ * Each attestation is written as a single JSON value followed by a line separator, matching
+ * the JSON Lines format used by {@code .intoto.jsonl}
+ * files. The mapper is configured not to auto-close the output stream so the caller can append
+ * the trailing newline, and to emit ISO-8601 timestamps rather than numeric ones.
*/
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
diff --git a/src/site/markdown/slsa/v0.1.0.md b/src/site/markdown/slsa/v0.1.0.md
index 2914b3141..abc0f9f24 100644
--- a/src/site/markdown/slsa/v0.1.0.md
+++ b/src/site/markdown/slsa/v0.1.0.md
@@ -20,8 +20,9 @@ separately attested.
The OpenPGP keys used to sign past and present artifacts are available at: https://downloads.apache.org/commons/KEYS
-Attestations are published to Maven Central under the released artifact's coordinates, distinguished by an
-`intoto.jsonl` type:
+Attestations are serialized in the [JSON Lines](https://jsonlines.org/) format used across the
+in-toto ecosystem, one JSON value per line, and published to Maven Central under the released
+artifact's coordinates with an `intoto.jsonl` type:
```xml
From 33b1c2e4217a8e6f5d0a63bdf9e43bd1848b36e7 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Tue, 21 Apr 2026 16:00:44 +0200
Subject: [PATCH 36/51] fix: remove unused method
---
.../release/plugin/internal/DsseUtils.java | 22 -------------------
1 file changed, 22 deletions(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/DsseUtils.java b/src/main/java/org/apache/commons/release/plugin/internal/DsseUtils.java
index 3ad334ff2..76ad5314b 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/DsseUtils.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/DsseUtils.java
@@ -24,12 +24,9 @@
import java.nio.file.Path;
import java.util.Locale;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.IOUtils;
import org.apache.commons.release.plugin.slsa.v1_2.DsseEnvelope;
-import org.apache.commons.release.plugin.slsa.v1_2.Statement;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
@@ -123,25 +120,6 @@ public static byte[] signFile(final AbstractGpgSigner signer, final Path path) t
return signatureBytes;
}
- /**
- * Serializes {@code statement} to JSON using the DSSE Pre-Authentication Encoding (PAE).
- *
- * PAE(type, body) = "DSSEv1" + SP + LEN(type) + SP + type + SP + LEN(body) + SP + body
- *
- * @param statement the attestation statement to encode
- * @param objectMapper the Jackson mapper used to serialize {@code statement}
- * @param buildDirectory directory in which the PAE file is created
- * @return path to the written PAE file
- * @throws MojoExecutionException if serialization or I/O fails
- */
- public static Path writePaeFile(final Statement statement, final ObjectMapper objectMapper, final Path buildDirectory) throws MojoExecutionException {
- try {
- return writePaeFile(objectMapper.writeValueAsBytes(statement), buildDirectory);
- } catch (final JsonProcessingException e) {
- throw new MojoExecutionException("Failed to serialize attestation statement", e);
- }
- }
-
/**
* Writes serialized JSON to a file using the DSSE Pre-Authentication Encoding (PAE).
*
From 286e0218ba79740bf0307f15cf14e10d391cc783 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Tue, 21 Apr 2026 16:01:00 +0200
Subject: [PATCH 37/51] fix: clean-up after signature
---
.../release/plugin/mojos/BuildAttestationMojo.java | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
index 03b7c9687..6029b7230 100644
--- a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
+++ b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
@@ -501,8 +501,14 @@ private void signAndWriteStatement(final Statement statement, final Path outputP
throw new MojoExecutionException("Failed to serialize attestation statement", e);
}
final AbstractGpgSigner signer = getSigner();
- final Path paeFile = DsseUtils.writePaeFile(statementBytes, outputPath);
- final byte[] sigBytes = DsseUtils.signFile(signer, paeFile);
+ final byte[] sigBytes;
+ try {
+ final Path paeFile = DsseUtils.writePaeFile(statementBytes, outputPath);
+ sigBytes = DsseUtils.signFile(signer, paeFile);
+ Files.deleteIfExists(paeFile);
+ } catch (final IOException e) {
+ throw new MojoExecutionException("Failed to sign attestation statement", e);
+ }
final Signature sig = new Signature()
.setKeyid(DsseUtils.getKeyId(sigBytes))
From 9b008bc1777e6aad68bfdc662682c70cd1f7dd32 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Tue, 21 Apr 2026 18:51:32 +0200
Subject: [PATCH 38/51] fix: add tests for `GitUtils`
---
pom.xml | 22 +++-
.../release/plugin/internal/GitUtils.java | 21 +++-
.../release/plugin/internal/GitFixture.java | 99 ++++++++++++++++
.../release/plugin/internal/GitUtilsTest.java | 106 ++++++++++++++++++
4 files changed, 241 insertions(+), 7 deletions(-)
create mode 100644 src/test/java/org/apache/commons/release/plugin/internal/GitFixture.java
create mode 100644 src/test/java/org/apache/commons/release/plugin/internal/GitUtilsTest.java
diff --git a/pom.xml b/pom.xml
index 5b914750a..d4bf52085 100644
--- a/pom.xml
+++ b/pom.xml
@@ -268,6 +268,12 @@
4.11.0
test
+
+ org.apache.commons
+ commons-exec
+ 1.6.0
+ test
+
org.apache.maven
@@ -518,6 +524,20 @@
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+
+
+ default-testResources
+
+ false
+
+
+
+
@@ -631,7 +651,7 @@
com.github.spotbugs
spotbugs-maven-plugin
-
+
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java b/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java
index ecaa19f35..0b529af40 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java
@@ -20,7 +20,6 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.security.MessageDigest;
import org.apache.commons.codec.binary.Hex;
@@ -32,7 +31,15 @@
*/
public final class GitUtils {
- /** The SCM URI prefix for Git repositories. */
+ /**
+ * Prefix used in a {@code gitfile} to point to the Git directory.
+ *
+ * See gitrepository-layout.
+ */
+ private static final String GITDIR_PREFIX = "gitdir: ";
+ /**
+ * The SCM URI prefix for Git repositories.
+ */
private static final String SCM_GIT_PREFIX = "scm:git:";
/**
@@ -52,8 +59,8 @@ private static Path findGitDir(final Path path) throws IOException {
if (Files.isRegularFile(candidate)) {
// git worktree: .git is a file containing "gitdir: /path/to/real/.git"
final String content = new String(Files.readAllBytes(candidate), StandardCharsets.UTF_8).trim();
- if (content.startsWith("gitdir: ")) {
- return Paths.get(content.substring("gitdir: ".length()));
+ if (content.startsWith(GITDIR_PREFIX)) {
+ return current.resolve(content.substring(GITDIR_PREFIX.length()));
}
}
current = current.getParent();
@@ -98,7 +105,7 @@ public static String gitTree(final Path path) throws IOException {
/**
* Converts an SCM URI to a download URI suffixed with the current branch name.
*
- * @param scmUri A Maven SCM URI starting with {@code scm:git}.
+ * @param scmUri A Maven SCM URI starting with {@code scm:git}.
* @param repositoryPath A path inside the Git repository.
* @return A download URI of the form {@code git+@}.
* @throws IOException If the current branch cannot be determined.
@@ -111,7 +118,9 @@ public static String scmToDownloadUri(final String scmUri, final Path repository
return "git+" + scmUri.substring(SCM_GIT_PREFIX.length()) + "@" + currentBranch;
}
- /** No instances. */
+ /**
+ * No instances.
+ */
private GitUtils() {
// no instantiation
}
diff --git a/src/test/java/org/apache/commons/release/plugin/internal/GitFixture.java b/src/test/java/org/apache/commons/release/plugin/internal/GitFixture.java
new file mode 100644
index 000000000..8e534a8b0
--- /dev/null
+++ b/src/test/java/org/apache/commons/release/plugin/internal/GitFixture.java
@@ -0,0 +1,99 @@
+/*
+ * 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
+ *
+ * https://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.commons.release.plugin.internal;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+
+import org.apache.commons.exec.CommandLine;
+import org.apache.commons.exec.DefaultExecutor;
+import org.apache.commons.exec.Executor;
+import org.apache.commons.exec.PumpStreamHandler;
+import org.apache.commons.exec.environment.EnvironmentUtils;
+import org.apache.commons.io.output.NullOutputStream;
+
+/**
+ * Builds real Git fixtures on disk by invoking the {@code git} CLI via Commons Exec
+ */
+final class GitFixture {
+
+ static final String REPO_BRANCH = "foo";
+ static final String WORKTREE_BRANCH = "bar";
+ static final String SUBDIR = "subdir";
+ /**
+ * SHA-1 of the single commit produced by {@link #createRepoAndWorktree}; deterministic thanks to {@link #ENV}.
+ */
+ static final String INITIAL_COMMIT_SHA = "a2782b3461d2ed2a81193da1139f65bf9d2befc2";
+
+ /**
+ * Process environment with fixed author/committer dates so commit SHAs are stable across runs.
+ */
+ private static final Map ENV;
+
+ static {
+ try {
+ final Map env = EnvironmentUtils.getProcEnvironment();
+ env.put("GIT_AUTHOR_DATE", "2026-01-01T00:00:00Z");
+ env.put("GIT_COMMITTER_DATE", "2026-01-01T00:00:00Z");
+ ENV = env;
+ } catch (final IOException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ /**
+ * Creates a Git repo for testing.
+ *
+ * @param repo Path to the repository to create
+ * @param worktree Path to a separate worktree to create
+ */
+ static void createRepoAndWorktree(final Path repo, final Path worktree) throws IOException {
+ final Path subdir = repo.resolve(SUBDIR);
+ Files.createDirectories(subdir);
+ git(repo, "init", "-q", ".");
+ // Put HEAD on 'foo' before the first commit (portable to older git without --initial-branch).
+ git(repo, "symbolic-ref", "HEAD", "refs/heads/" + REPO_BRANCH);
+ git(repo, "config", "user.email", "test@example.invalid");
+ git(repo, "config", "user.name", "Test");
+ git(repo, "config", "commit.gpgsign", "false");
+ final Path readme = subdir.resolve("README");
+ Files.write(readme, "hi\n".getBytes(StandardCharsets.UTF_8));
+ git(repo, "add", repo.relativize(readme).toString());
+ git(repo, "commit", "-q", "-m", "init");
+ git(repo, "branch", WORKTREE_BRANCH);
+ git(repo, "worktree", "add", "-q", repo.relativize(worktree).toString(), "bar");
+ }
+
+ /**
+ * Runs {@code git} with the given args; stdout is discarded, stderr is forwarded to {@link System#err}.
+ */
+ static void git(final Path workingDir, final String... args) throws IOException {
+ final CommandLine cmd = new CommandLine("git");
+ for (final String a : args) {
+ cmd.addArgument(a, false);
+ }
+ final Executor exec = DefaultExecutor.builder().setWorkingDirectory(workingDir.toFile()).get();
+ exec.setStreamHandler(new PumpStreamHandler(NullOutputStream.INSTANCE, System.err));
+ exec.execute(cmd, ENV);
+ }
+
+ private GitFixture() {
+ }
+}
diff --git a/src/test/java/org/apache/commons/release/plugin/internal/GitUtilsTest.java b/src/test/java/org/apache/commons/release/plugin/internal/GitUtilsTest.java
new file mode 100644
index 000000000..caed45c4c
--- /dev/null
+++ b/src/test/java/org/apache/commons/release/plugin/internal/GitUtilsTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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
+ *
+ * https://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.commons.release.plugin.internal;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class GitUtilsTest {
+
+ private static Path repo;
+ @TempDir
+ static Path tempDir;
+ private static Path worktree;
+
+ @BeforeAll
+ static void setUp() throws IOException {
+ repo = tempDir.resolve("repo");
+ worktree = tempDir.resolve("worktree");
+ GitFixture.createRepoAndWorktree(repo, worktree);
+ }
+
+ static Stream testGetCurrentBranch() {
+ return Stream.of(Arguments.of(repo, GitFixture.REPO_BRANCH), Arguments.of(repo.resolve(GitFixture.SUBDIR), GitFixture.REPO_BRANCH),
+ Arguments.of(worktree, GitFixture.WORKTREE_BRANCH), Arguments.of(worktree.resolve(GitFixture.SUBDIR), GitFixture.WORKTREE_BRANCH));
+ }
+
+ static Stream testScmToDownloadUri() {
+ return Stream.of(
+ Arguments.of("scm:git:https://gitbox.apache.org/repos/asf/commons-release-plugin.git",
+ repo,
+ "git+https://gitbox.apache.org/repos/asf/commons-release-plugin.git@" + GitFixture.REPO_BRANCH),
+ Arguments.of("scm:git:git@github.com:apache/commons-release-plugin.git",
+ repo,
+ "git+git@github.com:apache/commons-release-plugin.git@" + GitFixture.REPO_BRANCH),
+ Arguments.of("scm:git:ssh://git@github.com/apache/commons-release-plugin.git",
+ worktree,
+ "git+ssh://git@github.com/apache/commons-release-plugin.git@" + GitFixture.WORKTREE_BRANCH));
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testGetCurrentBranch(final Path repo, final String expectedBranchName) throws Exception {
+ assertEquals(expectedBranchName, GitUtils.getCurrentBranch(repo));
+ }
+
+ @Test
+ void testGetCurrentBranchDetachedHead() throws IOException {
+ // Build a fresh repo so we don't mutate HEAD shared with the parameterized tests.
+ final Path detachedRepo = tempDir.resolve("detached-repo");
+ final Path detachedWorktree = tempDir.resolve("detached-worktree");
+ GitFixture.createRepoAndWorktree(detachedRepo, detachedWorktree);
+ GitFixture.git(detachedRepo, "checkout", "-q", "--detach", "HEAD");
+ assertEquals(GitFixture.INITIAL_COMMIT_SHA, GitUtils.getCurrentBranch(detachedRepo));
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testScmToDownloadUri(final String scmUri, final Path repositoryPath, final String expectedDownloadUri) throws IOException {
+ assertEquals(expectedDownloadUri, GitUtils.scmToDownloadUri(scmUri, repositoryPath));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "scm:svn:https://svn.apache.org/repos/asf/commons-release-plugin",
+ "scm:hg:https://example.com/repo",
+ "https://github.com/apache/commons-release-plugin.git",
+ "git:https://github.com/apache/commons-release-plugin.git",
+ ""
+ })
+ void testScmToDownloadUriRejectsNonGit(final String scmUri) {
+ assertThrows(IllegalArgumentException.class, () -> GitUtils.scmToDownloadUri(scmUri, repo));
+ }
+
+ @Test
+ void throwsWhenNoGitDirectoryFound() throws IOException {
+ final Path plain = Files.createDirectories(tempDir.resolve("plain"));
+ assertThrows(IOException.class, () -> GitUtils.getCurrentBranch(plain));
+ }
+}
From a654a983d7dad51401b518af4e314f6455503434 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Tue, 21 Apr 2026 19:24:22 +0200
Subject: [PATCH 39/51] fix: filter possibly sensitive use properties
---
.../plugin/internal/BuildDefinitions.java | 49 ++++++++++++++++++-
.../plugin/internal/BuildDefinitionsTest.java | 18 +++++--
2 files changed, 60 insertions(+), 7 deletions(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java b/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
index 537e3ad4d..ca46ec52d 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
@@ -21,9 +21,11 @@
import java.lang.management.ManagementFactory;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
@@ -37,8 +39,18 @@
*/
public final class BuildDefinitions {
+ /**
+ * User-property names containing any of these substrings (case-insensitive) are omitted from attestations.
+ *
+ * The Maven GPG plugin discourages passing credentials on the command line, but a stray {@code -Dgpg.passphrase=...} must not be captured in the
+ * attestation if someone does it anyway.
+ */
+ private static final List SENSITIVE_KEYWORDS =
+ Arrays.asList("secret", "password", "passphrase", "token", "credential");
+
/**
* Reconstructs the Maven command line string from the given execution request.
+ * User properties whose name matches {@link #SENSITIVE_KEYWORDS} are omitted.
*
* @param request the Maven execution request
* @return a string representation of the Maven command line
@@ -49,10 +61,26 @@ static String commandLine(final MavenExecutionRequest request) {
if (!profiles.isEmpty()) {
args.add("-P" + profiles);
}
- request.getUserProperties().forEach((key, value) -> args.add("-D" + key + "=" + value));
+ request.getUserProperties().forEach((key, value) -> {
+ final String k = key.toString();
+ if (isNotSensitive(k)) {
+ args.add("-D" + k + "=" + value);
+ }
+ });
return String.join(" ", args);
}
+ /**
+ * Checks if a property key is not sensitive.
+ *
+ * @param property A property key
+ * @return {@code true} if the property is not considered sensitive
+ */
+ private static boolean isNotSensitive(final String property) {
+ final String lower = property.toLowerCase(Locale.ROOT);
+ return SENSITIVE_KEYWORDS.stream().noneMatch(lower::contains);
+ }
+
/**
* Returns a map of external build parameters captured from the current JVM and Maven session.
*
@@ -65,7 +93,7 @@ public static Map externalParameters(final MavenSession session)
final MavenExecutionRequest request = session.getRequest();
params.put("maven.goals", request.getGoals());
params.put("maven.profiles", request.getActiveProfiles());
- params.put("maven.user.properties", request.getUserProperties());
+ params.put("maven.user.properties", getUserProperties(request));
params.put("maven.cmdline", commandLine(request));
final Map env = new HashMap<>();
params.put("env", env);
@@ -78,6 +106,23 @@ public static Map externalParameters(final MavenSession session)
return params;
}
+ /**
+ * Returns a filtered map of user properties.
+ *
+ * @param request A Maven request
+ * @return A map of user properties.
+ */
+ private static TreeMap getUserProperties(final MavenExecutionRequest request) {
+ final TreeMap properties = new TreeMap<>();
+ request.getUserProperties().forEach((k, value) -> {
+ final String key = k.toString();
+ if (isNotSensitive(key)) {
+ properties.put(key, value.toString());
+ }
+ });
+ return properties;
+ }
+
/**
* Creates a {@link ResourceDescriptor} for the JDK used during the build.
*
diff --git a/src/test/java/org/apache/commons/release/plugin/internal/BuildDefinitionsTest.java b/src/test/java/org/apache/commons/release/plugin/internal/BuildDefinitionsTest.java
index 6c7a4d7a0..dac28a51c 100644
--- a/src/test/java/org/apache/commons/release/plugin/internal/BuildDefinitionsTest.java
+++ b/src/test/java/org/apache/commons/release/plugin/internal/BuildDefinitionsTest.java
@@ -40,15 +40,23 @@ static Stream commandLineArguments() {
Arguments.of("multiple goals", asList("clean", "verify"), emptyList(), new Properties(), "clean verify"),
Arguments.of("single profile", singletonList("verify"), singletonList("release"), new Properties(), "verify -Prelease"),
Arguments.of("multiple profiles", singletonList("verify"), asList("release", "sign"), new Properties(), "verify -Prelease,sign"),
- Arguments.of("user property", singletonList("verify"), emptyList(), singletonProperties("foo", "bar"), "verify -Dfoo=bar"),
- Arguments.of("goals, profile and property", singletonList("verify"), singletonList("release"), singletonProperties("foo", "bar"),
- "verify -Prelease -Dfoo=bar")
+ Arguments.of("user property", singletonList("verify"), emptyList(), toProperties("foo", "bar"), "verify -Dfoo=bar"),
+ Arguments.of("goals, profile and property", singletonList("verify"), singletonList("release"), toProperties("foo", "bar"),
+ "verify -Prelease -Dfoo=bar"),
+ Arguments.of("redacts gpg.passphrase", singletonList("verify"), emptyList(), toProperties("gpg.passphrase", "s3cr3t"), "verify"),
+ Arguments.of("redacts passphrase case-insensitively", singletonList("verify"), emptyList(), toProperties("GPG_PASSPHRASE", "s3cr3t"), "verify"),
+ Arguments.of("redacts any *password*", singletonList("verify"), emptyList(), toProperties("my.db.password", "hunter2"), "verify"),
+ Arguments.of("redacts *token*", singletonList("verify"), emptyList(), toProperties("github.token", "ghp_xxx"), "verify"),
+ Arguments.of("keeps safe property, drops sensitive one", singletonList("verify"), emptyList(),
+ toProperties("foo", "bar", "gpg.passphrase", "s3cr3t"), "verify -Dfoo=bar")
);
}
- private static Properties singletonProperties(final String key, final String value) {
+ private static Properties toProperties(final String... keysAndValues) {
final Properties p = new Properties();
- p.setProperty(key, value);
+ for (int i = 0; i < keysAndValues.length; i += 2) {
+ p.setProperty(keysAndValues[i], keysAndValues[i + 1]);
+ }
return p;
}
From 21ad6733e7554ccc953686a6dbf5673f7e8b98d9 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Tue, 21 Apr 2026 19:28:14 +0200
Subject: [PATCH 40/51] fix: lower-case key id
---
.../org/apache/commons/release/plugin/internal/DsseUtils.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/DsseUtils.java b/src/main/java/org/apache/commons/release/plugin/internal/DsseUtils.java
index 76ad5314b..dd7063112 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/DsseUtils.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/DsseUtils.java
@@ -88,7 +88,7 @@ public static String getKeyId(final byte[] sigBytes) throws MojoExecutionExcepti
return Hex.encodeHexString(fp.getFingerprint());
}
}
- return Long.toHexString(sig.getKeyID()).toUpperCase(Locale.ROOT);
+ return Long.toHexString(sig.getKeyID()).toLowerCase(Locale.ROOT);
} catch (final IOException e) {
throw new MojoExecutionException("Failed to extract key ID from signature", e);
}
From 476ac4f510f4802d795d65403873b398fa00bed7 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Tue, 21 Apr 2026 19:46:02 +0200
Subject: [PATCH 41/51] fix: `_type` property of attestation
---
.../release/plugin/slsa/v1_2/Statement.java | 11 +++++++-
src/site/markdown/slsa/v0.1.0.md | 1 +
.../mojos/BuildAttestationMojoTest.java | 26 +++++++++----------
.../attestations/commons-text-1.4.intoto.json | 1 +
4 files changed, 24 insertions(+), 15 deletions(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Statement.java b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Statement.java
index b44dfc03c..1d779c63d 100644
--- a/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Statement.java
+++ b/src/main/java/org/apache/commons/release/plugin/slsa/v1_2/Statement.java
@@ -29,7 +29,6 @@
public class Statement {
/** The in-toto statement schema URI. */
- @JsonProperty("_type")
public static final String TYPE = "https://in-toto.io/Statement/v1";
/** The provenance predicate. */
@JsonProperty("predicate")
@@ -55,6 +54,16 @@ public boolean equals(Object o) {
statement.predicate);
}
+ /**
+ * Type of JSON object.
+ *
+ * @return Always {@value TYPE}
+ */
+ @JsonProperty("_type")
+ public String getType() {
+ return TYPE;
+ }
+
/**
* Gets the provenance predicate.
*
diff --git a/src/site/markdown/slsa/v0.1.0.md b/src/site/markdown/slsa/v0.1.0.md
index abc0f9f24..5e37371c7 100644
--- a/src/site/markdown/slsa/v0.1.0.md
+++ b/src/site/markdown/slsa/v0.1.0.md
@@ -169,6 +169,7 @@ contains that envelope.
```json5
{
+ "_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "commons-text-1.4.jar",
diff --git a/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java b/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
index a079a85a4..d266e67b5 100644
--- a/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
+++ b/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
@@ -20,7 +20,6 @@
import static net.javacrumbs.jsonunit.JsonAssert.assertJsonNodeAbsent;
import static net.javacrumbs.jsonunit.JsonAssert.assertJsonNodePresent;
import static net.javacrumbs.jsonunit.JsonAssert.assertJsonPartEquals;
-import static net.javacrumbs.jsonunit.JsonAssert.whenIgnoringPaths;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.File;
@@ -81,30 +80,29 @@ public class BuildAttestationMojoTest {
private static RepositorySystemSession repoSession;
private static void assertStatementContent(final JsonNode statement) {
- assertJsonEquals(expectedStatement.get("subject"), statement.get("subject"),
- JsonAssert.when(Option.IGNORING_ARRAY_ORDER));
- assertJsonEquals(expectedStatement.get("predicateType"), statement.get("predicateType"));
- assertJsonEquals(expectedStatement.at("/predicate/buildDefinition/buildType"),
- statement.at("/predicate/buildDefinition/buildType"));
- assertJsonEquals(expectedStatement.at("/predicate/buildDefinition/externalParameters"),
- statement.at("/predicate/buildDefinition/externalParameters"),
- JsonAssert.when(Option.IGNORING_VALUES).whenIgnoringPaths("jvm.args", "env"));
- assertJsonEquals(expectedStatement.at("/predicate/buildDefinition/internalParameters"),
- statement.at("/predicate/buildDefinition/internalParameters"));
+ // Check all fields except `predicate`
+ assertJsonEquals(expectedStatement, statement,
+ JsonAssert.whenIgnoringPaths("predicate"));
+
+ // Build definition except:
+ // - some external parameters we don't control
+ // - the resolved dependencies for which we check the structure, but ignore the values
+ assertJsonEquals(expectedStatement.at("/predicate/buildDefinition"), statement.at("/predicate/buildDefinition"),
+ JsonAssert.whenIgnoringPaths("externalParameters.jvm.args", "externalParameters.env", "resolvedDependencies",
+ "runDetails.metadata.finishedOn"));
+
// `[0].annotations` holds JVM system properties;
// Not all properties are available on all JDKs, so they are either null or strings, which json-unit treats as a structural mismatch.
// We will check them below
assertJsonEquals(expectedStatement.at("/predicate/buildDefinition/resolvedDependencies"),
statement.at("/predicate/buildDefinition/resolvedDependencies"),
JsonAssert.when(Option.IGNORING_VALUES).whenIgnoringPaths("[0].annotations"));
+
final Set expectedJdkFields = fieldNames(
expectedStatement.at("/predicate/buildDefinition/resolvedDependencies/0/annotations"));
final Set actualJdkFields = fieldNames(
statement.at("/predicate/buildDefinition/resolvedDependencies/0/annotations"));
assertEquals(expectedJdkFields, actualJdkFields);
- assertJsonEquals(expectedStatement.at("/predicate/runDetails"),
- statement.at("/predicate/runDetails"),
- whenIgnoringPaths("metadata.finishedOn"));
}
private static void configureBuildAttestationMojo(final BuildAttestationMojo mojo, final boolean signAttestation) {
diff --git a/src/test/resources/attestations/commons-text-1.4.intoto.json b/src/test/resources/attestations/commons-text-1.4.intoto.json
index ca76d35a2..1ddc4f2ed 100644
--- a/src/test/resources/attestations/commons-text-1.4.intoto.json
+++ b/src/test/resources/attestations/commons-text-1.4.intoto.json
@@ -1,4 +1,5 @@
{
+ "_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "commons-text-1.4.jar",
From b7fbbf6a4c0e4c564c0441fd6ddd917cc7824aff Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Tue, 21 Apr 2026 19:52:03 +0200
Subject: [PATCH 42/51] Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/site/markdown/slsa/v0.1.0.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/site/markdown/slsa/v0.1.0.md b/src/site/markdown/slsa/v0.1.0.md
index 5e37371c7..adb6f0668 100644
--- a/src/site/markdown/slsa/v0.1.0.md
+++ b/src/site/markdown/slsa/v0.1.0.md
@@ -144,7 +144,7 @@ PURL to the signed artifact on Maven Central to inspect the builder.
## Subjects
The [`subject`](https://github.com/in-toto/attestation/blob/main/spec/v1/statement.md#fields) array
-lists every artifact produces by the build. It has the following properties
+lists every artifact produced by the build. It has the following properties
| Field | Value |
|----------|-------------------------------------------------------------------------------------------------------------------------------------|
From 1f9bb3016d4a68f4dcdedcf7e63ec6222aaff8ac Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Tue, 21 Apr 2026 19:52:38 +0200
Subject: [PATCH 43/51] Apply suggestions from code review (2)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.../commons/release/plugin/internal/BuildDefinitions.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java b/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
index ca46ec52d..5935492bb 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/BuildDefinitions.java
@@ -150,7 +150,7 @@ public static ResourceDescriptor jvm(final Path javaHome) throws IOException {
* Creates a {@link ResourceDescriptor} for the Maven installation used during the build.
*
* {@code build.properties} resides in a JAR inside {@code ${maven.home}/lib/}, which is loaded by Maven's Core Classloader.
- * Plugin code runs in an isolated Plugin Classloader, which does see that resources. Therefore, we need to pass the classloader from a class from
+ * Plugin code runs in an isolated Plugin Classloader, which does not see those resources. Therefore, we need to pass the classloader from a class from
* Maven Core, such as {@link org.apache.maven.rtinfo.RuntimeInformation}.
*
* @param version Maven version string
From cfd3b97268da4c00b91b6dd002ea67fdd9295d7a Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Tue, 21 Apr 2026 20:36:01 +0200
Subject: [PATCH 44/51] fix: document attestation examples
---
src/test/resources/attestations/README.md | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
create mode 100644 src/test/resources/attestations/README.md
diff --git a/src/test/resources/attestations/README.md b/src/test/resources/attestations/README.md
new file mode 100644
index 000000000..abfedaff1
--- /dev/null
+++ b/src/test/resources/attestations/README.md
@@ -0,0 +1,23 @@
+
+# Attestation examples
+
+Golden files used as expected values by the tests.
+
+Real build attestations are single-line JSON Lines (`.intoto.jsonl`). The files here are pretty-printed JSON (`.intoto.json`) for readability.
+
+The tests compare structurally via `json-unit`, ignoring environment-dependent fields (JVM arguments, timestamps, `resolvedDependencies` digests), so formatting differences are not significant.
\ No newline at end of file
From 095d93fd4c060c9189d80bb1c860f04dbbddd207 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Tue, 21 Apr 2026 21:24:01 +0200
Subject: [PATCH 45/51] fix: add documentation of the `build-attestation` goal
---
.../plugin/mojos/BuildAttestationMojo.java | 20 +++-
src/site/markdown/build-attestation.md | 105 ++++++++++++++++++
src/site/xdoc/index.xml | 4 +
3 files changed, 127 insertions(+), 2 deletions(-)
create mode 100644 src/site/markdown/build-attestation.md
diff --git a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
index 6029b7230..4ebf0a921 100644
--- a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
+++ b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
@@ -70,9 +70,25 @@
import org.apache.maven.scm.repository.ScmRepository;
/**
- * This plugin generates an in-toto attestation for all the artifacts.
+ * Generates a SLSA v1.2 in-toto attestation covering all artifacts attached to the project.
+ *
+ * The goal binds to the {@code post-integration-test} phase so the following constraints are
+ * satisfied:
+ *
+ * - it runs after {@code package}, so every build artifact is already attached to the project;
+ * - it runs before {@code maven-gpg-plugin}'s {@code sign} goal (bound to {@code verify}), so
+ * the attestation file itself receives the detached {@code .asc} signature required by
+ * Maven Central;
+ * - it runs before {@code detach-distributions} (also bound to {@code verify}), so the
+ * distribution archives ({@code tar.gz}, {@code zip}) are covered by the attestation before
+ * they are removed from the list of artifacts to deploy.
+ *
+ *
+ * Binding to an earlier lifecycle phase than {@code verify} is needed because Maven 3 cannot
+ * order executions of different plugins within the same phase in a way that satisfies all three
+ * constraints at once.
*/
-@Mojo(name = "build-attestation", defaultPhase = LifecyclePhase.VERIFY, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
+@Mojo(name = "build-attestation", defaultPhase = LifecyclePhase.POST_INTEGRATION_TEST, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
public class BuildAttestationMojo extends AbstractMojo {
/**
diff --git a/src/site/markdown/build-attestation.md b/src/site/markdown/build-attestation.md
new file mode 100644
index 000000000..49b96a68c
--- /dev/null
+++ b/src/site/markdown/build-attestation.md
@@ -0,0 +1,105 @@
+
+# commons-release:build-attestation
+
+## Overview
+
+The `commons-release:build-attestation` goal produces a [SLSA](https://slsa.dev/) v1.2 provenance
+statement in the [in-toto](https://in-toto.io/) format. The attestation lists every artifact
+attached to the project as a subject, records the JDK, Maven installation, SCM source and
+resolved dependencies used during the build, and writes the result to
+`target/-.intoto.jsonl`. The envelope is signed with GPG by default and
+attached to the project so that it is deployed alongside the other artifacts.
+
+The structure of the `predicate.buildDefinition.buildType` field is documented at
+[SLSA build type v0.1.0](slsa/v0.1.0.html).
+
+## Phase ordering
+
+A Commons release relies on three goals running in a fixed order:
+
+1. `commons-release:build-attestation`, bound to `post-integration-test`. At this point every
+ build artifact, including the distribution archives, is already attached to the project.
+2. `maven-gpg-plugin:sign`, bound to `verify`. It signs every attached artifact with a detached
+ `.asc`, including the `.intoto.jsonl` produced in step 1. Maven Central requires this for
+ every uploaded file.
+3. `commons-release:detach-distributions`, bound to `verify`. It removes the `.tar.gz` and
+ `.zip` archives from the set of artifacts that will be uploaded to Nexus.
+
+Binding `build-attestation` to `post-integration-test` (rather than `verify`) puts it in an
+earlier lifecycle phase than the other two goals, so Maven 3 is guaranteed to run it first,
+regardless of the order in which plugins are declared in the POM. Within the `verify` phase,
+`sign` must run before `detach-distributions`; this is controlled by declaring
+`maven-gpg-plugin` before `commons-release-plugin` in the POM, since Maven executes plugins
+within a single phase in the order they appear.
+
+If the distribution archives should not be covered by the attestation, override the default
+phase binding and bind `build-attestation` to `verify` after `detach-distributions`.
+
+## Example configuration
+
+The snippet below wires the three goals in the recommended order.
+
+```xml
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+ org.apache.commons
+ commons-release-plugin
+
+
+
+ build-attestation
+
+ build-attestation
+
+
+
+ detach-distributions
+ verify
+
+ detach-distributions
+
+
+
+
+
+
+```
+
+See the [goal parameters](build-attestation-mojo.html) for the full list of configurable
+properties.
diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml
index ed9ce8dbc..36792674f 100644
--- a/src/site/xdoc/index.xml
+++ b/src/site/xdoc/index.xml
@@ -52,6 +52,10 @@
code readability):
+ -
+ commons-release:build-attestation generates a signed
+ SLSA v1.2 in-toto attestation covering all build artifacts and attaches it to the project.
+
-
commons-release:detach-distributions - Remove
tar.gz, tar.gz.asc, zip, and zip.asc
From 9f69d2e762efba20a24dd3057c7b174aecfb3241 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Tue, 21 Apr 2026 21:52:30 +0200
Subject: [PATCH 46/51] fix: remove dependency on ScmManager
---
pom.xml | 6 -
.../release/plugin/internal/GitUtils.java | 105 +++++++++++++++++-
.../plugin/mojos/BuildAttestationMojo.java | 83 +-------------
.../release/plugin/internal/GitUtilsTest.java | 33 ++++++
.../mojos/BuildAttestationMojoTest.java | 4 +-
5 files changed, 143 insertions(+), 88 deletions(-)
diff --git a/pom.xml b/pom.xml
index d4bf52085..368c9cfe0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -180,12 +180,6 @@
${maven-scm.version}
compile
-
- org.apache.maven.scm
- maven-scm-provider-gitexe
- ${maven-scm.version}
- runtime
-
org.apache.maven.scm
maven-scm-provider-svnexe
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java b/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java
index 0b529af40..3b7c7be91 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java
@@ -16,6 +16,7 @@
*/
package org.apache.commons.release.plugin.internal;
+import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -37,6 +38,14 @@ public final class GitUtils {
* See gitrepository-layout.
*/
private static final String GITDIR_PREFIX = "gitdir: ";
+ /**
+ * Maximum number of symbolic-ref hops before we give up (to avoid cycles).
+ */
+ private static final int MAX_REF_DEPTH = 5;
+ /**
+ * Prefix used in {@code HEAD} and ref files to indicate a symbolic reference.
+ */
+ private static final String REF_PREFIX = "ref: ";
/**
* The SCM URI prefix for Git repositories.
*/
@@ -79,14 +88,37 @@ private static Path findGitDir(final Path path) throws IOException {
*/
public static String getCurrentBranch(final Path repositoryPath) throws IOException {
final Path gitDir = findGitDir(repositoryPath);
- final String head = new String(Files.readAllBytes(gitDir.resolve("HEAD")), StandardCharsets.UTF_8).trim();
+ final String head = readHead(gitDir);
if (head.startsWith("ref: refs/heads/")) {
return head.substring("ref: refs/heads/".length());
}
- // detached HEAD — return the commit SHA
+ // Detached HEAD: the file contains the commit SHA.
return head;
}
+ /**
+ * Gets the commit SHA pointed to by {@code HEAD}.
+ *
+ * Handles loose refs under {@code /refs/...}, packed refs in {@code /packed-refs},
+ * symbolic indirection (a ref file that itself contains {@code ref: ...}), and detached HEAD.
+ *
+ * @param repositoryPath A path inside the Git repository.
+ * @return The hex-encoded commit SHA.
+ * @throws IOException If the {@code .git} directory cannot be found, the ref cannot be resolved,
+ * or the symbolic chain is deeper than {@value #MAX_REF_DEPTH}.
+ */
+ public static String getHeadCommit(final Path repositoryPath) throws IOException {
+ final Path gitDir = findGitDir(repositoryPath);
+ String value = readHead(gitDir);
+ for (int i = 0; i < MAX_REF_DEPTH; i++) {
+ if (!value.startsWith(REF_PREFIX)) {
+ return value;
+ }
+ value = resolveRef(gitDir, value.substring(REF_PREFIX.length()));
+ }
+ throw new IOException("Symbolic ref chain exceeds " + MAX_REF_DEPTH + " hops in: " + gitDir);
+ }
+
/**
* Returns the Git tree hash for the given directory.
*
@@ -102,6 +134,75 @@ public static String gitTree(final Path path) throws IOException {
return Hex.encodeHexString(GitIdentifiers.treeId(digest, path));
}
+ /**
+ * Reads and trims the {@code HEAD} file of the given Git directory.
+ *
+ * @param gitDir The {@code .git} directory.
+ * @return The trimmed contents of {@code /HEAD}.
+ * @throws IOException If the file cannot be read.
+ */
+ private static String readHead(final Path gitDir) throws IOException {
+ return new String(Files.readAllBytes(gitDir.resolve("HEAD")), StandardCharsets.UTF_8).trim();
+ }
+
+ /**
+ * Returns the directory that holds shared repository state (loose refs, {@code packed-refs}).
+ * In a linked worktree this is read from {@code /commondir}; otherwise it is
+ * {@code gitDir} itself.
+ *
+ * @param gitDir The {@code .git} directory.
+ * @return The shared-state directory.
+ * @throws IOException If {@code commondir} exists but cannot be read.
+ */
+ private static Path resolveCommonDir(final Path gitDir) throws IOException {
+ final Path commonDir = gitDir.resolve("commondir");
+ if (Files.isRegularFile(commonDir)) {
+ final String value = new String(Files.readAllBytes(commonDir), StandardCharsets.UTF_8).trim();
+ return gitDir.resolve(value).normalize();
+ }
+ return gitDir;
+ }
+
+ /**
+ * Resolves a single ref (e.g. {@code refs/heads/foo}) to its stored value.
+ *
+ * The return value is either a commit SHA or another {@code ref: ...} line, which the caller continues to resolve.
+ *
+ * In a linked worktree, loose and packed refs are stored in the "common dir" (usually the
+ * main repository's {@code .git}), which is pointed to by {@code /commondir}.
+ *
+ * @param gitDir The {@code .git} directory.
+ * @param refPath The ref path relative to the common dir (e.g. {@code refs/heads/main}).
+ * @return Either a commit SHA or another {@code ref: ...} line to be resolved by the caller.
+ * @throws IOException If the ref is not found as a loose file or in {@code packed-refs}.
+ */
+ private static String resolveRef(final Path gitDir, final String refPath) throws IOException {
+ final Path refsDir = resolveCommonDir(gitDir);
+ final Path refFile = refsDir.resolve(refPath);
+ if (Files.isRegularFile(refFile)) {
+ return new String(Files.readAllBytes(refFile), StandardCharsets.UTF_8).trim();
+ }
+ final Path packed = refsDir.resolve("packed-refs");
+ if (Files.isRegularFile(packed)) {
+ try (BufferedReader reader = Files.newBufferedReader(packed, StandardCharsets.UTF_8)) {
+ // packed-refs format: one ref per line as " ", with '#' header lines,
+ // blank lines, and "^" peeled-tag continuation lines that we skip.
+ // See https://git-scm.com/docs/gitrepository-layout
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.isEmpty() || line.charAt(0) == '#' || line.charAt(0) == '^') {
+ continue;
+ }
+ final int space = line.indexOf(' ');
+ if (space > 0 && refPath.equals(line.substring(space + 1))) {
+ return line.substring(0, space);
+ }
+ }
+ }
+ }
+ throw new IOException("Cannot resolve ref: " + refPath);
+ }
+
/**
* Converts an SCM URI to a download URI suffixed with the current branch name.
*
diff --git a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
index 4ebf0a921..7e5ef10fb 100644
--- a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
+++ b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
@@ -61,13 +61,6 @@
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.maven.rtinfo.RuntimeInformation;
-import org.apache.maven.scm.CommandParameters;
-import org.apache.maven.scm.ScmException;
-import org.apache.maven.scm.ScmFileSet;
-import org.apache.maven.scm.command.info.InfoItem;
-import org.apache.maven.scm.command.info.InfoScmResult;
-import org.apache.maven.scm.manager.ScmManager;
-import org.apache.maven.scm.repository.ScmRepository;
/**
* Generates a SLSA v1.2 in-toto attestation covering all artifacts attached to the project.
@@ -174,10 +167,6 @@ public class BuildAttestationMojo extends AbstractMojo {
*/
@Parameter(property = "commons.release.scmDirectory", defaultValue = "${basedir}")
private File scmDirectory;
- /**
- * SCM manager to detect the Git revision.
- */
- private final ScmManager scmManager;
/**
* The current Maven session, used to resolve plugin dependencies.
*/
@@ -215,16 +204,14 @@ public class BuildAttestationMojo extends AbstractMojo {
* Creates a new instance with the given dependencies.
*
* @param project A Maven project.
- * @param scmManager A SCM manager.
* @param runtimeInformation Maven runtime information.
* @param session A Maven session.
* @param mavenProjectHelper A helper to attach artifacts to the project.
*/
@Inject
- public BuildAttestationMojo(final MavenProject project, final ScmManager scmManager, final RuntimeInformation runtimeInformation,
+ public BuildAttestationMojo(final MavenProject project, final RuntimeInformation runtimeInformation,
final MavenSession session, final MavenProjectHelper mavenProjectHelper) {
this.project = project;
- this.scmManager = scmManager;
this.runtimeInformation = runtimeInformation;
this.session = session;
this.mavenProjectHelper = mavenProjectHelper;
@@ -323,13 +310,13 @@ private List getProjectDependencies() throws MojoExecutionEx
* Gets a resource descriptor for the current SCM source, including the URI and Git commit digest.
*
* @return A resource descriptor for the SCM source.
- * @throws IOException If the current branch cannot be determined.
- * @throws MojoExecutionException If the SCM revision cannot be retrieved.
+ * @throws IOException If the current branch or the HEAD commit cannot be determined.
*/
- private ResourceDescriptor getScmDescriptor() throws IOException, MojoExecutionException {
+ private ResourceDescriptor getScmDescriptor() throws IOException {
+ final Path scmPath = scmDirectory.toPath();
return new ResourceDescriptor()
- .setUri(GitUtils.scmToDownloadUri(scmConnectionUrl, scmDirectory.toPath()))
- .setDigest(Collections.singletonMap("gitCommit", getScmRevision()));
+ .setUri(GitUtils.scmToDownloadUri(scmConnectionUrl, scmPath))
+ .setDigest(Collections.singletonMap("gitCommit", GitUtils.getHeadCommit(scmPath)));
}
/**
@@ -341,64 +328,6 @@ public File getScmDirectory() {
return scmDirectory;
}
- /**
- * Gets an SCM repository from the configured connection URL.
- *
- * @return The SCM repository.
- * @throws MojoExecutionException If the SCM repository cannot be created.
- */
- private ScmRepository getScmRepository() throws MojoExecutionException {
- try {
- return scmManager.makeScmRepository(scmConnectionUrl);
- } catch (final ScmException e) {
- throw new MojoExecutionException("Failed to create SCM repository", e);
- }
- }
-
- /**
- * Gets the current SCM revision (commit hash) for the configured SCM directory.
- *
- * @return The current SCM revision string.
- * @throws MojoExecutionException If the revision cannot be retrieved from SCM.
- */
- private String getScmRevision() throws MojoExecutionException {
- final ScmRepository scmRepository = getScmRepository();
- final CommandParameters commandParameters = new CommandParameters();
- try {
- final InfoScmResult result = scmManager.getProviderByRepository(scmRepository).info(scmRepository.getProviderRepository(),
- new ScmFileSet(scmDirectory), commandParameters);
-
- return getScmRevision(result);
- } catch (final ScmException e) {
- throw new MojoExecutionException("Failed to retrieve SCM revision", e);
- }
- }
-
- /**
- * Extracts the revision string from an SCM info result.
- *
- * @param result The SCM info result.
- * @return The revision string.
- * @throws MojoExecutionException If the result is unsuccessful or contains no revision.
- */
- private String getScmRevision(final InfoScmResult result) throws MojoExecutionException {
- if (!result.isSuccess()) {
- throw new MojoExecutionException("Failed to retrieve SCM revision: " + result.getProviderMessage());
- }
-
- if (result.getInfoItems() == null || result.getInfoItems().isEmpty()) {
- throw new MojoExecutionException("No SCM revision information found for " + scmDirectory);
- }
-
- final InfoItem item = result.getInfoItems().get(0);
-
- final String revision = item.getRevision();
- if (revision == null) {
- throw new MojoExecutionException("Empty SCM revision returned for " + scmDirectory);
- }
- return revision;
- }
-
/**
* Gets the GPG signer, creating and preparing it from plugin parameters if not already set.
*
diff --git a/src/test/java/org/apache/commons/release/plugin/internal/GitUtilsTest.java b/src/test/java/org/apache/commons/release/plugin/internal/GitUtilsTest.java
index caed45c4c..b6e35ae9f 100644
--- a/src/test/java/org/apache/commons/release/plugin/internal/GitUtilsTest.java
+++ b/src/test/java/org/apache/commons/release/plugin/internal/GitUtilsTest.java
@@ -51,6 +51,14 @@ static Stream testGetCurrentBranch() {
Arguments.of(worktree, GitFixture.WORKTREE_BRANCH), Arguments.of(worktree.resolve(GitFixture.SUBDIR), GitFixture.WORKTREE_BRANCH));
}
+ static Stream testGetHeadCommit() {
+ return Stream.of(
+ Arguments.of(repo, GitFixture.INITIAL_COMMIT_SHA),
+ Arguments.of(repo.resolve(GitFixture.SUBDIR), GitFixture.INITIAL_COMMIT_SHA),
+ Arguments.of(worktree, GitFixture.INITIAL_COMMIT_SHA),
+ Arguments.of(worktree.resolve(GitFixture.SUBDIR), GitFixture.INITIAL_COMMIT_SHA));
+ }
+
static Stream testScmToDownloadUri() {
return Stream.of(
Arguments.of("scm:git:https://gitbox.apache.org/repos/asf/commons-release-plugin.git",
@@ -80,6 +88,31 @@ void testGetCurrentBranchDetachedHead() throws IOException {
assertEquals(GitFixture.INITIAL_COMMIT_SHA, GitUtils.getCurrentBranch(detachedRepo));
}
+ @ParameterizedTest
+ @MethodSource
+ void testGetHeadCommit(final Path repositoryPath, final String expectedSha) throws IOException {
+ assertEquals(expectedSha, GitUtils.getHeadCommit(repositoryPath));
+ }
+
+ @Test
+ void testGetHeadCommitDetachedHead() throws IOException {
+ final Path detachedRepo = tempDir.resolve("detached-head-commit-repo");
+ final Path detachedWorktree = tempDir.resolve("detached-head-commit-worktree");
+ GitFixture.createRepoAndWorktree(detachedRepo, detachedWorktree);
+ GitFixture.git(detachedRepo, "checkout", "-q", "--detach", "HEAD");
+ assertEquals(GitFixture.INITIAL_COMMIT_SHA, GitUtils.getHeadCommit(detachedRepo));
+ }
+
+ @Test
+ void testGetHeadCommitPackedRefs() throws IOException {
+ final Path packedRepo = tempDir.resolve("packed-repo");
+ final Path packedWorktree = tempDir.resolve("packed-worktree");
+ GitFixture.createRepoAndWorktree(packedRepo, packedWorktree);
+ // Move all loose refs (branches, tags) into .git/packed-refs and delete the loose files.
+ GitFixture.git(packedRepo, "pack-refs", "--all", "--prune");
+ assertEquals(GitFixture.INITIAL_COMMIT_SHA, GitUtils.getHeadCommit(packedRepo));
+ }
+
@ParameterizedTest
@MethodSource
void testScmToDownloadUri(final String scmUri, final Path repositoryPath, final String expectedDownloadUri) throws IOException {
diff --git a/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java b/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
index d266e67b5..19f40be4d 100644
--- a/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
+++ b/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
@@ -59,7 +59,6 @@
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.maven.rtinfo.RuntimeInformation;
-import org.apache.maven.scm.manager.ScmManager;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.eclipse.aether.RepositorySystemSession;
@@ -118,9 +117,8 @@ private static void configureBuildAttestationMojo(final BuildAttestationMojo moj
private static BuildAttestationMojo createBuildAttestationMojo(final MavenProject project, final MavenProjectHelper projectHelper)
throws ComponentLookupException {
- final ScmManager scmManager = container.lookup(ScmManager.class);
final RuntimeInformation runtimeInfo = container.lookup(RuntimeInformation.class);
- return new BuildAttestationMojo(project, scmManager, runtimeInfo,
+ return new BuildAttestationMojo(project, runtimeInfo,
createMavenSession(createMavenExecutionRequest(), new DefaultMavenExecutionResult()), projectHelper);
}
From d74eb3eb58e0cd7bf08ab234f0173686679111b0 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Tue, 21 Apr 2026 22:16:53 +0200
Subject: [PATCH 47/51] fix: use temporary files for signing
---
.../release/plugin/internal/DsseUtils.java | 75 +++++++++++--------
.../plugin/mojos/BuildAttestationMojo.java | 10 +--
2 files changed, 44 insertions(+), 41 deletions(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/internal/DsseUtils.java b/src/main/java/org/apache/commons/release/plugin/internal/DsseUtils.java
index dd7063112..22e49249d 100644
--- a/src/main/java/org/apache/commons/release/plugin/internal/DsseUtils.java
+++ b/src/main/java/org/apache/commons/release/plugin/internal/DsseUtils.java
@@ -17,6 +17,7 @@
package org.apache.commons.release.plugin.internal;
import java.io.ByteArrayOutputStream;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@@ -25,6 +26,7 @@
import java.util.Locale;
import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.release.plugin.slsa.v1_2.DsseEnvelope;
import org.apache.maven.plugin.MojoExecutionException;
@@ -47,7 +49,7 @@ public final class DsseUtils {
/**
* Creates and prepares a {@link GpgSigner} from the given configuration.
*
- * The returned signer has {@link AbstractGpgSigner#prepare()} already called and is ready for use with {@link #signFile(AbstractGpgSigner, Path)}.
+ * The returned signer has {@link AbstractGpgSigner#prepare()} already called and is ready for use with {@link #signStatement}.
*
* @param executable path to the GPG executable, or {@code null} to use {@code gpg} from {@code PATH}
* @param defaultKeyring whether to include the default GPG keyring
@@ -95,57 +97,66 @@ public static String getKeyId(final byte[] sigBytes) throws MojoExecutionExcepti
}
/**
- * Signs {@code paeFile} and returns the raw OpenPGP signature bytes.
+ * Signs a serialized DSSE payload and returns the raw OpenPGP signature bytes.
*
- * The signer must already have {@link AbstractGpgSigner#prepare()} called before this method is invoked.
+ * Creates a unique temporary {@code .pae} file inside {@code workDir}, containing the payload
+ * wrapped in the DSSE Pre-Authentication Encoding:
*
- * @param signer the configured, prepared signer
- * @param path path to the file to sign
+ * PAE(type, body) = "DSSEv1" + SP + LEN(type) + SP + type + SP + LEN(body) + SP + body
+ *
+ * then invokes {@code signer} on that file, reads back the un-armored PGP signature, and
+ * deletes both temporary files in a {@code finally} block. Cleanup is best-effort; a delete
+ * failure at the end of a successful sign only leaves a stray temporary file in {@code workDir}
+ * and does not fail the build.
+ *
+ * The signer must already have {@link AbstractGpgSigner#prepare()} called before this method
+ * is invoked.
+ *
+ * @param signer the configured, prepared signer
+ * @param statementBytes the already-serialized JSON statement bytes to sign
+ * @param workDir directory in which to create the intermediate PAE and signature files
* @return raw binary PGP signature bytes
- * @throws MojoExecutionException if signing or signature decoding fails
+ * @throws MojoExecutionException if encoding, signing, or signature decoding fails
*/
- public static byte[] signFile(final AbstractGpgSigner signer, final Path path) throws MojoExecutionException {
- final Path signaturePath = signer.generateSignatureForArtifact(path.toFile()).toPath();
- final byte[] signatureBytes;
- try (InputStream in = Files.newInputStream(signaturePath); ArmoredInputStream armoredIn = new ArmoredInputStream(in)) {
- signatureBytes = IOUtils.toByteArray(armoredIn);
- } catch (final IOException e) {
- throw new MojoExecutionException("Failed to read signature file: " + signaturePath, e);
- }
+ public static byte[] signStatement(final AbstractGpgSigner signer, final byte[] statementBytes, final Path workDir)
+ throws MojoExecutionException {
+ File paeFile = null;
+ File ascFile = null;
try {
- Files.delete(signaturePath);
+ paeFile = File.createTempFile("statement-", ".pae", workDir.toFile());
+ FileUtils.writeByteArrayToFile(paeFile, paeEncode(statementBytes));
+ ascFile = signer.generateSignatureForArtifact(paeFile);
+ try (InputStream in = Files.newInputStream(ascFile.toPath());
+ ArmoredInputStream armoredIn = new ArmoredInputStream(in)) {
+ return IOUtils.toByteArray(armoredIn);
+ }
} catch (final IOException e) {
- throw new MojoExecutionException("Failed to delete signature file: " + signaturePath, e);
+ throw new MojoExecutionException("Failed to sign attestation statement", e);
+ } finally {
+ FileUtils.deleteQuietly(paeFile);
+ FileUtils.deleteQuietly(ascFile);
}
- return signatureBytes;
}
/**
- * Writes serialized JSON to a file using the DSSE Pre-Authentication Encoding (PAE).
- *
- * PAE(type, body) = "DSSEv1" + SP + LEN(type) + SP + type + SP + LEN(body) + SP + body
+ * Encodes {@code statementBytes} using the DSSEv1 Pre-Authentication Encoding.
*
* @param statementBytes the already-serialized JSON statement bytes to encode
- * @param buildDirectory directory in which the PAE file is created
- * @return path to the written PAE file
- * @throws MojoExecutionException if I/O fails
+ * @return the PAE-encoded bytes
*/
- public static Path writePaeFile(final byte[] statementBytes, final Path buildDirectory) throws MojoExecutionException {
+ private static byte[] paeEncode(final byte[] statementBytes) {
+ final byte[] payloadTypeBytes = DsseEnvelope.PAYLOAD_TYPE.getBytes(StandardCharsets.UTF_8);
+ final ByteArrayOutputStream pae = new ByteArrayOutputStream();
try {
- final byte[] payloadTypeBytes = DsseEnvelope.PAYLOAD_TYPE.getBytes(StandardCharsets.UTF_8);
-
- final ByteArrayOutputStream pae = new ByteArrayOutputStream();
pae.write(("DSSEv1 " + payloadTypeBytes.length + " ").getBytes(StandardCharsets.UTF_8));
pae.write(payloadTypeBytes);
pae.write((" " + statementBytes.length + " ").getBytes(StandardCharsets.UTF_8));
pae.write(statementBytes);
-
- final Path paeFile = buildDirectory.resolve("statement.pae");
- Files.write(paeFile, pae.toByteArray());
- return paeFile;
} catch (final IOException e) {
- throw new MojoExecutionException("Failed to write PAE file", e);
+ // ByteArrayOutputStream#write(byte[]) never throws; this branch is unreachable.
+ throw new IllegalStateException(e);
}
+ return pae.toByteArray();
}
/**
diff --git a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
index 7e5ef10fb..04cd317b7 100644
--- a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
+++ b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
@@ -445,15 +445,7 @@ private void signAndWriteStatement(final Statement statement, final Path outputP
} catch (final JsonProcessingException e) {
throw new MojoExecutionException("Failed to serialize attestation statement", e);
}
- final AbstractGpgSigner signer = getSigner();
- final byte[] sigBytes;
- try {
- final Path paeFile = DsseUtils.writePaeFile(statementBytes, outputPath);
- sigBytes = DsseUtils.signFile(signer, paeFile);
- Files.deleteIfExists(paeFile);
- } catch (final IOException e) {
- throw new MojoExecutionException("Failed to sign attestation statement", e);
- }
+ final byte[] sigBytes = DsseUtils.signStatement(getSigner(), statementBytes, outputPath);
final Signature sig = new Signature()
.setKeyid(DsseUtils.getKeyId(sigBytes))
From 8102f343dd25569d063ce33d2d9fa2ceb89ed3b5 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Tue, 21 Apr 2026 22:22:17 +0200
Subject: [PATCH 48/51] fix: remove unnecessary `pom.xml` changes
---
pom.xml | 20 --------------------
1 file changed, 20 deletions(-)
diff --git a/pom.xml b/pom.xml
index 368c9cfe0..404d3c42f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -174,12 +174,6 @@
maven-scm-api
${maven-scm.version}
-
- org.apache.maven.scm
- maven-scm-manager-plexus
- ${maven-scm.version}
- compile
-
org.apache.maven.scm
maven-scm-provider-svnexe
@@ -518,20 +512,6 @@
-
-
-
- org.apache.maven.plugins
- maven-resources-plugin
-
-
- default-testResources
-
- false
-
-
-
-
From 2dec90f2a051ca44496c765b9fca1c01f140c7b4 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Tue, 21 Apr 2026 22:27:33 +0200
Subject: [PATCH 49/51] fix: clean-up public API
---
.../release/plugin/mojos/BuildAttestationMojo.java | 11 +----------
1 file changed, 1 insertion(+), 10 deletions(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
index 04cd317b7..dbf5f1b6a 100644
--- a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
+++ b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
@@ -319,15 +319,6 @@ private ResourceDescriptor getScmDescriptor() throws IOException {
.setDigest(Collections.singletonMap("gitCommit", GitUtils.getHeadCommit(scmPath)));
}
- /**
- * Gets the SCM directory.
- *
- * @return The SCM directory.
- */
- public File getScmDirectory() {
- return scmDirectory;
- }
-
/**
* Gets the GPG signer, creating and preparing it from plugin parameters if not already set.
*
@@ -397,7 +388,7 @@ void setScmConnectionUrl(final String scmConnectionUrl) {
*
* @param scmDirectory The SCM directory.
*/
- public void setScmDirectory(final File scmDirectory) {
+ void setScmDirectory(final File scmDirectory) {
this.scmDirectory = scmDirectory;
}
From 02477241d62d676e2b4bfa766d3cfb87e5e138bd Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Tue, 21 Apr 2026 22:44:24 +0200
Subject: [PATCH 50/51] fix: document checksum choice
---
.../release/plugin/mojos/BuildAttestationMojo.java | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
index dbf5f1b6a..39c398ff7 100644
--- a/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
+++ b/src/main/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojo.java
@@ -107,6 +107,12 @@ public class BuildAttestationMojo extends AbstractMojo {
/**
* Checksum algorithms used in the generated attestation.
+ *
+ * The default list is:
+ *
+ * - {@code SHA-1} and {@code MD5} are easily available from Maven Central without downloading the artifact;
+ * - {@code SHA-512} and {@code SHA-256} provide more security to the signed attestation, if the artifact is downloaded.
+ *
*/
@Parameter(property = "commons.release.checksums.algorithms", defaultValue = "SHA-512,SHA-256,SHA-1,MD5")
private String algorithmNames;
@@ -189,7 +195,7 @@ public class BuildAttestationMojo extends AbstractMojo {
/**
* Whether to skip attaching the attestation artifact to the project.
*/
- @Parameter(property = "commons.release.skipAttach")
+ @Parameter(property = "commons.release.skipAttach", defaultValue = "false")
private boolean skipAttach;
/**
* Whether to use gpg-agent for passphrase management.
From d92845a9ab0fe6a8f5a43a6f5b6df1b2e9a96830 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Tue, 21 Apr 2026 22:48:34 +0200
Subject: [PATCH 51/51] fix: strengthen signature verification in test
---
.../commons/release/plugin/mojos/BuildAttestationMojoTest.java | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java b/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
index 19f40be4d..fc507375a 100644
--- a/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
+++ b/src/test/java/org/apache/commons/release/plugin/mojos/BuildAttestationMojoTest.java
@@ -248,6 +248,8 @@ void signingTest() throws Exception {
assertJsonNodePresent(envelopeJson, "signatures[0]");
assertJsonNodeAbsent(envelopeJson, "signatures[1]");
assertJsonPartEquals("${json-unit.regex}.+", envelopeJson, "signatures[0].sig");
+ // Issuer fingerprint extracted from the canned commons-text-1.4.jar.asc.
+ assertJsonPartEquals("b6e73d84ea4fcc47166087253faad2cd5ecbb314", envelopeJson, "signatures[0].keyid");
final DsseEnvelope envelope = OBJECT_MAPPER.readValue(envelopeJson.trim(), DsseEnvelope.class);
final JsonNode statement = OBJECT_MAPPER.readTree(envelope.getPayload());