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 fe902d2 add ability to state completeness of relationship description
fe902d2 is described below
commit fe902d2cb49ed6aa72e5a3c3fcbe4a029dade182
Author: Stefan Bodewig <[email protected]>
AuthorDate: Thu Aug 27 22:26:59 2026 +0200
add ability to state completeness of relationship description
---
build.xml | 38 +++++++++++++++-
changes.xml | 4 ++
docs/component.html | 12 +++++
src/main/org/apache/ant/cyclonedx/Component.java | 26 +++++++++++
.../org/apache/ant/cyclonedx/ComponentBomTask.java | 30 ++++++++++++
.../apache/ant/cyclonedx/CompositionAggregate.java | 53 ++++++++++++++++++++++
src/tests/antunit/componentbom-test.xml | 48 ++++++++++++++++++++
7 files changed, 209 insertions(+), 2 deletions(-)
diff --git a/build.xml b/build.xml
index a13aa19..5ca6312 100644
--- a/build.xml
+++ b/build.xml
@@ -111,6 +111,38 @@ under the License.
</cdx:externalreferenceset>
</target>
+ <!-- overridden until common/cyclonedx.xml supports compositionAggregate -->
+ <target name="define-core-ant-components"
+ if="add.ant.core.component"
+ xmlns:cdx="antlib:org.apache.ant.cyclonedx"
+ depends="define-common-cyclonedx-references">
+ <cdx:component
+ name="ant-launcher"
+ group="org.apache.ant"
+ version="${ant.core.version}"
+ isExternal="true"
+ compositionAggregate="complete"
+ id="ant-launcher">
+ <supplier refid="ant-pmc"/>
+ <license refid="apache-2"/>
+ <externalReferenceSet refid="ant-common-refs"/>
+ <externalReferenceSet refid="ant-ext-refs"/>
+ </cdx:component>
+ <cdx:component
+ name="ant"
+ group="org.apache.ant"
+ version="${ant.core.version}"
+ isExternal="true"
+ compositionAggregate="complete"
+ id="ant">
+ <supplier refid="ant-pmc"/>
+ <license refid="apache-2"/>
+ <externalReferenceSet refid="ant-common-refs"/>
+ <externalReferenceSet refid="ant-ext-refs"/>
+ <dependency componentRef="ant-launcher"/>
+ </cdx:component>
+ </target>
+
<target name="create-antlib-sbom"
depends="define-cyclonedx-components"
if="can.use.cyclonedx"
@@ -134,7 +166,8 @@ under the License.
<component
description="Apache CycloneDX Antlib"
publisher="The Apache Software Foundation"
- supplierIsManufacturer="true">
+ supplierIsManufacturer="true"
+ compositionAggregate="complete">
<cdx:ivyModule conf="default,provided" externalConf="provided">
<templateComponent refid="ant"/>
<templateComponent refid="ant-launcher"/>
@@ -143,7 +176,8 @@ under the License.
group="org.apache.ivy"
publisher="The Apache Software Foundation"
description="Apache Ivy"
- supplierIsManufacturer="true">
+ supplierIsManufacturer="true"
+ compositionAggregate="complete">
<supplier refid="ant-pmc"/>
<license refid="apache-2"/>
<externalReferenceSet refid="ant-common-refs"/>
diff --git a/changes.xml b/changes.xml
index a34da27..c1b147f 100644
--- a/changes.xml
+++ b/changes.xml
@@ -118,6 +118,10 @@
The license element now supports a new expression attribute you
can use to specify license expressions.
</action>
+ <action type="add">
+ A new attribute compositionAggregate on component allows you to
+ specify the completeness of the component's description.
+ </action>
</release>
<release version="0.1" date="2026-06-03" description="initial release">
diff --git a/docs/component.html b/docs/component.html
index 1716483..bf8403a 100644
--- a/docs/component.html
+++ b/docs/component.html
@@ -140,6 +140,18 @@ <h3>Attributes</h3>
having no dependencies at all. </td>
<td>No - defaults to <code>false</code>.</td>
</tr>
+ <tr>
+ <td>compositionAggregate</td>
+ <td>The completeness of the component's description specified
+ as a CylcloneDX composition aggregate. Valid
+ compositionAggregate are defined by the
+ <a
href="https://cyclonedx.org/docs/1.7/json/#compositions_items_aggregate">CycloneDX
+ specification</a>.</td>
+ <td>No - by default no composition including this component
+ will be created.<br/>
+ <em>since CylconeDX Antlib 0.2</em>
+ </td>
+ </tr>
</table>
<h3>Nested elements</h3>
diff --git a/src/main/org/apache/ant/cyclonedx/Component.java
b/src/main/org/apache/ant/cyclonedx/Component.java
index cd7a6c7..406bea4 100644
--- a/src/main/org/apache/ant/cyclonedx/Component.java
+++ b/src/main/org/apache/ant/cyclonedx/Component.java
@@ -85,6 +85,7 @@ public class Component extends DataType {
private Set<String> tags = new HashSet<>();
private List<Property> properties = new ArrayList<>();
private String mimeType;
+ private CompositionAggregate compositionAggregate;
private ComponentResolver resolver;
/**
@@ -164,6 +165,7 @@ public class Component extends DataType {
this.tags = new HashSet<>(other.tags);
this.properties = new ArrayList<>(other.properties);
this.mimeType = other.mimeType;
+ this.compositionAggregate = other.compositionAggregate;
this.resolver = other.resolver;
}
@@ -287,6 +289,16 @@ public class Component extends DataType {
this.mimeType = mimeType;
}
+ /**
+ * Sets the composition aggregate of this component.
+ *
+ * @param compositionAggregate composition aggregate
+ * @since CycloneDX Antlib 0.2
+ */
+ public void setCompositionAggregate(CompositionAggregate
compositionAggregate) {
+ this.compositionAggregate = compositionAggregate;
+ }
+
/**
* Sets the manufacturer of the component.
*
@@ -644,6 +656,20 @@ public class Component extends DataType {
return mimeType;
}
+ /**
+ * Gets the composition aggregate of the component.
+ *
+ * @return composition aggregate - may be null
+ * @since CycloneDX Antlib 0.2
+ */
+ public CompositionAggregate getCompositionAggregate() {
+ if (isReference()) {
+ return getRef().getCompositionAggregate();
+ }
+ dieOnCircularReference();
+ return compositionAggregate;
+ }
+
/**
* Gets the manufacturer of the component.
*
diff --git a/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java
b/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java
index 179b994..9e04701 100644
--- a/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java
+++ b/src/main/org/apache/ant/cyclonedx/ComponentBomTask.java
@@ -35,6 +35,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.regex.Matcher;
@@ -53,6 +54,8 @@ 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.BomReference;
+import org.cyclonedx.model.Composition;
import org.cyclonedx.model.Dependency;
import org.cyclonedx.model.LicenseChoice;
import org.cyclonedx.model.LicenseItem;
@@ -346,6 +349,7 @@ public class ComponentBomTask extends Task {
cs.sort(Component.CycloneDxComponentComparator);
bom.setComponents(cs);
addDependencies(bom, knownComponents, resolvedComponentsAdded);
+ addCompositions(bom, resolvedComponentsAdded);
return bom;
}
@@ -484,6 +488,32 @@ public class ComponentBomTask extends Task {
bom.setDependencies(dependencies);
}
+ private void addCompositions(Bom bom, List<Component>
resolvedComponentsAdded) {
+ final Map<Composition.Aggregate, List<String>> componentsByAggregate =
new TreeMap<>();
+ visitAllComponentsWithExtra(resolvedComponentsAdded, c -> {
+ String bomRef = c.getBomRef();
+ CompositionAggregate a = c.getCompositionAggregate();
+ if (bomRef != null && a != null) {
+ Composition.Aggregate key = a.getAggregate();
+ componentsByAggregate.putIfAbsent(key, new ArrayList<>());
+ List<String> dependencies = componentsByAggregate.get(key);
+ dependencies.add(bomRef);
+ }
+ });
+
+ List<Composition> compositions = new ArrayList<>();
+ for (Map.Entry<Composition.Aggregate, List<String>> compositionEntry :
componentsByAggregate.entrySet()) {
+ Composition composition = new Composition();
+ composition.setAggregate(compositionEntry.getKey());
+ compositionEntry.getValue().stream().sorted().forEach(s ->
composition.addDependency(new BomReference(s)));
+ compositions.add(composition);
+ }
+
+ if (!compositions.isEmpty()) {
+ bom.setCompositions(compositions);
+ }
+ }
+
private void visitAllComponents(Consumer<Component> visitor) {
visitAllComponentsWithExtra(Collections.emptyList(), visitor);
}
diff --git a/src/main/org/apache/ant/cyclonedx/CompositionAggregate.java
b/src/main/org/apache/ant/cyclonedx/CompositionAggregate.java
new file mode 100644
index 0000000..002e66c
--- /dev/null
+++ b/src/main/org/apache/ant/cyclonedx/CompositionAggregate.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.ant.cyclonedx;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.EnumeratedAttribute;
+
+import org.cyclonedx.model.Composition.Aggregate;
+
+/**
+ * CycloneDX composition's aggregate type.
+ *
+ * <p>Specifies an aggregate type that describes how complete a
+ * relationship of a composition is.</p>
+ *
+ * <p>Accepts the enum constants like {@code COMPLETE} as well as the
+ * lowercase version {@code complete}. The values are directly
+ * provided by CycloneDX Core's enum.</p>
+ *
+ * @since CycloneDX Antlib 0.2
+ */
+public class CompositionAggregate extends EnumeratedAttribute {
+
+ @Override
+ public String[] getValues() {
+ return EnumUtils.valuesPlus(Aggregate.class,
Aggregate::getAggregateName);
+ }
+
+ /**
+ * Translates this instance to a {@link Aggregate}.
+ *
+ * @return translated aggregate
+ * @throws BuildException if the value can not be translated.
+ */
+ public Aggregate getAggregate() {
+ return EnumUtils.valueOf(Aggregate.class, getValue(),
Aggregate::getAggregateName);
+ }
+}
diff --git a/src/tests/antunit/componentbom-test.xml
b/src/tests/antunit/componentbom-test.xml
index 2c8b99b..fff9d57 100644
--- a/src/tests/antunit/componentbom-test.xml
+++ b/src/tests/antunit/componentbom-test.xml
@@ -538,4 +538,52 @@
resource="${output}/bom.xml"
value='<hash alg="SHA-256">${ant.file.sha256}</hash>'/>
</target>
+
+ <target name="testNoCompositionsByDefault">
+ <cdx:componentbom outputdirectory="${output}" format="xml"
+ xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+ <component name="testname">
+ <file file="${antlib.location}"/>
+ </component>
+ </cdx:componentbom>
+ <au:assertResourceDoesntContain
+ xmlns:au="antlib:org.apache.ant.antunit"
+ resource="${output}/bom.xml"
+ value='<compositions>'/>
+ </target>
+
+ <target name="testAddsCompositionsWhenAggregateIsSet">
+ <cdx:componentbom outputdirectory="${output}" format="xml"
+ xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+ <component bomRef="[email protected]" name="testname"
compositionAggregate="complete"/>
+ </cdx:componentbom>
+ <xmlproperty file="${output}/bom.xml"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.compositions.composition.aggregate"
+ value="complete"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.compositions.composition.dependencies.dependency(ref)"
+ value="[email protected]"/>
+ </target>
+
+ <target name="testGroupsCompositionsByAggregate">
+ <cdx:componentbom outputdirectory="${output}" format="xml"
+ xmlns:cdx="antlib:org.apache.ant.cyclonedx">
+ <component bomRef="[email protected]" name="testname1"
compositionAggregate="complete"/>
+ <additionalComponent bomRef="[email protected]" name="testname2"
compositionAggregate="unknown"/>
+ <additionalComponent bomRef="[email protected]" name="testname4"
compositionAggregate="complete"/>
+ <additionalComponent bomRef="[email protected]" name="testname3"
compositionAggregate="complete"/>
+ </cdx:componentbom>
+ <xmlproperty file="${output}/bom.xml"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.compositions.composition.aggregate"
+ value="complete,unknown"/>
+ <au:assertPropertyEquals
+ xmlns:au="antlib:org.apache.ant.antunit"
+ name="bom.compositions.composition.dependencies.dependency(ref)"
+ value="[email protected],[email protected],[email protected],[email protected]"/>
+ </target>
</project>