Repository: CycloneDX/cyclonedx-maven-plugin
Pull request #437 (2023-11-30 11:45:31)

Bump actions/setup-java from 3 to 4
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 3 to 4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/actions/setup-java/releases">actions/setup-java's releases</a>.</em></p>
<blockquote>
<h2>v4.0.0</h2>
<h2>What's Changed</h2>
<p>In the scope of this release, the version of the Node.js runtime was updated to 20. The majority of dependencies were updated to the latest versions. From now on, the code for the setup-java will run on Node.js 20 instead of Node.js 16.</p>
<h2>Breaking changes</h2>
<ul>
<li>Update Node.js runtime to version 20 by <a href="https://github.com/aparnajyothi-y"><code>@​aparnajyothi-y</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/558">actions/setup-java#558</a></li>
</ul>
<h2>Non-breaking changes</h2>
<ul>
<li>Adding support for microsoft openjdk 21.0.0 by <a href="https://github.com/ralfstuckert"><code>@​ralfstuckert</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/546">actions/setup-java#546</a></li>
<li>Update <code>@​actions/cache</code> dependency and documentation by <a href="https://github.com/IvanZosimov"><code>@​IvanZosimov</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/549">actions/setup-java#549</a></li>
<li>Implementation of the cache-dependency-path option to control caching dependency by <a href="https://github.com/itchyny"><code>@​itchyny</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/499">actions/setup-java#499</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/ralfstuckert"><code>@​ralfstuckert</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-java/pull/546">actions/setup-java#546</a></li>
<li><a href="https://github.com/itchyny"><code>@​itchyny</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-java/pull/499">actions/setup-java#499</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/setup-java/compare/v3...v4.0.0">https://github.com/actions/setup-java/compare/v3...v4.0.0</a></p>
<h2>v3.13.0</h2>
<h2>What's changed</h2>
<p>In the scope of this release, support for Dragonwell JDK was added by <a href="https://github.com/Accelerator1996"><code>@​Accelerator1996</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/532">actions/setup-java#532</a></p>
<pre lang="yaml"><code>steps:
 - name: Checkout
   uses: actions/checkout@v3
 - name: Setup-java
   uses: actions/setup-java@v3
   with:
     distribution: 'dragonwell'
     java-version: '17'
</code></pre>
<p>Several inaccuracies were also fixed:</p>
<ul>
<li>Fix XML namespaces wrongly using https by <a href="https://github.com/gnodet"><code>@​gnodet</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/503">actions/setup-java#503</a></li>
<li>Fix typo and remove unintentional(?) word by <a href="https://github.com/CyberFlameGO"><code>@​CyberFlameGO</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/518">actions/setup-java#518</a></li>
<li>Fix usage link within the README.md file by <a href="https://github.com/dassiorleando"><code>@​dassiorleando</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/525">actions/setup-java#525</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/CyberFlameGO"><code>@​CyberFlameGO</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-java/pull/518">actions/setup-java#518</a></li>
<li><a href="https://github.com/dassiorleando"><code>@​dassiorleando</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-java/pull/525">actions/setup-java#525</a></li>
<li><a href="https://github.com/gnodet"><code>@​gnodet</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-java/pull/503">actions/setup-java#503</a></li>
<li><a href="https://github.com/Accelerator1996"><code>@​Accelerator1996</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-java/pull/532">actions/setup-java#532</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/setup-java/compare/v3...v3.13.0">https://github.com/actions/setup-java/compare/v3...v3.13.0</a></p>
<h2>v3.12.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/actions/setup-java/commit/387ac29b308b003ca37ba93a6cab5eb57c8f5f93"><code>387ac29</code></a> Upgrade Node to v20 (<a href="https://redirect.github.com/actions/setup-java/issues/558">#558</a>)</li>
<li><a href="https://github.com/actions/setup-java/commit/9eda6b51cc4f6ee99be3dd5537b85e389e47bda9"><code>9eda6b5</code></a> feat: implement cache-dependency-path option to control caching dependency (#...</li>
<li><a href="https://github.com/actions/setup-java/commit/78078da0cd035d0d177cc2cb696e05d96fba7d11"><code>78078da</code></a> Update <code>@​actions/cache</code> dependency and documentation (<a href="https://redirect.github.com/actions/setup-java/issues/549">#549</a>)</li>
<li><a href="https://github.com/actions/setup-java/commit/5caaba646e214abb5c4c808eb8fe13db519ab757"><code>5caaba6</code></a> add support for microsoft openjdk 21.0.0 (<a href="https://redirect.github.com/actions/setup-java/issues/546">#546</a>)</li>
<li>See full diff in <a href="https://github.com/actions/setup-java/compare/v3...v4">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-java&package-manager=github_actions&previous-version=3&new-version=4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 8332ef3..9d76359 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -4,6 +4,10 @@ updates:
     directory: "/"
     schedule:
       interval: "daily"
+    ignore:
+      # JsonUnit `2.x` is the last Java 8 compatible major release
+      - dependency-name: "net.javacrumbs.json-unit:*"
+        update-types: ["version-update:semver-major"]
 
   - package-ecosystem: "github-actions"
     directory: "/"
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index a95be10..88ba90a 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -10,7 +10,7 @@ jobs:
     steps:
     - uses: actions/checkout@v4.1.1
     - name: Set up JDK 1.8
-      uses: actions/setup-java@v3
+      uses: actions/setup-java@v4
       with:
         distribution: 'adopt'
         java-version: '8'
diff --git a/pom.xml b/pom.xml
index 17799cf..9c70985 100644
--- a/pom.xml
+++ b/pom.xml
@@ -203,6 +203,12 @@
             <artifactId>junit-vintage-engine</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>net.javacrumbs.json-unit</groupId>
+            <artifactId>json-unit-assertj</artifactId>
+            <version>2.38.0</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <dependencyManagement>
diff --git a/src/it/makeBom/verify.groovy b/src/it/makeBom/verify.groovy
index 28f59f0..4f54802 100644
--- a/src/it/makeBom/verify.groovy
+++ b/src/it/makeBom/verify.groovy
@@ -9,7 +9,6 @@ assert bomFileXml.text.contains('<reference type="website"><url>https://github.c
 assert !bomFileXml.text.contains('<property name="maven.optional.unused">')
 
 // Reproducible Builds
-assert !bomFileJson.text.contains('"serialNumber"')
 assert !bomFileJson.text.contains('"timestamp"')
 assert bomFileJson.text.contains('"name" : "cdx:reproducible",')
 assert bomFileJson.text.contains('"value" : "enabled"')
diff --git a/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java b/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java
index 4b50480..34ba8a0 100644
--- a/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java
+++ b/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java
@@ -334,7 +334,6 @@ public abstract class BaseCycloneDxMojo extends AbstractMojo {
 
             if (outputTimestamp != null) {
                 // activate Reproducible Builds mode
-                includeBomSerialNumber = false;
                 metadata.setTimestamp(null);
                 if (schemaVersion().getVersion() >= 1.3) {
                     metadata.addProperty(newProperty("cdx:reproducible", "enabled"));
@@ -342,7 +341,8 @@ public abstract class BaseCycloneDxMojo extends AbstractMojo {
             }
 
             if (schemaVersion().getVersion() >= 1.1 && includeBomSerialNumber) {
-                bom.setSerialNumber("urn:uuid:" + UUID.randomUUID());
+                String serialNumber = generateSerialNumber(metadata.getProperties());
+                bom.setSerialNumber(serialNumber);
             }
 
             if (schemaVersion().getVersion() >= 1.2) {
@@ -371,6 +371,21 @@ public abstract class BaseCycloneDxMojo extends AbstractMojo {
         }
     }
 
+    private String generateSerialNumber(List<Property> properties) {
+        String gav = String.format("%s:%s:%s", project.getGroupId(), project.getArtifactId(), project.getVersion());
+        StringBuilder sb = new StringBuilder(gav);
+        if (properties != null) {
+            for(Property prop: properties) {
+                sb.append(';');
+                sb.append(prop.getName());
+                sb.append('=');
+                sb.append(prop.getValue());
+            }
+        }
+        UUID uuid = UUID.nameUUIDFromBytes(sb.toString().getBytes(StandardCharsets.UTF_8));
+        return String.format("urn:uuid:%s", uuid);
+    }
+
     private void saveBom(Bom bom) throws ParserConfigurationException, IOException, GeneratorException,
             MojoExecutionException {
         if ("all".equalsIgnoreCase(outputFormat) || "xml".equalsIgnoreCase(outputFormat)) {


Test patch:
diff --git a/src/test/java/org/cyclonedx/maven/Issue420Test.java b/src/test/java/org/cyclonedx/maven/Issue420Test.java
new file mode 100644
index 0000000..9638dcd
--- /dev/null
+++ b/src/test/java/org/cyclonedx/maven/Issue420Test.java
@@ -0,0 +1,73 @@
+package org.cyclonedx.maven;
+
+import io.takari.maven.testing.executor.MavenRuntime.MavenRuntimeBuilder;
+import io.takari.maven.testing.executor.MavenVersions;
+import io.takari.maven.testing.executor.junit.MavenJUnitTestRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import javax.annotation.Nullable;
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+
+import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
+
+/**
+ * Verifies that serial numbers are generated deterministically.
+ *
+ * @see <a href="https://github.com/CycloneDX/cyclonedx-maven-plugin/issues/420">Issue 420</a>
+ */
+@RunWith(MavenJUnitTestRunner.class)
+@MavenVersions({"3.6.3"})
+public class Issue420Test extends BaseMavenVerifier {
+
+    public Issue420Test(MavenRuntimeBuilder runtimeBuilder) throws Exception {
+        super(runtimeBuilder);
+    }
+
+    @Test
+    public void testDefaults() throws Exception {
+        test(new String[0], "urn:uuid:af111a48-2091-3e2e-ad2e-60b1975b651d");
+    }
+
+    @Test
+    public void testDefaultsWhenSerialNumberIsDisabled() throws Exception {
+        test(new String[]{"-DincludeBomSerialNumber=false"}, null);
+    }
+
+    @Test
+    public void testWhenOutputTimestampIsSet() throws Exception {
+        test(new String[]{"-Dproject.build.outputTimestamp=2023-11-08T00:00:00Z"}, "urn:uuid:3e383c4c-ef61-3eba-8214-3ecd46c4bbee");
+    }
+
+    @Test
+    public void testWhenOutputTimestampIsSetAndSerialNumberIsDisabled() throws Exception {
+        test(new String[]{"-Dproject.build.outputTimestamp=2023-11-08T00:00:00Z", "-DincludeBomSerialNumber=false"}, null);
+    }
+
+    private void test(String[] cliOptions, @Nullable String expectedSerialNumber) throws Exception {
+        File projDir = resources.getBasedir("issue-420");
+        verifier
+                .forProject(projDir)
+                .withCliOption("-Dcurrent.version=" + getCurrentVersion())
+                .withCliOption("-B")
+                .withCliOption("-Dmaven.test.skip")
+                .withCliOptions(cliOptions)
+                .execute("clean", "verify")
+                .assertErrorFreeLog();
+        assertSerialNumber(projDir, expectedSerialNumber);
+    }
+
+    private static void assertSerialNumber(File projDir, @Nullable String expectedSerialNumber) throws Exception {
+        File bomFile = new File(projDir, "target/bom.json");
+        String bomJson = new String(Files.readAllBytes(bomFile.toPath()), StandardCharsets.UTF_8);
+        String serialNumberKey = "serialNumber";
+        if (expectedSerialNumber == null) {
+            assertThatJson(bomJson).isObject().doesNotContainKey(serialNumberKey);
+        } else {
+            assertThatJson(bomJson).isObject().containsEntry(serialNumberKey, expectedSerialNumber);
+        }
+    }
+
+}
diff --git a/src/test/resources/issue-420/module-A/pom.xml b/src/test/resources/issue-420/module-A/pom.xml
new file mode 100644
index 0000000..f4244d8
--- /dev/null
+++ b/src/test/resources/issue-420/module-A/pom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.example</groupId>
+        <artifactId>issue-420</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>issue-420-module-A</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.1</version>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/src/test/resources/issue-420/pom.xml b/src/test/resources/issue-420/pom.xml
new file mode 100644
index 0000000..efae20a
--- /dev/null
+++ b/src/test/resources/issue-420/pom.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.example</groupId>
+    <artifactId>issue-420</artifactId>
+    <packaging>pom</packaging>
+    <version>${revision}</version>
+
+    <licenses>
+        <license>
+            <name>Apache-2.0</name>
+            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+        </license>
+    </licenses>
+
+    <modules>
+        <module>module-A</module>
+    </modules>
+
+    <properties>
+        <revision>1.0.0</revision>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.cyclonedx</groupId>
+                <artifactId>cyclonedx-maven-plugin</artifactId>
+                <version>${current.version}</version>
+                <executions>
+                    <execution>
+                        <phase>verify</phase>
+                        <goals>
+                            <goal>makeAggregateBom</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <outputFormat>json</outputFormat>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>