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"

Reply via email to