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
commit e5a58029d614f105257a795e163d497752913694 Author: Stefan Bodewig <[email protected]> AuthorDate: Fri May 8 21:55:23 2026 +0200 add support for nested components --- examples/ant-cyclonedx-0.1alpha-cyclonedx.json | 37 +++++++++++----------- examples/ant-cyclonedx-0.1alpha-cyclonedx.xml | 37 +++++++++++----------- src/main/org/apache/ant/cyclonedx/Component.java | 32 +++++++++++++++++++ .../org/apache/ant/cyclonedx/ComponentBomTask.java | 12 +++++-- src/tests/antunit/componentbom-test.xml | 26 +++++++++++++++ 5 files changed, 105 insertions(+), 39 deletions(-) diff --git a/examples/ant-cyclonedx-0.1alpha-cyclonedx.json b/examples/ant-cyclonedx-0.1alpha-cyclonedx.json index 6b3e00c..cd77c27 100644 --- a/examples/ant-cyclonedx-0.1alpha-cyclonedx.json +++ b/examples/ant-cyclonedx-0.1alpha-cyclonedx.json @@ -1,10 +1,10 @@ { "bomFormat" : "CycloneDX", "specVersion" : "1.6", - "serialNumber" : "urn:uuid:266aabc2-6812-4036-88b0-1c88d390502c", + "serialNumber" : "urn:uuid:b102ed07-8561-4e8d-9124-2fbaeda2af87", "version" : 1, "metadata" : { - "timestamp" : "2026-05-08T16:48:44Z", + "timestamp" : "2026-05-08T19:48:59Z", "lifecycles" : [ { "phase" : "build" @@ -34,35 +34,35 @@ "hashes" : [ { "alg" : "MD5", - "content" : "6d0e2adf8c544249288e7ee596c566e9" + "content" : "cd60dca84b42bad257fd776c541a937d" }, { "alg" : "SHA-1", - "content" : "48d86c01594b9ac882d4153bf41e5fc55b620faa" + "content" : "f27f9d1e378987673e96868fab2c64ff076310c7" }, { "alg" : "SHA-256", - "content" : "4219d857d3100fd4ec043318f63836bafddedf4181b0767774ee1cf4916f8a2d" + "content" : "ad7490c23be951353bfdc299269816c87c680f2b08a0461d55bde3689d291234" }, { "alg" : "SHA-512", - "content" : "2347e3843020e10bc1bb5b3d255d105ae4403a832398406c541b7bcf45eef73bfed63bb7181cddae056f386faba9effdd5e83a48692db48df79307b4e39688ba" + "content" : "80784ae9b36496e7b5a70e91dfe0280d3437d79a211e2a6b8f761b77f0407494752ecbee9e7b6d5a2c014e04b7069b4dfc36a684fb9a11c982c59913d7ff97e6" }, { "alg" : "SHA3-256", - "content" : "6f64a5f69c6a2dc750cef5143c66beaf3aa19600716ed63cbb0afa980c5b46fb" + "content" : "1c6387b9e32013e79dfb420bc005e6dff736546152a95bbd6673b07a413b78f7" }, { "alg" : "SHA3-512", - "content" : "6859d0d58ea10a43f7704c1617a73609522763d4460ad5c52f067374bc995f7aa3a3d43851015625bcddaf354c528e79b38a5d7c9cb36272afa2a98b9b05c966" + "content" : "f57f015857b33731781a646b5131037897dc23f2e1a9e202984d5ac9a484d3843bbc9e1216aee46b4a36da213323f2d1f0f79dfa1e69f788d7bc538adfad9bc1" }, { "alg" : "SHA-384", - "content" : "72d5b1dfaa25985a0891d763ef8b65169a58f67ca3b47864f3aa16204649247e8d6f8c0654959553ae12e6d3bb564a81" + "content" : "e93b7acd21cbb36b41b273582b4c3a58828d0e908261549fdef3da0ebdce4e573cfd65e0406c0186cb6d8e76552347c3" }, { "alg" : "SHA3-384", - "content" : "56afebe15d38d52e2f80580659f0558f618deba550f0139f9a72b6254afe9f3a90bb3cb9837568ca8f93a8495c2ba2f3" + "content" : "241ac570fb9fc5e5ba1b7362df02167d214be3e7e6e371fe90ce4312bf63c7cd5ba3d988b5bd276040e94a4fdc23464c" } ], "licenses" : [ @@ -102,6 +102,7 @@ "https://ant.apache.org/" ] }, + "publisher" : "The Apache Software Foundation", "group" : "org.apache.ant", "name" : "ant-cyclonedx", "version" : "0.1alpha", @@ -109,35 +110,35 @@ "hashes" : [ { "alg" : "MD5", - "content" : "6d0e2adf8c544249288e7ee596c566e9" + "content" : "cd60dca84b42bad257fd776c541a937d" }, { "alg" : "SHA-1", - "content" : "48d86c01594b9ac882d4153bf41e5fc55b620faa" + "content" : "f27f9d1e378987673e96868fab2c64ff076310c7" }, { "alg" : "SHA-256", - "content" : "4219d857d3100fd4ec043318f63836bafddedf4181b0767774ee1cf4916f8a2d" + "content" : "ad7490c23be951353bfdc299269816c87c680f2b08a0461d55bde3689d291234" }, { "alg" : "SHA-512", - "content" : "2347e3843020e10bc1bb5b3d255d105ae4403a832398406c541b7bcf45eef73bfed63bb7181cddae056f386faba9effdd5e83a48692db48df79307b4e39688ba" + "content" : "80784ae9b36496e7b5a70e91dfe0280d3437d79a211e2a6b8f761b77f0407494752ecbee9e7b6d5a2c014e04b7069b4dfc36a684fb9a11c982c59913d7ff97e6" }, { "alg" : "SHA3-256", - "content" : "6f64a5f69c6a2dc750cef5143c66beaf3aa19600716ed63cbb0afa980c5b46fb" + "content" : "1c6387b9e32013e79dfb420bc005e6dff736546152a95bbd6673b07a413b78f7" }, { "alg" : "SHA3-512", - "content" : "6859d0d58ea10a43f7704c1617a73609522763d4460ad5c52f067374bc995f7aa3a3d43851015625bcddaf354c528e79b38a5d7c9cb36272afa2a98b9b05c966" + "content" : "f57f015857b33731781a646b5131037897dc23f2e1a9e202984d5ac9a484d3843bbc9e1216aee46b4a36da213323f2d1f0f79dfa1e69f788d7bc538adfad9bc1" }, { "alg" : "SHA-384", - "content" : "72d5b1dfaa25985a0891d763ef8b65169a58f67ca3b47864f3aa16204649247e8d6f8c0654959553ae12e6d3bb564a81" + "content" : "e93b7acd21cbb36b41b273582b4c3a58828d0e908261549fdef3da0ebdce4e573cfd65e0406c0186cb6d8e76552347c3" }, { "alg" : "SHA3-384", - "content" : "56afebe15d38d52e2f80580659f0558f618deba550f0139f9a72b6254afe9f3a90bb3cb9837568ca8f93a8495c2ba2f3" + "content" : "241ac570fb9fc5e5ba1b7362df02167d214be3e7e6e371fe90ce4312bf63c7cd5ba3d988b5bd276040e94a4fdc23464c" } ], "licenses" : [ diff --git a/examples/ant-cyclonedx-0.1alpha-cyclonedx.xml b/examples/ant-cyclonedx-0.1alpha-cyclonedx.xml index f9bb86e..18b76f3 100644 --- a/examples/ant-cyclonedx-0.1alpha-cyclonedx.xml +++ b/examples/ant-cyclonedx-0.1alpha-cyclonedx.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> -<bom serialNumber="urn:uuid:266aabc2-6812-4036-88b0-1c88d390502c" version="1" xmlns="http://cyclonedx.org/schema/bom/1.6"> +<bom serialNumber="urn:uuid:b102ed07-8561-4e8d-9124-2fbaeda2af87" version="1" xmlns="http://cyclonedx.org/schema/bom/1.6"> <metadata> - <timestamp>2026-05-08T16:48:44Z</timestamp> + <timestamp>2026-05-08T19:48:59Z</timestamp> <lifecycles> <lifecycle> <phase>build</phase> @@ -24,14 +24,14 @@ <version>0.1alpha</version> <description>Apache CycloneDX Antlib</description> <hashes> - <hash alg="MD5">6d0e2adf8c544249288e7ee596c566e9</hash> - <hash alg="SHA-1">48d86c01594b9ac882d4153bf41e5fc55b620faa</hash> - <hash alg="SHA-256">4219d857d3100fd4ec043318f63836bafddedf4181b0767774ee1cf4916f8a2d</hash> - <hash alg="SHA-512">2347e3843020e10bc1bb5b3d255d105ae4403a832398406c541b7bcf45eef73bfed63bb7181cddae056f386faba9effdd5e83a48692db48df79307b4e39688ba</hash> - <hash alg="SHA3-256">6f64a5f69c6a2dc750cef5143c66beaf3aa19600716ed63cbb0afa980c5b46fb</hash> - <hash alg="SHA3-512">6859d0d58ea10a43f7704c1617a73609522763d4460ad5c52f067374bc995f7aa3a3d43851015625bcddaf354c528e79b38a5d7c9cb36272afa2a98b9b05c966</hash> - <hash alg="SHA-384">72d5b1dfaa25985a0891d763ef8b65169a58f67ca3b47864f3aa16204649247e8d6f8c0654959553ae12e6d3bb564a81</hash> - <hash alg="SHA3-384">56afebe15d38d52e2f80580659f0558f618deba550f0139f9a72b6254afe9f3a90bb3cb9837568ca8f93a8495c2ba2f3</hash> + <hash alg="MD5">cd60dca84b42bad257fd776c541a937d</hash> + <hash alg="SHA-1">f27f9d1e378987673e96868fab2c64ff076310c7</hash> + <hash alg="SHA-256">ad7490c23be951353bfdc299269816c87c680f2b08a0461d55bde3689d291234</hash> + <hash alg="SHA-512">80784ae9b36496e7b5a70e91dfe0280d3437d79a211e2a6b8f761b77f0407494752ecbee9e7b6d5a2c014e04b7069b4dfc36a684fb9a11c982c59913d7ff97e6</hash> + <hash alg="SHA3-256">1c6387b9e32013e79dfb420bc005e6dff736546152a95bbd6673b07a413b78f7</hash> + <hash alg="SHA3-512">f57f015857b33731781a646b5131037897dc23f2e1a9e202984d5ac9a484d3843bbc9e1216aee46b4a36da213323f2d1f0f79dfa1e69f788d7bc538adfad9bc1</hash> + <hash alg="SHA-384">e93b7acd21cbb36b41b273582b4c3a58828d0e908261549fdef3da0ebdce4e573cfd65e0406c0186cb6d8e76552347c3</hash> + <hash alg="SHA3-384">241ac570fb9fc5e5ba1b7362df02167d214be3e7e6e371fe90ce4312bf63c7cd5ba3d988b5bd276040e94a4fdc23464c</hash> </hashes> <licenses> <license> @@ -60,19 +60,20 @@ <name>Apache Ant Development Team</name> <url>https://ant.apache.org/</url> </manufacturer> + <publisher>The Apache Software Foundation</publisher> <group>org.apache.ant</group> <name>ant-cyclonedx</name> <version>0.1alpha</version> <description>Apache CycloneDX Antlib</description> <hashes> - <hash alg="MD5">6d0e2adf8c544249288e7ee596c566e9</hash> - <hash alg="SHA-1">48d86c01594b9ac882d4153bf41e5fc55b620faa</hash> - <hash alg="SHA-256">4219d857d3100fd4ec043318f63836bafddedf4181b0767774ee1cf4916f8a2d</hash> - <hash alg="SHA-512">2347e3843020e10bc1bb5b3d255d105ae4403a832398406c541b7bcf45eef73bfed63bb7181cddae056f386faba9effdd5e83a48692db48df79307b4e39688ba</hash> - <hash alg="SHA3-256">6f64a5f69c6a2dc750cef5143c66beaf3aa19600716ed63cbb0afa980c5b46fb</hash> - <hash alg="SHA3-512">6859d0d58ea10a43f7704c1617a73609522763d4460ad5c52f067374bc995f7aa3a3d43851015625bcddaf354c528e79b38a5d7c9cb36272afa2a98b9b05c966</hash> - <hash alg="SHA-384">72d5b1dfaa25985a0891d763ef8b65169a58f67ca3b47864f3aa16204649247e8d6f8c0654959553ae12e6d3bb564a81</hash> - <hash alg="SHA3-384">56afebe15d38d52e2f80580659f0558f618deba550f0139f9a72b6254afe9f3a90bb3cb9837568ca8f93a8495c2ba2f3</hash> + <hash alg="MD5">cd60dca84b42bad257fd776c541a937d</hash> + <hash alg="SHA-1">f27f9d1e378987673e96868fab2c64ff076310c7</hash> + <hash alg="SHA-256">ad7490c23be951353bfdc299269816c87c680f2b08a0461d55bde3689d291234</hash> + <hash alg="SHA-512">80784ae9b36496e7b5a70e91dfe0280d3437d79a211e2a6b8f761b77f0407494752ecbee9e7b6d5a2c014e04b7069b4dfc36a684fb9a11c982c59913d7ff97e6</hash> + <hash alg="SHA3-256">1c6387b9e32013e79dfb420bc005e6dff736546152a95bbd6673b07a413b78f7</hash> + <hash alg="SHA3-512">f57f015857b33731781a646b5131037897dc23f2e1a9e202984d5ac9a484d3843bbc9e1216aee46b4a36da213323f2d1f0f79dfa1e69f788d7bc538adfad9bc1</hash> + <hash alg="SHA-384">e93b7acd21cbb36b41b273582b4c3a58828d0e908261549fdef3da0ebdce4e573cfd65e0406c0186cb6d8e76552347c3</hash> + <hash alg="SHA3-384">241ac570fb9fc5e5ba1b7362df02167d214be3e7e6e371fe90ce4312bf63c7cd5ba3d988b5bd276040e94a4fdc23464c</hash> </hashes> <licenses> <license> diff --git a/src/main/org/apache/ant/cyclonedx/Component.java b/src/main/org/apache/ant/cyclonedx/Component.java index 6c8f457..c34f820 100644 --- a/src/main/org/apache/ant/cyclonedx/Component.java +++ b/src/main/org/apache/ant/cyclonedx/Component.java @@ -48,6 +48,7 @@ public class Component extends DataType { private List<org.cyclonedx.model.ExternalReference> externalReferences = new ArrayList<>(); private org.cyclonedx.model.Component.Scope scope; private boolean isExternal = false; + private List<Component> nestedComponents = new ArrayList<>(); private List<Dependency> dependencies = new ArrayList<>(); private boolean unknownDependencies = false; private boolean sbomLinkResolved = false; @@ -221,9 +222,29 @@ public class Component extends DataType { if (isReference()) { return getRef().getDependencies(); } + dieOnCircularReference(); return dependencies; } + public void addComponent(Component c) { + checkChildrenAllowed(); + nestedComponents.add(c); + } + + public List<Component> getNestedComponents() { + if (isReference()) { + return getRef().getNestedComponents(); + } + dieOnCircularReference(); + List<Component> result = new ArrayList<>(); + result.addAll(nestedComponents); + result.addAll(nestedComponents + .stream() + .flatMap(c -> c.getNestedComponents().stream()) + .collect(Collectors.toList())); + return result; + } + public void setUnknownDependencies(boolean unknownDependencies) { checkAttributesAllowed(); this.unknownDependencies = unknownDependencies; @@ -356,6 +377,7 @@ public class Component extends DataType { private org.cyclonedx.model.Component toCycloneDxComponent(Version bomVersion) throws IOException { + dieOnCircularReference(); if (name == null) { throw new BuildException("component name is required"); } @@ -429,6 +451,9 @@ public class Component extends DataType { if (!externalReferences.isEmpty()) { component.setExternalReferences(externalReferences); } + for (Component c : nestedComponents) { + component.addComponent(c.toAdditionalCycloneDxComponent(bomVersion)); + } // add isExternal once VERSION_17 is supported by cyclonedx-java-core addHashes(component, bomVersion); return component; @@ -475,6 +500,13 @@ public class Component extends DataType { tags.clear(); tags.addAll(real.getTags().getTags()); } + if (real.getComponents() != null) { + nestedComponents.clear(); + nestedComponents.addAll(real.getComponents() + .stream() + .map(Component::from) + .collect(Collectors.toList())); + } } private void addHashes(org.cyclonedx.model.Component component, Version bomVersion) diff --git a/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java b/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java index d811490..ae1c2e2 100644 --- a/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java +++ b/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java @@ -130,7 +130,7 @@ public class ComponentBomTask extends Task { throw new BuildException("nested component element is required"); } Set<String> knownComponents = new HashSet<>(); - knownComponents.add(component.getGroup() + ":" + component.getName()); + addToKnownComponents(knownComponents, component); meta.setComponent(component.toMainCycloneDxComponent(specVersion.getVersion())); if (useComponentSupplier) { OrganizationalEntity componentSupplier = meta.getComponent().getSupplier(); @@ -152,14 +152,14 @@ public class ComponentBomTask extends Task { List<org.cyclonedx.model.Component> cs = new ArrayList<>(); List<Component> resolvedComponents = new ArrayList<>(); for (Component c : additionalComponents) { - knownComponents.add(c.getGroup() + ":" + c.getName()); + addToKnownComponents(knownComponents, c); resolvedComponents.addAll(c.resolve()); cs.add(c.toAdditionalCycloneDxComponent(specVersion.getVersion())); } for (Component c : resolvedComponents) { String componentKey = c.getGroup() + ":" + c.getName(); if (!knownComponents.contains(componentKey)) { - knownComponents.add(componentKey); + addToKnownComponents(knownComponents, c); cs.add(c.toAdditionalCycloneDxComponent(specVersion.getVersion())); } } @@ -214,6 +214,12 @@ public class ComponentBomTask extends Task { bom.setDependencies(dependencies); } + private void addToKnownComponents(Set<String> knownComponents, Component component) { + knownComponents.add(component.getGroup() + ":" + component.getName()); + component.getNestedComponents().stream() + .forEach(c -> addToKnownComponents(knownComponents, c)); + } + private void writeBom(Bom bom, Format format, File bomFile) throws IOException, GeneratorException { switch (format) { diff --git a/src/tests/antunit/componentbom-test.xml b/src/tests/antunit/componentbom-test.xml index 1fa123d..0679bdb 100644 --- a/src/tests/antunit/componentbom-test.xml +++ b/src/tests/antunit/componentbom-test.xml @@ -272,6 +272,7 @@ </manufacturer> <license licenseId="Apache-2.0"/> <externalReference type="WEBSITE" url="https://example.com/"/> + <component name="other-test" group="org.example" version="1.1"/> </component> </cdx:componentbom> <xmlproperty file="${output}/bom.xml"/> @@ -315,6 +316,30 @@ xmlns:au="antlib:org.apache.ant.antunit" resource="${output}/bom.xml" value='<hash alg="SHA-256">${ant.file.sha256}</hash>'/> + <au:assertPropertyEquals + xmlns:au="antlib:org.apache.ant.antunit" + name="bom.metadata.component.components.component.name" + value="other-test"/> + <au:assertPropertyEquals + xmlns:au="antlib:org.apache.ant.antunit" + name="bom.metadata.component.components.component(type)" + value="library"/> + <au:assertPropertyEquals + xmlns:au="antlib:org.apache.ant.antunit" + name="bom.metadata.component.components.component.group" + value="org.example"/> + <au:assertPropertyEquals + xmlns:au="antlib:org.apache.ant.antunit" + name="bom.metadata.component.components.component.version" + value="1.1"/> + <au:assertPropertyEquals + xmlns:au="antlib:org.apache.ant.antunit" + name="bom.metadata.component.components.component.purl" + value="pkg:maven/org.example/[email protected]?type=jar"/> + <au:assertPropertyEquals + xmlns:au="antlib:org.apache.ant.antunit" + name="bom.metadata.component.components.component(bom-ref)" + value="pkg:maven/org.example/[email protected]?type=jar"/> <au:assertResourceContains xmlns:au="antlib:org.apache.ant.antunit" resource="${output}/bom.xml" @@ -393,6 +418,7 @@ group="org.apache.ant" version="${artifact.version}" description="Apache CycloneDX Antlib" + publisher="The Apache Software Foundation" manufacturerIsSupplier="true"> <file file="${antlib.location}"/> <manufacturer refid="ant-team"/>
