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 f568de9 add support for license expressions
f568de9 is described below
commit f568de9168144e48be04e744e20ac48ce8f6d97b
Author: Stefan Bodewig <[email protected]>
AuthorDate: Sun Aug 16 17:45:41 2026 +0200
add support for license expressions
---
changes.xml | 4 ++
docs/ivyModule.html | 1 -
docs/license.html | 7 +-
docs/sbomLink.html | 2 +-
src/main/org/apache/ant/cyclonedx/Component.java | 12 ++--
.../org/apache/ant/cyclonedx/ComponentBomTask.java | 7 +-
src/main/org/apache/ant/cyclonedx/License.java | 76 ++++++++++++++++++----
src/tests/antunit/component-ivymodule-test.xml | 2 -
src/tests/antunit/license-test.xml | 49 +++++++++++++-
9 files changed, 129 insertions(+), 31 deletions(-)
diff --git a/changes.xml b/changes.xml
index 37c4e75..f5bd076 100644
--- a/changes.xml
+++ b/changes.xml
@@ -114,6 +114,10 @@
The license element now tries to guess the license's SPDX id
if only a name is given.
</action>
+ <action type="add">
+ The license element now supports a new expression attribute you
+ can use to specify license expressions.
+ </action>
</release>
<release version="0.1" date="2026-06-03" description="initial release">
diff --git a/docs/ivyModule.html b/docs/ivyModule.html
index dfcd652..e0a2f84 100644
--- a/docs/ivyModule.html
+++ b/docs/ivyModule.html
@@ -243,4 +243,3 @@ <h4>templateComponent</h4>
</pre>
</body>
</html>
-
diff --git a/docs/license.html b/docs/license.html
index 672e3fc..459a64b 100644
--- a/docs/license.html
+++ b/docs/license.html
@@ -44,12 +44,17 @@ <h3>Attributes</h3>
<td>The id of the license. Must be a
valid <a href="https://spdx.org/licenses/">SPDX
identifier</a>.</td>
- <td rowspan="2">One of the two is required</td>
+ <td rowspan="3">One of the three is required</td>
</tr>
<tr>
<td>name</td>
<td>The name of the license.</td>
</tr>
+ <tr>
+ <td>expression</td>
+ <td>The license expression.<br/>
+ <em>since CycloneDX Antlib 0.2</em></td>
+ </tr>
</table>
<p>Since CycloneDX Antlib 0.2 the Antlib will try to guess the
diff --git a/docs/sbomLink.html b/docs/sbomLink.html
index 988d5b3..b0a34e5 100644
--- a/docs/sbomLink.html
+++ b/docs/sbomLink.html
@@ -95,4 +95,4 @@ <h3>Attributes</h3>
</table>
</body>
-</html>
+</html>
diff --git a/src/main/org/apache/ant/cyclonedx/Component.java
b/src/main/org/apache/ant/cyclonedx/Component.java
index f2a8c42..f757189 100644
--- a/src/main/org/apache/ant/cyclonedx/Component.java
+++ b/src/main/org/apache/ant/cyclonedx/Component.java
@@ -71,7 +71,7 @@ public class Component extends DataType {
private Organization manufacturer = null;
private Organization supplier = null;
private boolean supplierIsManufacturer = false;
- private List<org.cyclonedx.model.License> licenses = new ArrayList<>();
+ private List<LicenseItem> licenses = new ArrayList<>();
private String purl;
private String bomRef;
private List<org.cyclonedx.model.ExternalReference> externalReferences =
new ArrayList<>();
@@ -380,7 +380,7 @@ public class Component extends DataType {
*/
public void addConfiguredLicense(License l) {
checkChildrenAllowed();
- licenses.add(l.toCycloneDxLicense());
+ licenses.add(l.toCycloneDxLicenseItem());
}
/**
@@ -1001,8 +1001,7 @@ public class Component extends DataType {
// would create an empty licenses node otherwise
LicenseChoice lc = new LicenseChoice();
lc.setItems(licenses.stream()
- .sorted(License.CycloneDxLicenseComparator)
- .map(LicenseItem::ofLicense)
+ .sorted(License.CycloneDxLicenseItemComparator)
.collect(Collectors.toList()));
component.setLicenses(lc);
}
@@ -1076,10 +1075,7 @@ public class Component extends DataType {
if (licenses.isEmpty()) {
LicenseChoice realLicenses = real.getLicenses();
if (realLicenses != null) {
- licenses.addAll(realLicenses.getItems().stream()
- .filter(i ->
LicenseItem.LicenseItemType.LICENSE.equals(i.getType()))
- .map(LicenseItem::getLicense)
- .collect(Collectors.toList()));
+ licenses.addAll(realLicenses.getItems());
}
}
if (externalReferences.isEmpty()) {
diff --git a/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java
b/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java
index df7e1a5..f74584b 100644
--- a/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java
+++ b/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java
@@ -84,7 +84,7 @@ public class ComponentBomTask extends Task {
private boolean useComponentSupplier = false;
private boolean useComponentManufacturer = false;
private Union pureFileComponents = new Union();
- private List<org.cyclonedx.model.License> licenses = new ArrayList<>();
+ private List<LicenseItem> licenses = new ArrayList<>();
/**
* Specifies the CycloneDX version to use.
@@ -216,7 +216,7 @@ public class ComponentBomTask extends Task {
* @param l SBOM's license
*/
public void addConfiguredLicense(License l) {
- licenses.add(l.toCycloneDxLicense());
+ licenses.add(l.toCycloneDxLicenseItem());
}
/**
@@ -410,8 +410,7 @@ public class ComponentBomTask extends Task {
if (!licenses.isEmpty()) {
LicenseChoice lc = new LicenseChoice();
lc.setItems(licenses.stream()
- .sorted(License.CycloneDxLicenseComparator)
- .map(LicenseItem::ofLicense)
+ .sorted(License.CycloneDxLicenseItemComparator)
.collect(Collectors.toList()));
meta.setLicenses(lc);
}
diff --git a/src/main/org/apache/ant/cyclonedx/License.java
b/src/main/org/apache/ant/cyclonedx/License.java
index e4fac3f..a0adb47 100644
--- a/src/main/org/apache/ant/cyclonedx/License.java
+++ b/src/main/org/apache/ant/cyclonedx/License.java
@@ -20,6 +20,8 @@ package org.apache.ant.cyclonedx;
import java.util.Comparator;
import org.cyclonedx.model.LicenseChoice;
+import org.cyclonedx.model.LicenseItem;
+import org.cyclonedx.model.license.Expression;
import org.cyclonedx.util.LicenseResolver;
import org.apache.tools.ant.BuildException;
@@ -45,6 +47,7 @@ public class License extends DataType {
private String id;
private String name;
private String url;
+ private String expression;
/**
* Comparator for CycloneDX license.
@@ -54,10 +57,31 @@ public class License extends DataType {
*
* @since CycloneDX Antlib 0.2
*/
- public static final Comparator<org.cyclonedx.model.License>
CycloneDxLicenseComparator =
+ private static final Comparator<org.cyclonedx.model.License>
CycloneDxLicenseComparator =
Comparator.comparing(org.cyclonedx.model.License::getId,
Comparator.nullsLast(Comparator.naturalOrder()))
.thenComparing(org.cyclonedx.model.License::getName,
Comparator.nullsLast(Comparator.naturalOrder()));
+ /**
+ * Comparator for CycloneDX License Expressions.
+ *
+ * <p>Sorts by expression value.</p>
+ *
+ * @since CycloneDX Antlib 0.2
+ */
+ private static final Comparator<Expression>
CycloneDxLicenseExpressionComparator =
+ Comparator.comparing(Expression::getValue, Comparator.naturalOrder());
+
+ /**
+ * Comparator for CycloneDX License Items.
+ *
+ * <p>Sorts licenses by id falling back to id before license expressions
which are sorted by expression value.
+ *
+ * @since CycloneDX Antlib 0.2
+ */
+ public static final Comparator<LicenseItem> CycloneDxLicenseItemComparator
=
+ Comparator.comparing(LicenseItem::getLicense,
Comparator.nullsLast(CycloneDxLicenseComparator))
+ .thenComparing(LicenseItem::getExpression,
Comparator.nullsLast(CycloneDxLicenseExpressionComparator));
+
/**
* Sets the {@code id} of the license.
*
@@ -82,6 +106,17 @@ public class License extends DataType {
this.name = name;
}
+ /**
+ * Sets the SPDX license expression of the license.
+ *
+ * @param expression license expression
+ * @since CycloneDX Antlib 0.2
+ */
+ public void setExpression(String expression) {
+ checkAttributesAllowed();
+ this.expression = expression;
+ }
+
/**
* Sets the url of the license.
*
@@ -103,13 +138,21 @@ public class License extends DataType {
*
* @return CycloneDX version of this instance
*/
- public org.cyclonedx.model.License toCycloneDxLicense() {
+ public LicenseItem toCycloneDxLicenseItem() {
if (isReference()) {
- return getRef().toCycloneDxLicense();
+ return getRef().toCycloneDxLicenseItem();
}
dieOnCircularReference();
- if (name == null && id == null) {
- throw new BuildException("license name or licenseId is required");
+ if (name == null && id == null && expression == null) {
+ throw new BuildException("license name, licenseId or expression is
required");
+ }
+
+ if ((name != null || id != null) && expression != null) {
+ throw new BuildException("license name or licenseId prohibit
expression");
+ }
+
+ if (expression != null) {
+ return LicenseItem.ofExpression(new Expression(expression));
}
if (id == null) {
@@ -118,7 +161,7 @@ public class License extends DataType {
if (url != null) {
l.setUrl(url);
}
- return l;
+ return LicenseItem.ofLicense(l);
}
}
@@ -132,14 +175,25 @@ public class License extends DataType {
if (url != null) {
l.setUrl(url);
}
- return l;
+ return LicenseItem.ofLicense(l);
}
/**
* @since CycloneDX Antlib 0.2
*/
- public static License from(org.cyclonedx.model.License l) {
+ public static License from(LicenseItem licenseItem) {
License license = new License();
+ Expression e = licenseItem.getExpression();
+ if (e != null) {
+ license.setExpression(e.getValue());
+ return license;
+ }
+
+ org.cyclonedx.model.License l = licenseItem.getLicense();
+ if (l == null) {
+ throw new BuildException("unsupported LicenseItem " + licenseItem);
+ }
+
String id = l.getId();
if (id != null) {
license.setLicenseId(id);
@@ -170,12 +224,12 @@ public class License extends DataType {
*/
private org.cyclonedx.model.License guessLicense() {
LicenseChoice lc = LicenseResolver.resolve(name, false);
- if ((lc == null || lc.getLicenses() == null ||
lc.getLicenses().isEmpty())
+ if ((lc == null || lc.getItems() == null || lc.getItems().isEmpty())
&& url != null) {
lc = LicenseResolver.resolve(url, false);
}
- if (lc != null && lc.getLicenses() != null &&
!lc.getLicenses().isEmpty()) {
- return lc.getLicenses().get(0);
+ if (lc != null && lc.getItems() != null && !lc.getItems().isEmpty()) {
+ return lc.getItems().get(0).getLicense();
}
return null;
}
diff --git a/src/tests/antunit/component-ivymodule-test.xml
b/src/tests/antunit/component-ivymodule-test.xml
index bdb92d0..08f7d6b 100644
--- a/src/tests/antunit/component-ivymodule-test.xml
+++ b/src/tests/antunit/component-ivymodule-test.xml
@@ -272,7 +272,6 @@
<manufacturer refid="ant-team"/>
</additionalComponent>
</cdx:componentbom>
- <copy todir="/tmp" file="${output}/bom.xml"/>
<xmlproperty file="${output}/bom.xml"/>
<au:assertPropertyEquals
xmlns:au="antlib:org.apache.ant.antunit"
@@ -352,7 +351,6 @@
</cdx:ivyModule>
</component>
</cdx:componentbom>
- <copy todir="/tmp" file="${output}/bom.xml"/>
<xmlproperty file="${output}/bom.xml"/>
<au:assertPropertyEquals
xmlns:au="antlib:org.apache.ant.antunit"
diff --git a/src/tests/antunit/license-test.xml
b/src/tests/antunit/license-test.xml
index 5aec828..3b4740e 100644
--- a/src/tests/antunit/license-test.xml
+++ b/src/tests/antunit/license-test.xml
@@ -19,9 +19,9 @@
<import file="shared.xml" />
- <target name="testLicenseRequiresIdOrName">
+ <target name="testLicenseRequiresIdNameOrExpression">
<au:expectfailure
- expectedMessage="license name or licenseId is required"
+ expectedMessage="license name, licenseId or expression is required"
xmlns:au="antlib:org.apache.ant.antunit">
<cdx:componentbom
outputdirectory="${output}" format="xml"
@@ -33,6 +33,34 @@
</au:expectfailure>
</target>
+ <target name="testLicenseDoesntAllowIdAndExpression">
+ <au:expectfailure
+ expectedMessage="license name or licenseId prohibit expression"
+ xmlns:au="antlib:org.apache.ant.antunit">
+ <cdx:componentbom
+ outputdirectory="${output}" format="xml"
+ xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+ <component name="testname">
+ <license licenseId="Apache-2.0" expression="foo"/>
+ </component>
+ </cdx:componentbom>
+ </au:expectfailure>
+ </target>
+
+ <target name="testLicenseDoesntAllowNameAndExpression">
+ <au:expectfailure
+ expectedMessage="license name or licenseId prohibit expression"
+ xmlns:au="antlib:org.apache.ant.antunit">
+ <cdx:componentbom
+ outputdirectory="${output}" format="xml"
+ xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+ <component name="testname">
+ <license name="bar" expression="foo"/>
+ </component>
+ </cdx:componentbom>
+ </au:expectfailure>
+ </target>
+
<target name="testLicenseDoesntAllowMultipleUrls">
<au:expectfailure
expectedMessage="only one URL is allowed in license"
@@ -50,7 +78,7 @@
</au:expectfailure>
</target>
- <target name="testLicenseWorksAsDirectChildOfComponent">
+ <target name="testLicenseNameWorksAsDirectChildOfComponent">
<cdx:componentbom
outputdirectory="${output}" format="xml"
xmlns:cdx="antlib:org.apache.ant.cyclonedx">
@@ -71,6 +99,21 @@
value="https://example.org/"/>
</target>
+ <target name="testLicenseExpressionWorksAsDirectChildOfComponent">
+ <cdx:componentbom
+ outputdirectory="${output}" format="xml"
+ xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+ <component name="testname">
+ <license expression="foo"/>
+ </component>
+ </cdx:componentbom>
+ <xmlproperty file="${output}/bom.xml"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.metadata.component.licenses.expression"
+ value="foo"/>
+ </target>
+
<target name="testLicenseGuessesIdFromLicenseName">
<cdx:componentbom
outputdirectory="${output}" format="xml"