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 2323b40 configure the component the bom is about
2323b40 is described below
commit 2323b403d7e3948dbd60e54b041b1c10c009baef
Author: Stefan Bodewig <[email protected]>
AuthorDate: Fri May 1 15:23:24 2026 +0200
configure the component the bom is about
---
src/main/org/apache/ant/cyclonedx/Component.java | 205 +++++++++++++++++++++
.../org/apache/ant/cyclonedx/ComponentBomTask.java | 14 ++
src/main/org/apache/ant/cyclonedx/ToolData.java | 29 ++-
src/tests/antunit/componentbom-test.xml | 168 ++++++++++++++++-
4 files changed, 396 insertions(+), 20 deletions(-)
diff --git a/src/main/org/apache/ant/cyclonedx/Component.java
b/src/main/org/apache/ant/cyclonedx/Component.java
new file mode 100644
index 0000000..39242b3
--- /dev/null
+++ b/src/main/org/apache/ant/cyclonedx/Component.java
@@ -0,0 +1,205 @@
+package org.apache.ant.cyclonedx;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.Resource;
+import org.apache.tools.ant.types.resources.FileProvider;
+import org.apache.tools.ant.types.resources.URLResource;
+
+import org.cyclonedx.Version;
+import org.cyclonedx.model.LicenseChoice;
+import org.cyclonedx.model.OrganizationalEntity;
+import org.cyclonedx.util.BomUtils;
+
+public class Component {
+ private Resource resource;
+ private org.cyclonedx.model.Component.Type type =
org.cyclonedx.model.Component.Type.LIBRARY;
+ private String name;
+ private String group;
+ private String version;
+ private String description;
+ private Manufacturer manufacturer = null;
+ private List<org.cyclonedx.model.License> licenses = new ArrayList<>();
+ private String purl;
+ private String bomRef;
+
+ public void add(Resource resource) {
+ if (this.resource != null) {
+ throw new BuildException("component can only be defined for a
single asset");
+ }
+ this.resource = resource;
+ }
+
+ public void setType(org.cyclonedx.model.Component.Type type) {
+ this.type = type;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setGroup(String group) {
+ this.group = group;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public Manufacturer createManufacturer() {
+ if (manufacturer != null) {
+ throw new BuildException("component can only have one
manufacturer");
+ }
+ manufacturer = new Manufacturer();
+ return manufacturer;
+ }
+
+ public void addConfiguredLicense(License l) {
+ licenses.add(l.toCycloneDxLicense());
+ }
+
+ public void setPurl(String purl) {
+ this.purl = purl;
+ }
+
+ public String getPurl() {
+ if (purl != null) {
+ return purl;
+ }
+ if (group != null && name != null && version != null) {
+ return "pkg:maven/" + group + "/" + name + "@" + version +
"?type=jar";
+ }
+ return null;
+ }
+
+ public void setBomRef(String bomRef) {
+ this.bomRef = bomRef;
+ }
+
+ public String getBomRef() {
+ if (bomRef == null) {
+ return getPurl();
+ }
+ return bomRef;
+ }
+
+ public org.cyclonedx.model.Component toCycloneDxComponent(Version
bomVersion)
+ throws IOException {
+ if (name == null) {
+ throw new BuildException("component name is required");
+ }
+
+ org.cyclonedx.model.Component component = new
org.cyclonedx.model.Component();
+
+ component.setType(type);
+ component.setName(name);
+ if (group != null) {
+ component.setGroup(group);
+ }
+ if (version != null) {
+ component.setVersion(version);
+ }
+ if (description != null) {
+ component.setDescription(description);
+ }
+ if (manufacturer != null) {
+ component.setManufacturer(manufacturer.toOrganizationalEntity());
+ }
+ if (!licenses.isEmpty()) {
+ LicenseChoice lc = new LicenseChoice();
+ lc.setLicenses(licenses);
+ component.setLicenses(lc);
+ }
+ String purl = getPurl();
+ if (purl != null) {
+ component.setPurl(purl);
+ }
+ String bomRef = getBomRef();
+ if (bomRef != null) {
+ component.setBomRef(bomRef);
+ }
+ addHashes(component, bomVersion);
+ return component;
+ }
+
+ private void addHashes(org.cyclonedx.model.Component component, Version
bomVersion)
+ throws IOException {
+ if (resource == null) {
+ return;
+ }
+
+ if (!resource.isFilesystemOnly()) {
+ throw new BuildException("component resource must be a file system
resource");
+ }
+
+ File file = null;
+ FileProvider fp = resource.as(FileProvider.class);
+ if (fp != null) {
+ file = fp.getFile();
+ }
+ if (file == null || !file.isFile()) {
+ throw new BuildException("component resource doesn't provide a
file");
+ }
+
+ component.setHashes(BomUtils.calculateHashes(file, bomVersion));
+ }
+
+ public static class Manufacturer {
+ private String name;
+ private List<String> urls = new ArrayList<>();
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void addConfiguredUrl(URLResource url) {
+ urls.add(url.getURL().toExternalForm());
+ }
+
+ public OrganizationalEntity toOrganizationalEntity() {
+ OrganizationalEntity oe = new OrganizationalEntity();
+ if (name != null) {
+ oe.setName(name);
+ }
+ if (!urls.isEmpty()) {
+ oe.setUrls(urls);
+ }
+ return oe;
+ }
+ }
+
+ public static class License {
+ private String id;
+ private String name;
+
+ public void setLicenseId(String id) {
+ this.id = id;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public org.cyclonedx.model.License toCycloneDxLicense() {
+ if (name == null && id == null) {
+ throw new BuildException("license name or id is required");
+ }
+ org.cyclonedx.model.License l = new org.cyclonedx.model.License();
+ if (name != null) {
+ l.setName(name);
+ }
+ if (id != null) {
+ l.setId(id);
+ }
+ return l;
+ }
+ }
+}
diff --git a/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java
b/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java
index a081757..0615bf0 100644
--- a/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java
+++ b/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java
@@ -30,6 +30,7 @@ public class ComponentBomTask extends Task {
private File bomFile;
private Format format = Format.JSON;
+ private Component component;
public void setBomFile(File f) {
bomFile = f;
@@ -39,6 +40,14 @@ public class ComponentBomTask extends Task {
this.format = format;
}
+ public Component createComponent() {
+ if (component != null) {
+ throw new BuildException("only one nested component element is
permitted");
+ }
+ component = new Component();
+ return component;
+ }
+
public void execute() {
try {
Bom bom = createBom();
@@ -62,6 +71,11 @@ public class ComponentBomTask extends Task {
l.setLifecycleChoice(Collections.singletonList(lc));
meta.setLifecycles(l);
+ if (component == null) {
+ throw new BuildException("nested component element is required");
+ }
+ meta.setComponent(component.toCycloneDxComponent(Version.VERSION_16));
+
bom.setMetadata(meta);
return bom;
}
diff --git a/src/main/org/apache/ant/cyclonedx/ToolData.java
b/src/main/org/apache/ant/cyclonedx/ToolData.java
index 6cdcd7e..5ac2b40 100644
--- a/src/main/org/apache/ant/cyclonedx/ToolData.java
+++ b/src/main/org/apache/ant/cyclonedx/ToolData.java
@@ -8,13 +8,11 @@ import java.security.CodeSource;
import java.util.Collections;
import java.util.Properties;
+import org.apache.tools.ant.types.resources.FileResource;
+import org.apache.tools.ant.types.resources.URLResource;
+
import org.cyclonedx.Version;
-import org.cyclonedx.model.Component;
-import org.cyclonedx.model.License;
-import org.cyclonedx.model.LicenseChoice;
-import org.cyclonedx.model.OrganizationalEntity;
import org.cyclonedx.model.metadata.ToolInformation;
-import org.cyclonedx.util.BomUtils;
/**
* Provides tool information for BOM's metadata section.
@@ -34,29 +32,28 @@ public class ToolData {
ToolInformation tool = new ToolInformation();
Component antlibComponent = new Component();
- antlibComponent.setType(Component.Type.LIBRARY);
antlibComponent.setGroup("org.apache.ant");
antlibComponent.setName("ant-cyclonedx");
antlibComponent.setVersion(getVersion());
antlibComponent.setDescription("Apache CycloneDX Antlib");
- OrganizationalEntity manufacturer = new OrganizationalEntity();
+ Component.Manufacturer manufacturer =
antlibComponent.createManufacturer();
manufacturer.setName("Apache Ant Development Team");
-
manufacturer.setUrls(Collections.singletonList("https://ant.apache.org/"));
- antlibComponent.setManufacturer(manufacturer);
+ manufacturer.addConfiguredUrl(new
URLResource("https://ant.apache.org/"));
- LicenseChoice lc = new LicenseChoice();
- License license = new License();
- license.setId("Apache-2.0");
- lc.setLicenses(Collections.singletonList(license));
- antlibComponent.setLicenses(lc);
+ Component.License license = new Component.License();
+ license.setLicenseId("Apache-2.0");
+ antlibComponent.addConfiguredLicense(license);
File antlib = findAntlib();
if (antlib != null) {
- antlibComponent.setHashes(BomUtils.calculateHashes(antlib,
Version.VERSION_16));
+ antlibComponent.add(new FileResource(antlib));
}
- tool.setComponents(Collections.singletonList(antlibComponent));
+ org.cyclonedx.model.Component cdxComponent =
+ antlibComponent.toCycloneDxComponent(Version.VERSION_16);
+ cdxComponent.setBomRef(null);
+ tool.setComponents(Collections.singletonList(cdxComponent));
return tool;
}
diff --git a/src/tests/antunit/componentbom-test.xml
b/src/tests/antunit/componentbom-test.xml
index c3c0ac4..7d1e1f2 100644
--- a/src/tests/antunit/componentbom-test.xml
+++ b/src/tests/antunit/componentbom-test.xml
@@ -19,11 +19,16 @@
<import file="shared.xml" />
+ <checksum property="sha256hash" file="${antlib.location}"
algorithm="SHA-256"/>
+
<target name="testToolMetadataInJsonFormat">
- <checksum property="sha256hash" file="${antlib.location}"
algorithm="SHA-256"/>
<mkdir dir="${output}"/>
<cdx:componentbom bomfile="${output}/bom.json"
- xmlns:cdx="antlib:org.apache.ant.cyclonedx"/>
+ xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+ <component name="testname">
+ <file file="${antlib.location}"/>
+ </component>
+ </cdx:componentbom>
<au:assertResourceContains
xmlns:au="antlib:org.apache.ant.antunit"
resource="${output}/bom.json"
@@ -47,10 +52,13 @@
</target>
<target name="testToolMetadataInXmlFormat">
- <checksum property="sha256hash" file="${antlib.location}"
algorithm="SHA-256"/>
<mkdir dir="${output}"/>
<cdx:componentbom bomfile="${output}/bom.xml" format="XML"
- xmlns:cdx="antlib:org.apache.ant.cyclonedx"/>
+ xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+ <component name="testname">
+ <file file="${antlib.location}"/>
+ </component>
+ </cdx:componentbom>
<xmlproperty file="${output}/bom.xml"/>
<au:assertPropertyEquals
xmlns:au="antlib:org.apache.ant.antunit"
@@ -93,4 +101,156 @@
resource="${output}/bom.xml"
value='<hash alg="SHA-256">${sha256hash}</hash>'/>
</target>
+
+ <target name="testComponentIsRequired">
+ <au:expectfailure expectedMessage="nested component element is required"
+ xmlns:au="antlib:org.apache.ant.antunit">
+ <cdx:componentbom bomfile="${output}/bom.xml" format="XML"
+ xmlns:cdx="antlib:org.apache.ant.cyclonedx"/>
+ </au:expectfailure>
+ </target>
+
+ <target name="testOnlyOneComponentIsPermitted">
+ <au:expectfailure expectedMessage="only one nested component element is
permitted"
+ xmlns:au="antlib:org.apache.ant.antunit">
+ <cdx:componentbom bomfile="${output}/bom.xml" format="XML"
+ xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+ <component/>
+ <component/>
+ </cdx:componentbom>
+ </au:expectfailure>
+ </target>
+
+ <target name="testComponentNameIsRequired">
+ <au:expectfailure expectedMessage="component name is required"
+ xmlns:au="antlib:org.apache.ant.antunit">
+ <cdx:componentbom bomfile="${output}/bom.xml" format="XML"
+ xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+ <component/>
+ </cdx:componentbom>
+ </au:expectfailure>
+ </target>
+
+ <target name="testMinimalComponentData">
+ <mkdir dir="${output}"/>
+ <cdx:componentbom bomfile="${output}/bom.xml" 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.metadata.component.name"
+ value="testname"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.metadata.component(type)"
+ value="library"/>
+ </target>
+
+ <target name="testComponentTypeCanBeSet">
+ <mkdir dir="${output}"/>
+ <cdx:componentbom bomfile="${output}/bom.xml" format="XML"
+ xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+ <component name="testname" type="APPLICATION"/>
+ </cdx:componentbom>
+ <xmlproperty file="${output}/bom.xml"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.metadata.component(type)"
+ value="application"/>
+ </target>
+
+ <target name="testCalculatesPurlAndBomRef">
+ <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"/>
+ </cdx:componentbom>
+ <xmlproperty file="${output}/bom.xml"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.metadata.component.purl"
+ value="pkg:maven/org.example/[email protected]?type=jar"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.metadata.component(bom-ref)"
+ value="pkg:maven/org.example/[email protected]?type=jar"/>
+ </target>
+
+ <target name="testPurlAndBomRefCanBeSet">
+ <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"
+ purl="foo" bomRef="bar"/>
+ </cdx:componentbom>
+ <xmlproperty file="${output}/bom.xml"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.metadata.component.purl"
+ value="foo"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.metadata.component(bom-ref)"
+ value="bar"/>
+ </target>
+
+ <target name="testMaximalComponentData">
+ <mkdir dir="${output}"/>
+ <checksum property="ant.file.sha256" file="${ant.file}"
algorithm="SHA-256"/>
+ <cdx:componentbom bomfile="${output}/bom.xml" format="XML"
+ xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+ <component name="testname" group="org.example" version="1.0"
+ description="My Test Library">
+ <file file="${ant.file}"/>
+ <manufacturer name="Example">
+ <url url="https://example.org/"/>
+ </manufacturer>
+ <license licenseId="Apache-2.0"/>
+ </component>
+ </cdx:componentbom>
+ <xmlproperty file="${output}/bom.xml"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.metadata.component.name"
+ value="testname"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.metadata.component(type)"
+ value="library"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.metadata.component.group"
+ value="org.example"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.metadata.component.version"
+ value="1.0"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.metadata.component.purl"
+ value="pkg:maven/org.example/[email protected]?type=jar"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.metadata.component(bom-ref)"
+ value="pkg:maven/org.example/[email protected]?type=jar"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.metadata.component.manufacturer.name"
+ value="Example"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.metadata.component.manufacturer.url"
+ value="https://example.org/"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.metadata.component.licenses.license.id"
+ value="Apache-2.0"/>
+ <au:assertResourceContains
+ xmlns:au="antlib:org.apache.ant.antunit"
+ resource="${output}/bom.xml"
+ value='<hash alg="SHA-256">${ant.file.sha256}</hash>'/>
+ </target>
+
</project>