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";

Reply via email to