This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ant-antlibs-cyclonedx.git
The following commit(s) were added to refs/heads/main by this push:
new 09ff42b allow a way to set the serial number of the generated BOM
09ff42b is described below
commit 09ff42b75e61cbcef93ef6ecada40f6a8aed804b
Author: Stefan Bodewig <[email protected]>
AuthorDate: Sat Jul 11 16:15:03 2026 +0200
allow a way to set the serial number of the generated BOM
---
changes.xml | 5 +++
docs/archivebom.html | 17 ++++++++
docs/componentbom.html | 18 ++++++++
.../org/apache/ant/cyclonedx/ComponentBomTask.java | 34 ++++++++++++++-
src/main/org/apache/ant/cyclonedx/antlib.xml | 2 +
src/tests/antunit/archivebom-test.xml | 21 +++++++++
src/tests/antunit/componentbom-test.xml | 50 ++++++++++++++++++++++
7 files changed, 146 insertions(+), 1 deletion(-)
diff --git a/changes.xml b/changes.xml
index 8bec624..e24fdfc 100644
--- a/changes.xml
+++ b/changes.xml
@@ -72,6 +72,11 @@
A new specialized task "archivebom" simplifies creation of SBOMs
for distribution zips/tarballs by providing a few defaults.
</action>
+ <action type="add">
+ A new attribute "serialNumber" of the componentbom task allows
+ the serial number of the generated SBOM to be set to a fixed
+ value for reproducible builds.
+ </action>
</release>
<release version="0.1" date="2026-06-03" description="initial release">
diff --git a/docs/archivebom.html b/docs/archivebom.html
index 11282cd..478d1db 100644
--- a/docs/archivebom.html
+++ b/docs/archivebom.html
@@ -85,6 +85,23 @@ <h3>Attributes</h3>
</td>
<td>No, defaults to "json".</td>
</tr>
+ <tr>
+ <td>serialNumber</td>
+ <td>Serial Number of the BOM created. Value must be a valid
+ <a href="https://www.ietf.org/rfc/rfc4122.html">RFC 4122 UUID
URN</a>.<br/>
+ The spec recommends each BOM generated to have a unique serial
+ number, even if the rest of the content doesn't
+ change. This is what this task does by default (i.e. when
+ the attribute is not set).<br/>
+ In the context of reproducible builds you may want to have
+ more control and this is a way to set a fixed serial
+ number.<br/>
+ The special value "random" can be used to explicitly trigger
+ the task's default behavior.<br/>
+ </td>
+ <td>No, defaults to a random UUID URN. Don't set this
+ attribute unless you know why.</td>
+ </tr>
<tr>
<td>bomName</td>
<td>The base name of the generated SBOM file. The full file
diff --git a/docs/componentbom.html b/docs/componentbom.html
index ad77138..e35ab26 100644
--- a/docs/componentbom.html
+++ b/docs/componentbom.html
@@ -77,6 +77,24 @@ <h3>Attributes</h3>
</td>
<td>No, defaults to "json".</td>
</tr>
+ <tr>
+ <td>serialNumber</td>
+ <td>Serial Number of the BOM created. Value must be a valid
+ <a href="https://www.ietf.org/rfc/rfc4122.html">RFC 4122 UUID
URN</a>.<br/>
+ The spec recommends each BOM generated to have a unique serial
+ number, even if the rest of the content doesn't
+ change. This is what this task does by default (i.e. when
+ the attribute is not set).<br/>
+ In the context of reproducible builds you may want to have
+ more control and this is a way to set a fixed serial
+ number.<br/>
+ The special value "random" can be used to explicitly trigger
+ the task's default behavior.<br/>
+ <em>since CycloneDX Antlib 0.2</em>
+ </td>
+ <td>No, defaults to a random UUID URN. Don't set this
+ attribute unless you know why.</td>
+ </tr>
<tr>
<td>bomName</td>
<td>The base name of the generated SBOM file. The full file
diff --git a/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java
b/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java
index 261ac25..ec076d4 100644
--- a/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java
+++ b/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java
@@ -12,6 +12,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@@ -44,7 +45,12 @@ import org.cyclonedx.model.metadata.ToolInformation;
*/
public class ComponentBomTask extends Task {
+ private static final String RANDOM_SERIAL_NUMBER = "random";
+ private static Pattern RFC4122_URN_PATTERN =
+
Pattern.compile("^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$");
+
private File outputDirectory;
+ private String serialNumber = null;
private String bomName = "bom";
private SpecVersion specVersion = SpecVersion.DEFAULT;
private OutputFormat format = OutputFormat.JSON;
@@ -69,6 +75,28 @@ public class ComponentBomTask extends Task {
this.specVersion = specVersion;
}
+ /**
+ * Specifies the serial number of the BOM.
+ *
+ * <p>The default behavior is to generate a new random serial number for
each BOM generated like the spec
+ * recommends. This default behavior can be specified explicitly be
setting the value to "random".</p>
+ *
+ * @param serialNumber serial number for the SBOM. Must be a valid RFC
4122 UUID URN or the literal String "random".
+ * @since CycloneDX Antlib 0.2
+ */
+ public void setSerialNumber(String serialNumber) {
+ if (serialNumber == null ||
RANDOM_SERIAL_NUMBER.equalsIgnoreCase(serialNumber)) {
+ this.serialNumber = null;
+ } else {
+ serialNumber = serialNumber.toLowerCase(Locale.US);
+ Matcher m = RFC4122_URN_PATTERN.matcher(serialNumber);
+ if (!m.matches()) {
+ throw new BuildException("serial number '" + serialNumber + "'
is not a value UUID URN");
+ }
+ this.serialNumber = serialNumber;
+ }
+ }
+
/**
* Which serialization format of CycloneDX SBOM to use.
*
@@ -231,7 +259,11 @@ public class ComponentBomTask extends Task {
private Bom createBom() throws IOException {
Bom bom = new Bom();
- bom.setSerialNumber("urn:uuid:" + UUID.randomUUID());
+ if (serialNumber != null) {
+ bom.setSerialNumber(serialNumber);
+ } else {
+ bom.setSerialNumber("urn:uuid:" + UUID.randomUUID());
+ }
Metadata meta = createMetadata();
diff --git a/src/main/org/apache/ant/cyclonedx/antlib.xml
b/src/main/org/apache/ant/cyclonedx/antlib.xml
index 42a9c13..70e235d 100644
--- a/src/main/org/apache/ant/cyclonedx/antlib.xml
+++ b/src/main/org/apache/ant/cyclonedx/antlib.xml
@@ -38,6 +38,7 @@ under the License.
<macrodef name="archivebom" backtrace="false">
<attribute name="specVersion" default="1.6"/>
<attribute name="format" default="json"/>
+ <attribute name="serialNumber" default="random"/>
<attribute name="bomName" default="bom"/>
<attribute name="outputDirectory" default="${basedir}"/>
<attribute name="useComponentManufacturer" default="true"/>
@@ -58,6 +59,7 @@ under the License.
<sequential>
<cdx:componentbom
specVersion="@{specVersion}"
+ serialNumber="@{serialNumber}"
bomName="@{bomName}"
outputdirectory="@{outputDirectory}"
format="@{format}"
diff --git a/src/tests/antunit/archivebom-test.xml
b/src/tests/antunit/archivebom-test.xml
index 947b940..985e32b 100644
--- a/src/tests/antunit/archivebom-test.xml
+++ b/src/tests/antunit/archivebom-test.xml
@@ -138,4 +138,25 @@
xmlns:au="antlib:org.apache.ant.antunit"/>
</target>
+ <target name="testSerialNumberCanBeSet" depends="commonReferences">
+ <cdx:archivebom
+ archiveVersion="1.0"
+ purl="foo.tar.gz"
+ description="test"
+ publisher="test"
+ serialNumber="urn:uuid:d4662514-a7ef-498a-8451-8261eca2de15"
+ outputdirectory="${output}"
+ format="xml"
+ xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+ <archivecontent/>
+ <additionalComponentChildren>
+ <supplier refid="ant-team"/>
+ </additionalComponentChildren>
+ </cdx:archivebom>
+ <xmlproperty file="${output}/bom.xml"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom(serialNumber)"
+ value="urn:uuid:d4662514-a7ef-498a-8451-8261eca2de15"/>
+ </target>
</project>
diff --git a/src/tests/antunit/componentbom-test.xml
b/src/tests/antunit/componentbom-test.xml
index c71816e..d121ad6 100644
--- a/src/tests/antunit/componentbom-test.xml
+++ b/src/tests/antunit/componentbom-test.xml
@@ -122,6 +122,56 @@
value="The Apache Software Foundation"/>
</target>
+ <target name="testSerialNumberCanBeSet">
+ <cdx:componentbom
+ serialNumber="urn:uuid:65f1b136-0b2f-4cf0-a526-74b8c9f03da5"
+ outputdirectory="${output}"
+ format="xml"
+ xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+ <component name="testname"/>
+ </cdx:componentbom>
+ <xmlproperty file="${output}/bom.xml"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom(serialNumber)"
+ value="urn:uuid:65f1b136-0b2f-4cf0-a526-74b8c9f03da5"/>
+
+ <cdx:componentbom
+ serialNumber="urn:uuid:64C9AC6B-6B00-4F73-971A-B6CAEA994179"
+ outputdirectory="${output}"
+ format="xml"
+ xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+ <component name="testname"/>
+ </cdx:componentbom>
+ <xmlproperty file="${output}/bom.xml" prefix="2"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="2.bom(serialNumber)"
+ value="urn:uuid:64c9ac6b-6b00-4f73-971a-b6caea994179"/>
+ </target>
+
+ <target name="testThrowsIfSerialNumberIsNotAnUuidUrn">
+ <au:expectfailure expectedMessage="serial number 'foo' is not a value UUID
URN"
+ xmlns:au="antlib:org.apache.ant.antunit">
+ <cdx:componentbom
+ serialNumber="foo"
+ outputdirectory="${output}"
+ format="xml"
+ xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+ <component name="testname"/>
+ </cdx:componentbom>
+ </au:expectfailure>
+
+ <!-- "random" is fine -->
+ <cdx:componentbom
+ serialNumber="random"
+ outputdirectory="${output}"
+ format="xml"
+ xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+ <component name="testname"/>
+ </cdx:componentbom>
+ </target>
+
<target name="testMulipleTools">
<cdx:componentbom
bomName="multi"