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
commit 23d0c48a31cbe8772ad7603ccfcaa88967588fb0 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 6ab79ae..439202a 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 a5c6839..f1bf861 100644 --- a/src/main/org/apache/ant/cyclonedx/Component.java +++ b/src/main/org/apache/ant/cyclonedx/Component.java @@ -1110,11 +1110,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> @@ -1159,5 +1161,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";
