Apache CycloneDX Ant Library 0.2 快速入门与集成示例
Apache CycloneDX Ant Library 0.2 快速入门与集成示例,覆盖 taskdef 装配 antlib、componentbom 生成 JSON/XML 双格式最小 SBOM、产物内容逐段解读、MD5/语法双重校验、四个集成坑实测排障与带依赖图的进阶示例,命令回显均实测通过。
Apache CycloneDX Ant Library 0.2 快速入门与集成示例
目标:在已装好 Apache CycloneDX Ant Library 0.2 的 Ant 环境里,用一段可复制的 build.xml 生成第一份真实的 CycloneDX SBOM(JSON + XML 双格式),看懂产物内容并完成双重校验;本文全部命令与回显均已在 Ubuntu 22.04 实测通过。
本文承接本专栏前两篇安装指南(Ubuntu 22.04 篇与 CentOS 8 篇),默认你已经按安装篇把 antlib 装到 /opt/cyclonedx-antlib/lib(Maven 坐标 org.apache.ant:ant-cyclonedx:0.2 及其依赖 jar)。如果你还没有这一步,请先回到安装篇,装好后即可直接照抄本文。全文围绕一个最小演示工程 notes-app 展开:第二节十分钟跑通最小 SBOM 并逐段解读产物;第三节收录四个集成坑的真实报错与修法;第四节给一个带依赖图(additionalComponent + dependency)的进阶示例;第五节给出与 Maven/Gradle 生态工具的选型对照。
一、环境信息
本节先固定可复现的基线环境,后续所有命令均在此环境执行。
- 操作系统:Ubuntu 22.04.5 LTS
- 架构:x86_64
- JDK:OpenJDK 17.0.20.1(apt 包
openjdk-17-jdk) - 构建工具:Apache Ant 1.10.12(apt 包
ant) - 组件版本:ant-cyclonedx 0.2(Maven 坐标
org.apache.ant:ant-cyclonedx:0.2) - 核心依赖:cyclonedx-core-java 13.2.0、packageurl-java 1.5.0、Jackson 2.22.2、networknt json-schema-validator 2.0.4
- Antlib 安装目录:
/opt/cyclonedx-antlib/lib(安装篇方案 A 布局,17 个 jar) - 演示工程目录:
/root/cdx-quickstart
系统与工具确认(实时回显):
head -2 /etc/os-release
PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"
ant -version
Apache Ant(TM) version 1.10.12 compiled on October 13 2021
ls /opt/cyclonedx-antlib/lib | head -5
ant-cyclonedx-0.2.jar
commons-codec-1.20.0.jar
commons-io-2.21.0.jar
cyclonedx-core-java-13.2.0.jar
java-uuid-generator-5.1.1.jar
💡 技术拓展:
ls | head -5只是节选,完整 17 个 jar 清单见本专栏 Ubuntu 安装篇。antlib 的核心是ant-cyclonedx-0.2.jar,但它只是薄壳——SBOM 的模型、序列化与哈希全部由cyclonedx-core-java-13.2.0.jar完成,因此 taskdef 的 classpath 必须包含全部 jar(安装篇 FAQ Q4 实测过只放一个 jar 的报错)。
二、十分钟最小可用
2.1 演示工程
创建工程目录 /root/cdx-quickstart,包含一个两文件的小应用(一个 record + 一个 main):
mkdir -p /root/cdx-quickstart/src/com/example/notes
src/com/example/notes/Note.java:
package com.example.notes;
public record Note(String title, String body) {}
src/com/example/notes/NotesApp.java:
package com.example.notes;
import java.util.ArrayList;
import java.util.List;
public class NotesApp {
public static void main(String[] args) {
List<Note> notes = new ArrayList<>();
notes.add(new Note("hello", "first note"));
for (Note n : notes) {
System.out.println(n.title() + " => " + n.body());
}
System.out.println("NotesApp OK");
}
}
2.2 build.xml 全文
工程根目录的 build.xml 只做三件事:编译打包、装配 antlib、生成 SBOM。
<?xml version="1.0" encoding="UTF-8"?>
<project name="cdx-quickstart" default="sbom" xmlns:cdx="antlib:org.apache.ant.cyclonedx">
<property name="app.version" value="1.0.0"/>
<target name="compile">
<mkdir dir="build/classes"/>
<javac srcdir="src" destdir="build/classes" includeantruntime="false"/>
</target>
<target name="jar" depends="compile">
<mkdir dir="dist"/>
<jar destfile="dist/notes-app-${app.version}.jar" basedir="build/classes"/>
</target>
<target name="sbom" depends="jar">
<taskdef uri="antlib:org.apache.ant.cyclonedx"
resource="org/apache/ant/cyclonedx/antlib.xml">
<classpath>
<fileset dir="/opt/cyclonedx-antlib/lib" includes="*.jar"/>
</classpath>
</taskdef>
<mkdir dir="bom"/>
<cdx:componentbom format="all"
bomName="notes-app-${app.version}"
outputDirectory="bom">
<cdx:component type="application"
name="notes-app"
group="com.example"
version="${app.version}"
purl="pkg:maven/com.example/notes-app@${app.version}"
publisher="Example Corp"
description="Quickstart demo application">
<file file="dist/notes-app-${app.version}.jar"/>
<cdx:license licenseId="Apache-2.0"/>
<cdx:tag>release</cdx:tag>
</cdx:component>
</cdx:componentbom>
</target>
</project>
要点说明:
xmlns:cdx="antlib:org.apache.ant.cyclonedx"是 Antlib 的标准用法,之后所有 antlib 元素都以cdx:前缀书写。taskdef的uri与resource固定不变,唯一要改的是<fileset dir>指向你的 antlib 安装目录。format="all"表示同时产出 JSON 与 XML 两份;bomName决定输出文件名;outputDirectory="bom"是相对basedir的相对路径。<file>嵌套元素会让 antlib 对 jar 计算哈希并写入 SBOM;licenseId用 SPDX 标识符,cdx:tag产出 tags 数组。
2.3 运行生成
cd /root/cdx-quickstart && ant
Buildfile: /root/cdx-quickstart/build.xml
compile:
[mkdir] Created dir: /root/cdx-quickstart/build/classes
[javac] Compiling 2 source files to /root/cdx-quickstart/build/classes
jar:
[mkdir] Created dir: /root/cdx-quickstart/dist
[jar] Building jar: /root/cdx-quickstart/dist/notes-app-1.0.0.jar
sbom:
[mkdir] Created dir: /root/cdx-quickstart/bom
[cdx:componentbom] creating CycloneDX SBOM /root/cdx-quickstart/bom/notes-app-1.0.0.json
[cdx:componentbom] creating CycloneDX SBOM /root/cdx-quickstart/bom/notes-app-1.0.0.xml
BUILD SUCCESSFUL
Total time: 3 seconds
确认应用本身可运行,同时查看产物清单:
java -cp dist/notes-app-1.0.0.jar com.example.notes.NotesApp && ls -la bom/
hello => first note
NotesApp OK
total 32
drwxr-xr-x 2 root root 4096 Oct 15 06:38 .
drwxr-xr-x 6 root root 4096 Oct 15 06:39 ..
-rw-r--r-- 1 root root 5535 Oct 15 06:38 notes-app-1.0.0-deps.json
-rw-r--r-- 1 root root 6154 Oct 15 06:36 notes-app-1.0.0.json
-rw-r--r-- 1 root root 5507 Oct 15 06:36 notes-app-1.0.0.xml
💡 说明:清单里的
notes-app-1.0.0-deps.json是本文第四节进阶示例的产物,此刻只需关注notes-app-1.0.0.json与notes-app-1.0.0.xml两个文件。serialNumber 每次构建随机生成,你复现时看到的 UUID 与本文不同属正常现象。
2.4 SBOM 内容逐段解读(JSON)
先看 JSON 的头部与工具元数据段(原文截取):
head -40 bom/notes-app-1.0.0.json
{
"bomFormat" : "CycloneDX",
"specVersion" : "1.7",
"serialNumber" : "urn:uuid:8f82dd02-4968-4858-b65e-e83da0c3e94d",
"version" : 1,
"metadata" : {
"timestamp" : "2026-10-15T06:36:30Z",
"lifecycles" : [
{
"phase" : "build"
}
],
"tools" : {
"components" : [
{
"type" : "library",
"supplier" : {
"name" : "Apache Ant Project Management Committee",
"url" : [
"https://ant.apache.org/"
]
},
"manufacturer" : {
"name" : "Apache Ant Project Management Committee",
"url" : [
"https://ant.apache.org/"
]
},
"publisher" : "The Apache Software Foundation",
"group" : "org.apache.ant",
"name" : "ant-cyclonedx",
"version" : "0.2",
"description" : "Apache CycloneDX Antlib",
"hashes" : [
{
"alg" : "MD5",
"content" : "975a184759d3b4e2b028fe50d8ac7df5"
},
再定位主组件 metadata.component 段并截取(原文截取):
grep -n '"type" : "application"' bom/notes-app-1.0.0.json | tail -1
127: "type" : "application",
sed -n '127,180p' bom/notes-app-1.0.0.json
"type" : "application",
"bom-ref" : "pkg:maven/com.example/notes-app@1.0.0",
"publisher" : "Example Corp",
"group" : "com.example",
"name" : "notes-app",
"version" : "1.0.0",
"description" : "Quickstart demo application",
"hashes" : [
{
"alg" : "MD5",
"content" : "45b56eeae3abbdcbd0d08c0141112508"
},
{
"alg" : "SHA-1",
"content" : "d34743c1e0d9db2194b6cbad83ac503c3e364973"
},
{
"alg" : "SHA-256",
"content" : "1b971934c175bcc990619b3c1157082df4f0eb9d71e978ee8defcbe9a8e4c698"
},
{
"alg" : "SHA-512",
"content" : "d14e8f2ae7f1df6b206032eca4bd81f2b843cba63b2f051c9338c6700912e25f7975671c9e85fed1cb52cc7dd5953b5a06ac197051b87b9b587c9e41e8292e4a"
},
{
"alg" : "SHA3-256",
"content" : "1663f58dd670c9cba32389700cbd8b0127cab7163478dab753eec4f31497b8fb"
},
{
"alg" : "SHA3-512",
"content" : "2004189ccb1d3a73389d4cf49bebd1b76850416b5ad0714b5719b45c859e5f2636cd0a7271a60b7f97838c602db6ddf528bdee3cc1b2267a6f0ae69a9dfda5e5"
},
{
"alg" : "SHA-384",
"content" : "ef30a78ae58ea67c6c2caa2263c162c57732926f84444e12451ae888018a4c129e7acb0532e2f435e61edd11f8cf239f"
},
{
"alg" : "SHA3-384",
"content" : "dead6bc88188c56edbc49c7ee7520e0b2a952275f641e0b05608b47e16814df46005de87a6f0a69b721a15c60473d9ff"
}
],
"licenses" : [
{
"license" : {
"id" : "Apache-2.0"
}
}
],
"purl" : "pkg:maven/com.example/notes-app@1.0.0",
"tags" : [
"release"
]
}
},
观察四点:一是 antlib 对 <file> 指向的 jar 以 MD5/SHA-1/SHA-256/SHA-384/SHA-512/SHA3-256/SHA3-384/SHA3-512 八种算法计算哈希并全部写入;二是 purl 原样进入 bom-ref;三是用户声明的字段(publisher、description、license、tag)严格按 build.xml 输出,不自动扩展;四是 metadata.tools 里的工具组件由 antlib 自报家门,自带 Apache 官方元数据(供应商、制造商、许可、11 条外部引用、构建系统与安全联系方式等),这部分内容无需也无需可能由用户声明。
另外注意文件末尾还有一个 dependencies 数组(全文共 186 行),即使只有一个组件也会生成:
tail -12 bom/notes-app-1.0.0.json
"dependencies" : [
{
"ref" : "pkg:maven/com.example/notes-app@1.0.0",
"dependsOn" : [ ]
}
]
2.5 XML 产物对照
format="all" 同时产出的 XML 与 JSON 同构,看头部与工具段(原文截取):
head -30 bom/notes-app-1.0.0.xml
<?xml version="1.0" encoding="UTF-8"?>
<bom serialNumber="urn:uuid:8f82dd02-4968-4858-b65e-e83da0c3e94d" version="1" xmlns="http://cyclonedx.org/schema/bom/1.7">
<metadata>
<timestamp>2026-10-15T06:36:30Z</timestamp>
<lifecycles>
<lifecycle>
<phase>build</phase>
</lifecycle>
</lifecycles>
<tools>
<components>
<component type="library">
<supplier>
<name>Apache Ant Project Management Committee</name>
<url>https://ant.apache.org/</url>
</supplier>
<manufacturer>
<name>Apache Ant Project Management Committee</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.2</version>
<description>Apache CycloneDX Antlib</description>
<hashes>
<hash alg="MD5">975a184759d3b4e2b028fe50d8ac7df5</hash>
<hash alg="SHA-1">3849a224990de6ec7e35ec1af148e56ac5f8c1a3</hash>
<hash alg="SHA-256">e9671c599926cb6e6927dd3f9c5e47de675b00cabf0e47c49dd00d763d2cb16e</hash>
<hash alg="SHA-512">635285cf7e8fcc1ef676a6b54d34404411e6909d9f1ce228b147bd1c8f7cc0789c41a02cd8d4cdeae6885a2678e267efea37b2304e7911af32ddcaf6b04f0cef</hash>
主组件在 XML 中位于 <components> 段(原文截取):
sed -n '/type="application"/,/<\/component>/p' bom/notes-app-1.0.0.xml
<component type="application" bom-ref="pkg:maven/com.example/notes-app@1.0.0">
<publisher>Example Corp</publisher>
<group>com.example</group>
<name>notes-app</name>
<version>1.0.0</version>
<description>Quickstart demo application</description>
<hashes>
<hash alg="MD5">45b56eeae3abbdcbd0d08c0141112508</hash>
<hash alg="SHA-1">d34743c1e0d9db2194b6cbad83ac503c3e364973</hash>
<hash alg="SHA-256">1b971934c175bcc990619b3c1157082df4f0eb9d71e978ee8defcbe9a8e4c698</hash>
<hash alg="SHA-512">d14e8f2ae7f1df6b206032eca4bd81f2b843cba63b2f051c9338c6700912e25f7975671c9e85fed1cb52cc7dd5953b5a06ac197051b87b9b587c9e41e8292e4a</hash>
<hash alg="SHA3-256">1663f58dd670c9cba32389700cbd8b0127cab7163478dab753eec4f31497b8fb</hash>
<hash alg="SHA3-512">2004189ccb1d3a73389d4cf49bebd1b76850416b5ad0714b5719b45c859e5f2636cd0a7271a60b7f97838c602db6ddf528bdee3cc1b2267a6f0ae69a9dfda5e5</hash>
<hash alg="SHA-384">ef30a78ae58ea67c6c2caa2263c162c57732926f84444e12451ae888018a4c129e7acb0532e2f435e61edd11f8cf239f</hash>
<hash alg="SHA3-384">dead6bc88188c56edbc49c7ee7520e0b2a952275f641e0b05608b47e16814df46005de87a6f0a69b721a15c60473d9ff</hash>
</hashes>
<licenses>
<license>
<id>Apache-2.0</id>
</license>
</licenses>
<purl>pkg:maven/com.example/notes-app@1.0.0</purl>
<tags>
<tag>release</tag>
</tags>
</component>
💡 技术拓展:JSON 与 XML 的 serialNumber 一致(同一个 BOM 的两种序列化),XML 用命名空间
http://cyclonedx.org/schema/bom/1.7标记规范版本。两类产物的用途不同:JSON 适合被 Dependency-Track 等平台直接摄入,XML 在部分老系统(如早期版本的 OWASP Dependency-Check)中兼容性更好。
2.6 双重校验
第一重:用 md5sum 对照 SBOM 里记录的 MD5 哈希(注意 JSON 中有两处 MD5,工具组件自身的在前,用 tail 取主组件那一处):
grep -A2 '"alg" : "MD5"' bom/notes-app-1.0.0.json | tail -3 && md5sum dist/notes-app-1.0.0.jar
"alg" : "MD5",
"content" : "45b56eeae3abbdcbd0d08c0141112508"
}
45b56eeae3abbdcbd0d08c0141112508 dist/notes-app-1.0.0.jar
两值一致,说明 SBOM 哈希与实际产物可互验。第二重:语法合法性校验:
python3 -m json.tool bom/notes-app-1.0.0.json > /dev/null && echo "JSON syntax OK"
JSON syntax OK
python3 -c "import xml.dom.minidom; xml.dom.minidom.parse('bom/notes-app-1.0.0.xml'); print('XML well-formed OK')"
XML well-formed OK
💡 技术拓展:语法校验只保证「能被解析」,不保证「符合 CycloneDX 规范」。规范级校验需要用官方 JSON Schema 做验证,完整做法(含失败用例与 CI 门禁脚本)见本专栏《Apache CycloneDX Ant Library 0.2 进阶实战详解》第四章。
三、集成常见坑(实测实录)
以下四个坑全部为真实复现,报错原文照抄。它们与安装篇 FAQ(Q3 单组件限制、Q4 classpath 缺 jar、Q5 输出目录)互不重复,可以互补阅读。
坑 1:format 写了不支持的值
format 是枚举属性,合法值只有 xml、json、all。写成 yaml:
cd /root/cdx-quickstart && ant -f pit-format.xml
BUILD FAILED
/root/cdx-quickstart/pit-format.xml:15: yaml is not a legal value for this attribute
Total time: 0 seconds
修法:改回三个合法值之一。不要被「能不能输出 YAML」的想法带偏——antlib 0.2 的输出面就是 XML/JSON 两种,与官方手册一致。
坑 2:主组件上设置 scope
scope 只对 additionalComponent(被依赖方)有意义,用在主组件上直接构建失败:
cd /root/cdx-quickstart && ant -f pit-scope.xml
BUILD FAILED
/root/cdx-quickstart/pit-scope.xml:15: scope must not be set for the main bom component
Total time: 0 seconds
修法:从主组件上删除 scope 属性;给被依赖组件标注 scope="optional" 的用法见本专栏《Apache CycloneDX Ant Library 0.2 常用 API 与任务详解》第 4.7 节。
坑 3:dependency 引用了不存在的组件
componentRef 只能指向同一次 componentbom 里以 id 声明过的组件,写悬空值:
cd /root/cdx-quickstart && ant -f pit-ref.xml
BUILD FAILED
/root/cdx-quickstart/pit-ref.xml:15: Reference lib-crypto not found.
Total time: 0 seconds
修法:先补 <cdx:additionalComponent id="lib-crypto" .../>,再写依赖。这个检查是好事——它保证依赖图里不会出现「幽灵节点」。
坑 4:outputDirectory 指向不存在的目录
antlib 只写入不建目录,目标目录缺失时:
cd /root/cdx-quickstart && ant -f pit-outdir.xml
BUILD FAILED
/root/cdx-quickstart/pit-outdir.xml:14: outputDirectory must point to a directory
Total time: 0 seconds
修法:在 componentbom 之前加一行 <mkdir dir="bom-missing"/>。这也是 CI 里最常见的翻车点:换了工作目录后相对路径失效。
四、进阶示例:带依赖图的 SBOM
真实 SBOM 的核心价值在 dependencies 数组(依赖图)。下面用 additionalComponent 声明两个内部库并用 dependency 连成图:notes-app 依赖 lib-util 与 lib-log,lib-util 又依赖 lib-log。
build-depgraph.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project name="cdx-depgraph" default="sbom" xmlns:cdx="antlib:org.apache.ant.cyclonedx">
<property name="app.version" value="1.0.0"/>
<target name="sbom">
<taskdef uri="antlib:org.apache.ant.cyclonedx"
resource="org/apache/ant/cyclonedx/antlib.xml">
<classpath>
<fileset dir="/opt/cyclonedx-antlib/lib" includes="*.jar"/>
</classpath>
</taskdef>
<mkdir dir="bom"/>
<cdx:componentbom format="json"
bomName="notes-app-${app.version}-deps"
outputDirectory="bom">
<cdx:component type="application"
name="notes-app"
group="com.example"
version="${app.version}"
purl="pkg:maven/com.example/notes-app@${app.version}"
description="Notes application depending on two in-house libraries">
<dependency componentRef="lib-util"/>
<dependency componentRef="lib-log"/>
</cdx:component>
<cdx:additionalComponent id="lib-util"
type="library"
name="lib-util"
group="com.example"
version="2.1.0"
purl="pkg:maven/com.example/lib-util@2.1.0">
<dependency componentRef="lib-log"/>
</cdx:additionalComponent>
<cdx:additionalComponent id="lib-log"
type="library"
name="lib-log"
group="com.example"
version="1.3.0"
purl="pkg:maven/com.example/lib-log@1.3.0"/>
</cdx:componentbom>
</target>
</project>
运行并截取产物的 dependencies 数组(原文照抄):
cd /root/cdx-quickstart && ant -f build-depgraph.xml
Buildfile: /root/cdx-quickstart/build-depgraph.xml
sbom:
[mkdir] Created dir: /root/cdx-quickstart/bom
[cdx:componentbom] creating CycloneDX SBOM /root/cdx-quickstart/bom/notes-app-1.0.0-deps.json
BUILD SUCCESSFUL
Total time: 1 second
sed -n '/"dependencies"/,$p' bom/notes-app-1.0.0-deps.json
"dependencies" : [
{
"ref" : "pkg:maven/com.example/lib-log@1.3.0",
"dependsOn" : [ ]
},
{
"ref" : "pkg:maven/com.example/lib-util@2.1.0",
"dependsOn" : [
"pkg:maven/com.example/lib-log@1.3.0"
]
},
{
"ref" : "pkg:maven/com.example/notes-app@1.0.0",
"dependsOn" : [
"pkg:maven/com.example/lib-log@1.3.0",
"pkg:maven/com.example/lib-util@2.1.0"
]
}
]
}
三层依赖关系被完整表达(输出按 ref 字典序排列,与书写顺序无关)。这里手工维护依赖图只适合演示与小工程;让 Ivy 自动解析依赖并填充依赖图的做法,见本专栏《Apache CycloneDX Ant Library 0.2 常用 API 与任务详解》第 5.3 节与《进阶实战详解》第二章。
💡 技术拓展:CycloneDX 的依赖图是「有向」的,
ref节点必须全部能在components数组里找到对应bom-ref,这正是坑 3 报错的由来。把ref想象成数据库外键,写 SBOM 时就不容易悬空。
五、与 Maven/Gradle 生态的集成选择
CycloneDX 官方生态里,Ant 之外还有两条主流生成路径,选型对照如下(均为同一 CycloneDX 规范输出,格式互通):
| 维度 | CycloneDX Antlib(本专栏) | cyclonedx-maven-plugin | CycloneDX Gradle plugin |
|---|---|---|---|
| 接入方式 | build.xml 里的 taskdef + componentbom | pom 里声明 plugin 或 CLI 调 goal | build.gradle 里声明 plugin |
| 组件来源 | 手工声明 / Ivy 解析 | Maven 依赖树自动解析 | Gradle 依赖图自动解析 |
| 默认规范版本 | 1.7 | 1.6(实测 2.9.3,详见进阶篇第六章) | 1.6 |
| 适用场景 | Ant/非 Maven 遗留构建、发行包打标 | 标准 Maven 工程 | Gradle 工程 |
判断标准很简单:项目用什么构建就用什么插件。Antlib 的不可替代场景有两个——构建链里有非 Maven 产物(如发行 tar 包,用 archivebom 宏打标,见详解篇第 5.1 节),以及需要把 SBOM 生成嵌进遗留 Ant 流水线。两种工具并存的工程可以各生成一份 SBOM,规范一致即可互认。
六、参考链接
- Apache CycloneDX Antlib 官方手册:
https://ant.apache.org/antlibs/cyclonedx/manual/ - Maven 坐标页(版本元数据):
https://repo1.maven.org/maven2/org/apache/ant/ant-cyclonedx/ - CycloneDX 规范官网:
https://cyclonedx.org/specs/ - Package-URL 规范:
https://github.com/package-url/purl-spec - 本专栏前置篇:《Ubuntu 22.04 安装 Apache CycloneDX Ant Library 指南》《CentOS 8 安装 Apache CycloneDX Ant Library 指南》
- 本专栏后续篇:《Apache CycloneDX Ant Library 0.2 常用 API 与任务详解》《Apache CycloneDX Ant Library 0.2 进阶实战详解》