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 2278e0992d71bcf9f80ad2bf2dfb5516a8eafe11 Author: Stefan Bodewig <[email protected]> AuthorDate: Fri May 1 21:39:35 2026 +0200 support dependencies --- src/main/org/apache/ant/cyclonedx/Component.java | 59 ++++++++++++++++++++++ .../org/apache/ant/cyclonedx/ComponentBomTask.java | 45 +++++++++++++++++ src/tests/antunit/componentbom-test.xml | 35 ++++++++++--- 3 files changed, 132 insertions(+), 7 deletions(-) diff --git a/src/main/org/apache/ant/cyclonedx/Component.java b/src/main/org/apache/ant/cyclonedx/Component.java index 8dd72a1..26d3fb5 100644 --- a/src/main/org/apache/ant/cyclonedx/Component.java +++ b/src/main/org/apache/ant/cyclonedx/Component.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.List; import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.ProjectComponent; import org.apache.tools.ant.types.Resource; import org.apache.tools.ant.types.resources.FileProvider; import org.apache.tools.ant.types.resources.URLResource; @@ -29,6 +30,8 @@ public class Component { private List<org.cyclonedx.model.ExternalReference> externalReferences = new ArrayList<>(); private org.cyclonedx.model.Component.Scope scope; private boolean isExternal = false; + private List<Dependency> dependencies = new ArrayList<>(); + private boolean unknownDependencies = false; public void add(Resource resource) { if (this.resource != null) { @@ -106,6 +109,22 @@ public class Component { this.isExternal = isExternal; } + public void addDependency(Dependency d) { + dependencies.add(d); + } + + public Iterable<Dependency> getDependencies() { + return dependencies; + } + + public void setUnknownDependencies(boolean unknownDependencies) { + this.unknownDependencies = unknownDependencies; + } + + public boolean areDependenciesUnknown() { + return unknownDependencies; + } + public org.cyclonedx.model.Component toMainCycloneDxComponent(Version bomVersion) throws IOException { if (isExternal) { @@ -157,6 +176,8 @@ public class Component { String bomRef = getBomRef(); if (bomRef != null) { component.setBomRef(bomRef); + } else if (!dependencies.isEmpty()) { + throw new BuildException("a component with dependencies must provide a bomRef"); } if (!externalReferences.isEmpty()) { component.setExternalReferences(externalReferences); @@ -264,4 +285,42 @@ public class Component { return r; } } + + public static class Dependency extends ProjectComponent { + private String bomRef; + private String componentRef; + + public void setBomRef(String bomRef) { + this.bomRef = bomRef; + } + + public void setComponentRef(String componentRef) { + this.componentRef = componentRef; + } + + public String getBomRef() { + if (bomRef == null && componentRef == null) { + throw new BuildException("bomRef or componentRef is required"); + } + if (bomRef != null && componentRef != null) { + throw new BuildException("only one of bomRef and componentRef are permitted"); + } + if (bomRef != null) { + return bomRef; + } + + Object component = getProject().getReference(componentRef); + if (component == null) { + throw new BuildException("componentRef '" + componentRef + "' is unknown"); + } + if (component instanceof Component) { + String b = ((Component) component).getBomRef(); + if (b == null) { + throw new BuildException("component with id '" + componentRef + "' doesn't provide a bomRef"); + } + return b; + } + throw new BuildException("componentRef '" + componentRef + "' doesn't refer to a component"); + } + } } diff --git a/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java b/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java index 027371d..8bb5528 100644 --- a/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java +++ b/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java @@ -8,7 +8,9 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.UUID; import org.apache.tools.ant.BuildException; @@ -21,6 +23,7 @@ import org.cyclonedx.generators.BomGeneratorFactory; import org.cyclonedx.generators.json.BomJsonGenerator; import org.cyclonedx.generators.xml.BomXmlGenerator; import org.cyclonedx.model.Bom; +import org.cyclonedx.model.Dependency; import org.cyclonedx.model.LifecycleChoice; import org.cyclonedx.model.Lifecycles; import org.cyclonedx.model.Metadata; @@ -93,9 +96,51 @@ public class ComponentBomTask extends Task { bom.setComponents(cs); } + addDependencies(bom); + return bom; } + private void addDependencies(Bom bom) { + List<Dependency> dependencies = new ArrayList<>(); + Set<String> bomRefs = new HashSet<>(); + if (component.getBomRef() != null) { + bomRefs.add(component.getBomRef()); + } + for (Component c : additionalComponents) { + if (c.getBomRef() != null) { + bomRefs.add(c.getBomRef()); + } + } + + if (component.getBomRef() != null) { + Dependency dep = new Dependency(component.getBomRef()); + for (Component.Dependency d : component.getDependencies()) { + String br = d.getBomRef(); + if (!bomRefs.contains(br)) { + throw new BuildException("dependency '" + br + "' is unknown"); + } + dep.addDependency(new Dependency(br)); + } + dependencies.add(dep); + } + for (Component c : additionalComponents) { + if (!c.areDependenciesUnknown() && c.getBomRef() != null) { + Dependency dep = new Dependency(c.getBomRef()); + for (Component.Dependency d : c.getDependencies()) { + String br = d.getBomRef(); + if (!bomRefs.contains(br)) { + throw new BuildException("dependency '" + br + "' is unknown"); + } + dep.addDependency(new Dependency(br)); + } + dependencies.add(dep); + } + } + + bom.setDependencies(dependencies); + } + private void writeBom(Bom bom, File bomFile) throws IOException, GeneratorException { switch (format) { case JSON: diff --git a/src/tests/antunit/componentbom-test.xml b/src/tests/antunit/componentbom-test.xml index 1c6ed7c..dfb61d0 100644 --- a/src/tests/antunit/componentbom-test.xml +++ b/src/tests/antunit/componentbom-test.xml @@ -268,18 +268,11 @@ <target name="testMinimalAdditionalComponentData"> <mkdir dir="${output}"/> - <cdx:componentbom bomfile="${output}/bom.json" format="JSON" - xmlns:cdx="antlib:org.apache.ant.cyclonedx"> - <component name="testname"/> - <additionalComponent name="dependency" scope="OPTIONAL"/> - </cdx:componentbom> - <copy file="${output}/bom.json" todir="/tmp"/> <cdx:componentbom bomfile="${output}/bom.xml" format="XML" xmlns:cdx="antlib:org.apache.ant.cyclonedx"> <component name="testname"/> <additionalComponent name="dependency" scope="OPTIONAL"/> </cdx:componentbom> - <copy file="${output}/bom.xml" todir="/tmp"/> <xmlproperty file="${output}/bom.xml"/> <au:assertPropertyEquals xmlns:au="antlib:org.apache.ant.antunit" @@ -295,4 +288,32 @@ value="optional"/> </target> + <target name="testDependencies"> + <mkdir dir="${output}"/> + <cdx:componentbom bomfile="${output}/bom.xml" format="XML" + xmlns:cdx="antlib:org.apache.ant.cyclonedx"> + <component name="testname" group="org.example" version="1.0"> + <dependency bomRef="pkg:maven/org.example/[email protected]?type=jar"/> + </component> + <additionalComponent + name="dependency" group="org.example" version="1.0"> + <dependency componentRef="transitve"/> + </additionalComponent> + <additionalComponent + name="transitive-dependency" group="org.example" + version="1.0" id="transitve" + unknownDependencies="true"/> + <additionalComponent name="empty" group="org.example" version="1.0"/> + </cdx:componentbom> + <xmlproperty file="${output}/bom.xml"/> + <au:assertPropertyEquals + xmlns:au="antlib:org.apache.ant.antunit" + name="bom.dependencies.dependency(ref)" + value="pkg:maven/org.example/[email protected]?type=jar,pkg:maven/org.example/[email protected]?type=jar,pkg:maven/org.example/[email protected]?type=jar"/> + <au:assertPropertyEquals + xmlns:au="antlib:org.apache.ant.antunit" + name="bom.dependencies.dependency.dependency(ref)" + value="pkg:maven/org.example/[email protected]?type=jar,pkg:maven/org.example/[email protected]?type=jar"/> + </target> + </project>
