This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch support-ivy-as-component-source
in repository https://gitbox.apache.org/repos/asf/ant-antlibs-cyclonedx.git
The following commit(s) were added to
refs/heads/support-ivy-as-component-source by this push:
new 15c8fc7 deal with optional and external components
15c8fc7 is described below
commit 15c8fc769f51371846fc499fc9fb25e1389a544f
Author: Stefan Bodewig <[email protected]>
AuthorDate: Sun Aug 2 12:31:07 2026 +0200
deal with optional and external components
---
build.xml | 5 +-
src/main/org/apache/ant/cyclonedx/Component.java | 39 ++++++-
.../ant/cyclonedx/IvyModuleComponentResolver.java | 117 +++++++++++++++++----
3 files changed, 138 insertions(+), 23 deletions(-)
diff --git a/build.xml b/build.xml
index b768362..df2595a 100644
--- a/build.xml
+++ b/build.xml
@@ -60,8 +60,7 @@ under the License.
<taskdef resource="org/apache/ivy/ant/antlib.xml"
uri="antlib:org.apache.ivy.ant"
loaderRef="cdx.loader"/>
- <ivy:resolve file="ivy.xml" xmlns:ivy="antlib:org.apache.ivy.ant"
- conf="default,provided" resolveId="compile-and-runtime"/>
+ <ivy:resolve file="ivy.xml" xmlns:ivy="antlib:org.apache.ivy.ant"/>
</target>
<target name="define-cyclonedx-components"
@@ -106,7 +105,7 @@ under the License.
description="Apache CycloneDX Antlib"
publisher="The Apache Software Foundation"
supplierIsManufacturer="true">
- <ivymodule conf="default,provided" resolveId="compile-and-runtime"/>
+ <ivymodule conf="default,provided" externalConf="provided"/>
<file file="${jarname}"/>
<supplier refid="ant-pmc"/>
<license refid="apache-2"/>
diff --git a/src/main/org/apache/ant/cyclonedx/Component.java
b/src/main/org/apache/ant/cyclonedx/Component.java
index 5a572dd..e3fc9f7 100644
--- a/src/main/org/apache/ant/cyclonedx/Component.java
+++ b/src/main/org/apache/ant/cyclonedx/Component.java
@@ -1105,11 +1105,13 @@ public class Component extends DataType {
*/
public static class IvyModule {
private String conf;
+ private String optionalConf;
+ private String externalConf;
private String resolveId;
private Reference antIvyEngineRef;
/**
- * Sets the configurations to take into consideration.
+ * Sets the configurations to include in the SBOM.
*
* <p>Defaults to the configurations resolved by the last resolve
call, or {@code *} if no resolve was
* explicitly called</p>
@@ -1154,5 +1156,40 @@ public class Component extends DataType {
return antIvyEngineRef;
}
+ /**
+ * Marks configurations as optional.
+ *
+ * <p>Any module that is included in the SBOM because it is required
by on of the configurations given in {@link
+ * #setConf} and only is included because of configurations listed
here is marked optional. Including
+ * configurations that are not part of {@link #setConf} doesn't have
any effect. {@code *} is no supported. The
+ * default is to have no optional compoments.</p>
+ *
+ * @param comma separated list of the configurations to mark optional.
+ */
+ public void setOptionalConf(String optionalConf) {
+ this.optionalConf = optionalConf;
+ }
+
+ String getOptionalConf() {
+ return optionalConf;
+ }
+
+ /**
+ * Marks configurations as external.
+ *
+ * <p>Any module that is included in the SBOM because it is required
by on of the configurations given in {@link
+ * #setConf} and only is included because of configurations listed
here is marked external. Including
+ * configurations that are not part of {@link #setConf} doesn't have
any effect. {@code *} is no supported. The
+ * default is to have no external compoments.</p>
+ *
+ * @param comma separated list of the configurations to mark external.
+ */
+ public void setExternalConf(String externalConf) {
+ this.externalConf = externalConf;
+ }
+
+ String getExternalConf() {
+ return externalConf;
+ }
}
}
diff --git a/src/main/org/apache/ant/cyclonedx/IvyModuleComponentResolver.java
b/src/main/org/apache/ant/cyclonedx/IvyModuleComponentResolver.java
index 43f97d8..79aca12 100644
--- a/src/main/org/apache/ant/cyclonedx/IvyModuleComponentResolver.java
+++ b/src/main/org/apache/ant/cyclonedx/IvyModuleComponentResolver.java
@@ -18,14 +18,17 @@
package org.apache.ant.cyclonedx;
import java.io.IOException;
+import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.apache.ivy.Ivy;
import org.apache.ivy.ant.IvyAntSettings;
@@ -41,6 +44,8 @@ import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.Reference;
import org.apache.tools.ant.types.resources.URLResource;
+import org.cyclonedx.model.Component.Scope;
+
/**
* Resolver that populates a Component from Ivy module data.
*
@@ -56,6 +61,10 @@ class IvyModuleComponentResolver {
private final Component.IvyModule ivyModule;
private final Project project;
+ private boolean includeAllConfigurations;
+ private Set<String> includedConfigurations;
+ private Set<String> optionalConfigurations;
+ private Set<String> externalConfigurations;
IvyModuleComponentResolver(Component.IvyModule ivyModule, Project project)
{
this.ivyModule = ivyModule;
@@ -73,16 +82,21 @@ class IvyModuleComponentResolver {
Ivy ivy = createIvyInstance(component);
IvySettings settings = ivy.getSettings();
+ parseConfigurations(settings);
+
ResolveReport report = loadResolveReport(settings);
ModuleDescriptor root = report.getModuleDescriptor();
- Map<ModuleRevisionId, Set<IvyNode>> dependencyTree =
populateDependencyTree(settings, report);
+ Set<ModuleRevisionId> optionalModules = new HashSet<>();
+ Set<ModuleRevisionId> externalModules = new HashSet<>();
+ Map<ModuleRevisionId, Set<IvyNode>> dependencyTree =
+ populateDependencyTree(settings, report, optionalModules,
externalModules);
fillFromModuleDescriptor(component, root, dependencyTree);
Collection<ModuleDescriptor> allDependencies =
getDependencies(dependencyTree, root);
return allDependencies.stream()
- .map(d -> toComponent(d, dependencyTree))
+ .map(d -> toComponent(d, dependencyTree, optionalModules,
externalModules))
.collect(Collectors.toList());
}
@@ -98,6 +112,24 @@ class IvyModuleComponentResolver {
return engine.getConfiguredIvyInstance(component);
}
+ private void parseConfigurations(IvySettings settings) {
+ String conf = ivyModule.getConf();
+ if (conf == null || "*".equals(conf)) {
+ conf = settings.getVariable("ivy.resolved.configurations");
+ }
+ if (conf == null) {
+ throw new BuildException("no conf provided, you need to call to
<resolve/> before using this task");
+ }
+ includeAllConfigurations = "*".equals(conf);
+ if (includeAllConfigurations) {
+ includedConfigurations = new HashSet<>();
+ } else {
+ includedConfigurations = confAsSet(conf);
+ }
+ optionalConfigurations = confAsSet(ivyModule.getOptionalConf());
+ externalConfigurations = confAsSet(ivyModule.getExternalConf());
+ }
+
private ResolveReport loadResolveReport(IvySettings settings) {
// explicit values for organisation and module would come in here,
once supported
String organisation = settings.getVariable("ivy.organisation");
@@ -132,15 +164,24 @@ class IvyModuleComponentResolver {
return report;
}
- private Component toComponent(ModuleDescriptor md, Map<ModuleRevisionId,
- Set<IvyNode>> dependencyTree) {
+ private Component toComponent(ModuleDescriptor md,
+ Map<ModuleRevisionId, Set<IvyNode>>
dependencyTree,
+ Set<ModuleRevisionId> optionalModules,
+ Set<ModuleRevisionId> externalModules) {
Component c = new Component();
c.setProject(project);
fillFromModuleDescriptor(c, md, dependencyTree);
+
+ ModuleRevisionId mrid = md.getModuleRevisionId();
+ if (optionalModules.contains(mrid)) {
+ c.setScope(ComponentScope.from(Scope.OPTIONAL));
+ }
+ c.setIsExternal(externalModules.contains(mrid));
return c;
}
- private static void fillFromModuleDescriptor(Component component,
ModuleDescriptor md,
+ private static void fillFromModuleDescriptor(Component component,
+ ModuleDescriptor md,
Map<ModuleRevisionId,
Set<IvyNode>> dependencyTree) {
ModuleRevisionId mrid = md.getModuleRevisionId();
if (component.getName() == null) {
@@ -152,7 +193,7 @@ class IvyModuleComponentResolver {
if (component.getVersion() == null) {
component.setVersion(mrid.getRevision());
}
- if (component.getDescription() == null && md.getDescription() != null)
{
+ if (component.getDescription() == null && md.getDescription() != null
&& md.getDescription().length() > 0) {
component.setDescription(md.getDescription());
}
@@ -192,28 +233,33 @@ class IvyModuleComponentResolver {
}
}
- private Map<ModuleRevisionId, Set<IvyNode>>
populateDependencyTree(IvySettings settings, ResolveReport report) {
- String conf = ivyModule.getConf();
- if (conf == null || "*".equals(conf)) {
- conf = settings.getVariable("ivy.resolved.configurations");
- }
- if (conf == null) {
- throw new BuildException("no conf provided, you need to call to
<resolve/> before using this task");
- }
-
+ private Map<ModuleRevisionId, Set<IvyNode>>
populateDependencyTree(IvySettings settings,
+
ResolveReport report,
+
Set<ModuleRevisionId> optionalModules,
+
Set<ModuleRevisionId> externalModules) {
Map<ModuleRevisionId, Set<IvyNode>> tree = new HashMap<>();
for (IvyNode dependency : report.getDependencies()) {
- populateDependencyTree(dependency, tree, conf);
+ populateDependencyTree(dependency, tree, optionalModules,
externalModules);
}
return tree;
}
- private void populateDependencyTree(IvyNode node, Map<ModuleRevisionId,
Set<IvyNode>> tree, String conf) {
- if (node.isEvicted(conf)) {
+ private void populateDependencyTree(IvyNode node,
+ Map<ModuleRevisionId, Set<IvyNode>>
tree,
+ Set<ModuleRevisionId> optionalModules,
+ Set<ModuleRevisionId> externalModules)
{
+ if (!isIncluded(node)) {
return;
}
+ ModuleRevisionId mrid = node.getId();
+ if (isOptional(node)) {
+ optionalModules.add(mrid);
+ }
+ if (isExternal(node)) {
+ externalModules.add(mrid);
+ }
- tree.computeIfAbsent(node.getId(), _ignored -> new HashSet<>());
+ tree.computeIfAbsent(mrid, _ignored -> new HashSet<>());
for (Caller caller : node.getAllCallers()) {
addDependency(caller.getModuleRevisionId(), node, tree);
}
@@ -249,6 +295,39 @@ class IvyModuleComponentResolver {
}
}
+ private Set<String> confAsSet(String conf) {
+ if (conf == null) {
+ return Collections.emptySet();
+ }
+ return Arrays.stream(conf.split(","))
+ .map(c -> c.trim())
+ .filter(c -> c.length() > 0)
+ .collect(Collectors.toSet());
+ }
+
+ private boolean isIncluded(IvyNode node) {
+ return includeAllConfigurations
+ || node.getRootModuleConfigurationsSet().stream().anyMatch(c ->
includedConfigurations.contains(c));
+ }
+
+ private boolean isOptional(IvyNode node) {
+ return isSpecial(node, optionalConfigurations);
+ }
+
+ private boolean isExternal(IvyNode node) {
+ return isSpecial(node, externalConfigurations);
+ }
+
+ private boolean isSpecial(IvyNode node, Set<String> specialConfigurations)
{
+ if (specialConfigurations.isEmpty()) {
+ return false;
+ }
+ Set<String> rootConfs = node.getRootModuleConfigurationsSet();
+ Stream<String> includedBecauseOf = includeAllConfigurations ?
rootConfs.stream()
+ : rootConfs.stream().filter(c ->
includedConfigurations.contains(c));
+ return includedBecauseOf.allMatch(c ->
specialConfigurations.contains(c));
+ }
+
private static String getBomRef(IvyNode n) {
ModuleRevisionId mrid = n.getId();
return "pkg:maven/" + mrid.getOrganisation() + "/" + mrid.getName() +
"@" + mrid.getRevision() + "?type=jar";