diff --git a/src/main/java/org/cyclonedx/gradle/ComponentProvider.java b/src/main/java/org/cyclonedx/gradle/ComponentProvider.java new file mode 100644 index 00000000..73ce5eb9 --- /dev/null +++ b/src/main/java/org/cyclonedx/gradle/ComponentProvider.java @@ -0,0 +1,69 @@ +/* + * This file is part of CycloneDX Gradle Plugin. + * + * Licensed 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 + * + * http://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. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) OWASP Foundation. All Rights Reserved. + */ +package org.cyclonedx.gradle; + +import java.util.concurrent.Callable; +import org.cyclonedx.gradle.model.SerializableComponents; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; + +public class ComponentProvider implements Callable { + + private final Project project; + + public ComponentProvider(final Project project) { + this.project = project; + } + + @Override + public SerializableComponents call() throws Exception { + + final CycloneDxDependencyTraverser traverser = + new CycloneDxDependencyTraverser(project.getLogger(), new CycloneDxBomBuilder(project.getLogger())); + + traverseParentProject(traverser); + traverseChildProjects(traverser); + registerArtifacts(traverser); + + return traverser.serializableComponents(); + } + + private void traverseParentProject(final CycloneDxDependencyTraverser traverser) { + project.getConfigurations().stream() + .filter(Configuration::isCanBeResolved) + .forEach(config -> traverser.traverseParentGraph( + config.getIncoming().getResolutionResult().getRoot(), project.getName(), config.getName())); + } + + private void traverseChildProjects(final CycloneDxDependencyTraverser traverser) { + project.getChildProjects().forEach((k, v) -> v.getConfigurations().stream() + .filter(Configuration::isCanBeResolved) + .forEach(config -> traverser.traverseChildGraph( + config.getIncoming().getResolutionResult().getRoot(), k, config.getName()))); + } + + private void registerArtifacts(final CycloneDxDependencyTraverser traverser) { + project.getAllprojects().stream() + .flatMap(project -> project.getConfigurations().stream()) + .filter(Configuration::isCanBeResolved) + .forEach(config -> config.getIncoming().getArtifacts().getArtifacts().stream() + .forEach(artifact -> traverser.registerArtifact( + artifact.getId().getComponentIdentifier(), artifact.getFile()))); + } +} diff --git a/src/main/java/org/cyclonedx/gradle/CycloneDxBomBuilder.java b/src/main/java/org/cyclonedx/gradle/CycloneDxBomBuilder.java index 12133979..791bb463 100644 --- a/src/main/java/org/cyclonedx/gradle/CycloneDxBomBuilder.java +++ b/src/main/java/org/cyclonedx/gradle/CycloneDxBomBuilder.java @@ -22,20 +22,30 @@ import com.networknt.schema.utils.StringUtils; import java.io.File; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.UUID; import java.util.stream.Collectors; import org.apache.commons.io.FilenameUtils; import org.cyclonedx.Version; import org.cyclonedx.gradle.model.ComponentComparator; import org.cyclonedx.gradle.model.DependencyComparator; -import org.cyclonedx.gradle.model.GraphNode; +import org.cyclonedx.gradle.model.SerializableComponent; import org.cyclonedx.gradle.utils.CycloneDxUtils; import org.cyclonedx.gradle.utils.DependencyUtils; -import org.cyclonedx.model.*; +import org.cyclonedx.model.Bom; +import org.cyclonedx.model.Component; +import org.cyclonedx.model.Dependency; +import org.cyclonedx.model.Hash; +import org.cyclonedx.model.Metadata; +import org.cyclonedx.model.Property; import org.cyclonedx.util.BomUtils; -import org.gradle.api.artifacts.ModuleVersionIdentifier; -import org.gradle.api.artifacts.component.ComponentIdentifier; -import org.gradle.api.artifacts.result.ResolvedComponentResult; import org.gradle.api.logging.Logger; public class CycloneDxBomBuilder { @@ -56,30 +66,29 @@ public CycloneDxBomBuilder(final Logger logger) { } public Bom buildBom( - final Map> resultGraph, - final GraphNode parentNode, - final Map resolvedArtifacts) { + final Map> resultGraph, + final SerializableComponent parentComponent) { final Set dependencies = new TreeSet<>(new DependencyComparator()); final Set components = new TreeSet<>(new ComponentComparator()); - resultGraph.keySet().forEach(node -> { - addDependency(dependencies, resultGraph.get(node), node, resolvedArtifacts); - addComponent(components, node, parentNode, resolvedArtifacts); + resultGraph.keySet().forEach(component -> { + addDependency(dependencies, resultGraph.get(component), component); + addComponent(components, component, parentComponent); }); final Bom bom = new Bom(); bom.setSerialNumber("urn:uuid:" + UUID.randomUUID()); - bom.setMetadata(buildMetadata(parentNode)); + bom.setMetadata(buildMetadata(parentComponent)); bom.setComponents(new ArrayList<>(components)); bom.setDependencies(new ArrayList<>(dependencies)); return bom; } - private Metadata buildMetadata(final GraphNode parentNode) { + private Metadata buildMetadata(final SerializableComponent parentComponent) { final Metadata metadata = new Metadata(); try { - metadata.setComponent(toComponent(parentNode, null)); + metadata.setComponent(toComponent(parentComponent, null)); } catch (MalformedPackageURLException e) { logger.warn("Error constructing packageUrl for parent component. Skipping...", e); } @@ -88,78 +97,74 @@ private Metadata buildMetadata(final GraphNode parentNode) { private void addDependency( final Set dependencies, - final Set dependencyNodes, - final GraphNode node, - final Map resolvedArtifacts) { + final Set dependencyComponents, + final SerializableComponent component) { final Dependency dependency; try { - dependency = toDependency(node.getResult(), resolvedArtifacts); + dependency = toDependency(component); } catch (MalformedPackageURLException e) { - logger.warn("Error constructing packageUrl for node. Skipping...", e); + logger.warn("Error constructing packageUrl for component. Skipping...", e); return; } - dependencyNodes.forEach(dependencyNode -> { + dependencyComponents.forEach(dependencyComponent -> { try { - dependency.addDependency(toDependency(dependencyNode.getResult(), resolvedArtifacts)); + dependency.addDependency(toDependency(dependencyComponent)); } catch (MalformedPackageURLException e) { - logger.warn("Error constructing packageUrl for node dependency. Skipping...", e); + logger.warn("Error constructing packageUrl for component dependency. Skipping...", e); } }); dependencies.add(dependency); } - private Dependency toDependency( - final ResolvedComponentResult component, final Map resolvedArtifacts) - throws MalformedPackageURLException { + private Dependency toDependency(final SerializableComponent component) throws MalformedPackageURLException { - final File artifactFile = resolvedArtifacts.get(component.getId()); - final String ref = DependencyUtils.generatePackageUrl(component.getModuleVersion(), getType(artifactFile)); + final String ref = DependencyUtils.generatePackageUrl( + component, getType(component.getArtifactFile().orElse(null))); return new Dependency(ref); } private void addComponent( final Set components, - final GraphNode node, - final GraphNode parentNode, - final Map resolvedArtifacts) { - if (!node.equals(parentNode)) { - final File artifactFile = resolvedArtifacts.get(node.getResult().getId()); + final SerializableComponent component, + final SerializableComponent parentComponent) { + if (!component.equals(parentComponent)) { + final File artifactFile = component.getArtifactFile().orElse(null); try { - components.add(toComponent(node, artifactFile)); + components.add(toComponent(component, artifactFile)); } catch (MalformedPackageURLException e) { - logger.warn("Error constructing packageUrl for node component. Skipping...", e); + logger.warn("Error constructing packageUrl for component. Skipping...", e); } } } - private Component toComponent(final GraphNode node, final File artifactFile) throws MalformedPackageURLException { + private Component toComponent(final SerializableComponent component, final File artifactFile) + throws MalformedPackageURLException { - final ModuleVersionIdentifier moduleVersion = node.getResult().getModuleVersion(); - final String packageUrl = DependencyUtils.generatePackageUrl(moduleVersion, getType(artifactFile)); + final String packageUrl = DependencyUtils.generatePackageUrl(component, getType(artifactFile)); - final Component component = new Component(); - component.setGroup(moduleVersion.getGroup()); - component.setName(moduleVersion.getName()); - component.setVersion(moduleVersion.getVersion()); - component.setType(Component.Type.LIBRARY); - component.setPurl(packageUrl); - component.setProperties(buildProperties(node)); + final Component resultComponent = new Component(); + resultComponent.setGroup(component.getGroup()); + resultComponent.setName(component.getName()); + resultComponent.setVersion(component.getVersion()); + resultComponent.setType(Component.Type.LIBRARY); + resultComponent.setPurl(packageUrl); + resultComponent.setProperties(buildProperties(component)); if (version.getVersion() >= 1.1) { - component.setModified(mavenHelper.isModified(null)); - component.setBomRef(packageUrl); + resultComponent.setModified(mavenHelper.isModified(null)); + resultComponent.setBomRef(packageUrl); } logger.debug(MESSAGE_CALCULATING_HASHES); if (artifactFile != null) { - component.setHashes(calculateHashes(artifactFile)); + resultComponent.setHashes(calculateHashes(artifactFile)); } - return component; + return resultComponent; } - private List buildProperties(GraphNode node) { - return node.getInScopeConfigurations().stream() + private List buildProperties(SerializableComponent component) { + return component.getInScopeConfigurations().stream() .map(v -> { Property property = new Property(); property.setName("inScopeConfiguration"); diff --git a/src/main/java/org/cyclonedx/gradle/CycloneDxDependencyTraverser.java b/src/main/java/org/cyclonedx/gradle/CycloneDxDependencyTraverser.java index b368a84c..7d2d5d84 100644 --- a/src/main/java/org/cyclonedx/gradle/CycloneDxDependencyTraverser.java +++ b/src/main/java/org/cyclonedx/gradle/CycloneDxDependencyTraverser.java @@ -22,14 +22,17 @@ import java.io.File; import java.util.ArrayDeque; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Queue; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; -import org.cyclonedx.gradle.model.ArtifactInfo; -import org.cyclonedx.gradle.model.GraphNode; -import org.cyclonedx.model.*; +import java.util.stream.Collectors; +import org.cyclonedx.gradle.model.ConfigurationScope; +import org.cyclonedx.gradle.model.SerializableComponent; +import org.cyclonedx.gradle.model.SerializableComponents; import org.gradle.api.GradleException; import org.gradle.api.artifacts.ModuleVersionIdentifier; import org.gradle.api.artifacts.component.ComponentIdentifier; @@ -37,6 +40,7 @@ import org.gradle.api.artifacts.result.ResolvedComponentResult; import org.gradle.api.artifacts.result.ResolvedDependencyResult; import org.gradle.api.logging.Logger; +import org.jetbrains.annotations.NotNull; public class CycloneDxDependencyTraverser { @@ -53,8 +57,8 @@ public CycloneDxDependencyTraverser(final Logger logger, final CycloneDxBomBuild this.resultGraph = new HashMap<>(); } - public void registerArtifact(final ArtifactInfo artifact) { - resolvedArtifacts.put(artifact.getComponentId(), artifact.getArtifactFile()); + public void registerArtifact(final ComponentIdentifier componentId, final File artifactFile) { + resolvedArtifacts.put(componentId, artifactFile); } public void traverseParentGraph( @@ -123,8 +127,35 @@ private void mergeIntoResultGraph( .forEach(v -> v.inScopeConfiguration(projectName, configName)); } - public Bom toBom() { - return builder.buildBom(this.resultGraph, this.parentNode, this.resolvedArtifacts); + public SerializableComponents serializableComponents() { + + Map> result = new HashMap<>(); + this.resultGraph.forEach((k, v) -> { + result.put( + serializableComponent(k), + v.stream().map(w -> serializableComponent(w)).collect(Collectors.toSet())); + }); + + return new SerializableComponents(result, serializableComponent(this.parentNode)); + } + + private SerializableComponent serializableComponent(final GraphNode node) { + + ResolvedComponentResult resolvedComponent = node.getResult(); + if (this.resolvedArtifacts.containsKey(resolvedComponent.getId())) { + return new SerializableComponent( + resolvedComponent.getModuleVersion().getGroup(), + resolvedComponent.getModuleVersion().getName(), + resolvedComponent.getModuleVersion().getVersion(), + node.getInScopeConfigurations(), + this.resolvedArtifacts.get(resolvedComponent.getId())); + } else { + return new SerializableComponent( + resolvedComponent.getModuleVersion().getGroup(), + resolvedComponent.getModuleVersion().getName(), + resolvedComponent.getModuleVersion().getVersion(), + node.getInScopeConfigurations()); + } } private String getRef(final ModuleVersionIdentifier identifier) { @@ -140,4 +171,51 @@ private String getRef(final ModuleVersionIdentifier identifier) { return String.format("%s:%s:%s", identifier.getGroup(), identifier.getName(), identifier.getVersion()); } + + private static class GraphNode implements Comparable { + + private final String ref; + private final ResolvedComponentResult result; + private final Set inScopeConfigurations; + + private GraphNode(final String ref, final ResolvedComponentResult result) { + this.ref = ref; + this.result = result; + this.inScopeConfigurations = new HashSet<>(); + } + + private String getRef() { + return ref; + } + + private ResolvedComponentResult getResult() { + return result; + } + + private void inScopeConfiguration(final String projectName, final String configName) { + inScopeConfigurations.add(new ConfigurationScope(projectName, configName)); + } + + private Set getInScopeConfigurations() { + return inScopeConfigurations; + } + + @Override + public int compareTo(@NotNull GraphNode o) { + return this.ref.compareTo(o.ref); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GraphNode graphNode = (GraphNode) o; + return Objects.equals(ref, graphNode.ref); + } + + @Override + public int hashCode() { + return Objects.hashCode(ref); + } + } } diff --git a/src/main/java/org/cyclonedx/gradle/CycloneDxPlugin.java b/src/main/java/org/cyclonedx/gradle/CycloneDxPlugin.java index a9dfc54d..f066da2d 100644 --- a/src/main/java/org/cyclonedx/gradle/CycloneDxPlugin.java +++ b/src/main/java/org/cyclonedx/gradle/CycloneDxPlugin.java @@ -19,65 +19,24 @@ package org.cyclonedx.gradle; import java.io.File; -import java.util.*; -import java.util.stream.Collectors; -import org.cyclonedx.gradle.model.*; +import org.cyclonedx.gradle.model.SerializableComponents; import org.gradle.api.Plugin; import org.gradle.api.Project; -import org.gradle.api.artifacts.Configuration; -import org.gradle.api.artifacts.result.ResolvedArtifactResult; import org.gradle.api.provider.Provider; public class CycloneDxPlugin implements Plugin { public void apply(Project project) { project.getTasks().register("cyclonedxBom", CycloneDxTask.class, (task) -> { - final ResolvedBuild resolvedBuild = getResolvedBuild(project); - final Provider> artifacts = getArtifacts(project); + final Provider components = + project.getProviders().provider(new ComponentProvider(project)); final File destination = project.getLayout().getBuildDirectory().dir("reports").get().getAsFile(); - task.getResolvedBuild().set(resolvedBuild); + task.getComponents().set(components); task.getDestination().set(destination); task.setGroup("Reporting"); task.setDescription("Generates a CycloneDX compliant Software Bill of Materials (SBOM)"); - task.getArtifacts().set(artifacts); }); } - - private ResolvedBuild getResolvedBuild(final Project project) { - - final ResolvedBuild resolvedBuild = new ResolvedBuild(project.getName()); - project.getConfigurations().stream() - .filter(Configuration::isCanBeResolved) - .forEach(v -> resolvedBuild.addProjectConfiguration(resolvedConfiguration(v))); - - project.getChildProjects().forEach((k, v) -> v.getConfigurations().stream() - .filter(Configuration::isCanBeResolved) - .forEach(w -> resolvedBuild.addSubProjectConfiguration(k, resolvedConfiguration(w)))); - - return resolvedBuild; - } - - private Provider> getArtifacts(final Project project) { - - final List configurations = project.getAllprojects().stream() - .flatMap(v -> v.getConfigurations().stream()) - .collect(Collectors.toList()); - - return project.getProviders().provider(() -> configurations.stream() - .filter(Configuration::isCanBeResolved) - .flatMap(config -> config.getIncoming().getArtifacts().getArtifacts().stream() - .map(artifact -> toArtifactInfo(artifact))) - .collect(Collectors.toSet())); - } - - private ArtifactInfo toArtifactInfo(final ResolvedArtifactResult result) { - return new ArtifactInfo(result.getId().getComponentIdentifier(), result.getFile()); - } - - private ResolvedConfiguration resolvedConfiguration(final Configuration config) { - return new ResolvedConfiguration( - config.getName(), config.getIncoming().getResolutionResult().getRootComponent()); - } } diff --git a/src/main/java/org/cyclonedx/gradle/CycloneDxTask.java b/src/main/java/org/cyclonedx/gradle/CycloneDxTask.java index 9335289b..5aa7c5f8 100644 --- a/src/main/java/org/cyclonedx/gradle/CycloneDxTask.java +++ b/src/main/java/org/cyclonedx/gradle/CycloneDxTask.java @@ -19,59 +19,33 @@ package org.cyclonedx.gradle; import java.io.File; -import java.util.Map; -import java.util.Set; -import org.cyclonedx.gradle.model.ArtifactInfo; -import org.cyclonedx.gradle.model.ResolvedBuild; -import org.cyclonedx.gradle.model.ResolvedConfiguration; +import org.cyclonedx.gradle.model.SerializableComponents; import org.cyclonedx.gradle.utils.CycloneDxUtils; import org.gradle.api.DefaultTask; import org.gradle.api.provider.Property; -import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.TaskAction; public abstract class CycloneDxTask extends DefaultTask { - private final CycloneDxDependencyTraverser traverser; + private final CycloneDxBomBuilder builder; public CycloneDxTask() { - this.traverser = new CycloneDxDependencyTraverser(getLogger(), new CycloneDxBomBuilder(getLogger())); + this.builder = new CycloneDxBomBuilder(getLogger()); } @Input - public abstract Property getResolvedBuild(); + public abstract Property getComponents(); @Input public abstract Property getDestination(); - @Input - public abstract SetProperty getArtifacts(); - @TaskAction public void createBom() { - final ResolvedBuild resolvedBuild = getResolvedBuild().get(); - - registerArtifacts(); - buildParentDependencies(resolvedBuild.getProjectName(), resolvedBuild.getProjectConfigurations()); - buildChildDependencies(resolvedBuild.getSubProjectsConfigurations()); - File destination = new File(getDestination().get(), "bom.json"); - CycloneDxUtils.writeBom(traverser.toBom(), destination); - } - - private void buildParentDependencies(final String projectName, Set configurations) { - configurations.forEach(config -> traverser.traverseParentGraph( - config.getDependencyGraph().get(), projectName, config.getConfigurationName())); - } - - private void buildChildDependencies(final Map> configurations) { - configurations.forEach((key, value) -> value.forEach(config -> - traverser.traverseChildGraph(config.getDependencyGraph().get(), key, config.getConfigurationName()))); - } - - private void registerArtifacts() { - getArtifacts().get().forEach(traverser::registerArtifact); + SerializableComponents components = getComponents().get(); + CycloneDxUtils.writeBom( + builder.buildBom(components.getSerializableComponents(), components.getRootComponent()), destination); } } diff --git a/src/main/java/org/cyclonedx/gradle/model/ArtifactInfo.java b/src/main/java/org/cyclonedx/gradle/model/ConfigurationScope.java similarity index 60% rename from src/main/java/org/cyclonedx/gradle/model/ArtifactInfo.java rename to src/main/java/org/cyclonedx/gradle/model/ConfigurationScope.java index 8a9c40fa..9dca898e 100644 --- a/src/main/java/org/cyclonedx/gradle/model/ArtifactInfo.java +++ b/src/main/java/org/cyclonedx/gradle/model/ConfigurationScope.java @@ -18,24 +18,23 @@ */ package org.cyclonedx.gradle.model; -import java.io.File; -import org.gradle.api.artifacts.component.ComponentIdentifier; +import java.io.Serializable; -public class ArtifactInfo { +public class ConfigurationScope implements Serializable { - private final ComponentIdentifier componentId; - private final File artifactFile; + private final String projectName; + private final String configName; - public ArtifactInfo(final ComponentIdentifier componentId, final File artifactFile) { - this.componentId = componentId; - this.artifactFile = artifactFile; + public ConfigurationScope(final String projectName, final String configName) { + this.projectName = projectName; + this.configName = configName; } - public ComponentIdentifier getComponentId() { - return componentId; + public String getProjectName() { + return projectName; } - public File getArtifactFile() { - return artifactFile; + public String getConfigName() { + return configName; } } diff --git a/src/main/java/org/cyclonedx/gradle/model/GraphNode.java b/src/main/java/org/cyclonedx/gradle/model/GraphNode.java deleted file mode 100644 index a43539fc..00000000 --- a/src/main/java/org/cyclonedx/gradle/model/GraphNode.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * This file is part of CycloneDX Gradle Plugin. - * - * Licensed 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 - * - * http://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. - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright (c) OWASP Foundation. All Rights Reserved. - */ -package org.cyclonedx.gradle.model; - -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import org.gradle.api.artifacts.result.ResolvedComponentResult; -import org.jetbrains.annotations.NotNull; - -public class GraphNode implements Comparable { - - private final String ref; - private final ResolvedComponentResult result; - private final Set inScopeConfigurations; - - public GraphNode(final String ref, final ResolvedComponentResult result) { - this.ref = ref; - this.result = result; - this.inScopeConfigurations = new HashSet<>(); - } - - public String getRef() { - return ref; - } - - public ResolvedComponentResult getResult() { - return result; - } - - public void inScopeConfiguration(final String projectName, final String configName) { - inScopeConfigurations.add(new ConfigurationScope(projectName, configName)); - } - - public Set getInScopeConfigurations() { - return inScopeConfigurations; - } - - @Override - public int compareTo(@NotNull GraphNode o) { - return this.ref.compareTo(o.ref); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - GraphNode graphNode = (GraphNode) o; - return Objects.equals(ref, graphNode.ref); - } - - @Override - public int hashCode() { - return Objects.hashCode(ref); - } - - public static class ConfigurationScope { - private final String projectName; - private final String configName; - - private ConfigurationScope(final String projectName, final String configName) { - this.projectName = projectName; - this.configName = configName; - } - - public String getProjectName() { - return projectName; - } - - public String getConfigName() { - return configName; - } - } -} diff --git a/src/main/java/org/cyclonedx/gradle/model/ResolvedBuild.java b/src/main/java/org/cyclonedx/gradle/model/ResolvedBuild.java deleted file mode 100644 index 22509fc4..00000000 --- a/src/main/java/org/cyclonedx/gradle/model/ResolvedBuild.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * This file is part of CycloneDX Gradle Plugin. - * - * Licensed 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 - * - * http://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. - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright (c) OWASP Foundation. All Rights Reserved. - */ -package org.cyclonedx.gradle.model; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -public class ResolvedBuild { - private final String projectName; - private final Set projectConfigurations; - private final Map> subProjectsConfigurations; - - public ResolvedBuild(final String projectName) { - this.projectName = projectName; - this.projectConfigurations = new HashSet<>(); - this.subProjectsConfigurations = new HashMap<>(); - } - - public String getProjectName() { - return projectName; - } - - public void addProjectConfiguration(final ResolvedConfiguration configuration) { - projectConfigurations.add(configuration); - } - - public Set getProjectConfigurations() { - return projectConfigurations; - } - - public void addSubProjectConfiguration(final String projectName, final ResolvedConfiguration configuration) { - if (subProjectsConfigurations.containsKey(projectName)) { - subProjectsConfigurations.get(projectName).add(configuration); - } else { - final Set subProjectConfigurations = new HashSet<>(); - subProjectConfigurations.add(configuration); - subProjectsConfigurations.put(projectName, subProjectConfigurations); - } - } - - public Map> getSubProjectsConfigurations() { - return subProjectsConfigurations; - } -} diff --git a/src/main/java/org/cyclonedx/gradle/model/ResolvedConfiguration.java b/src/main/java/org/cyclonedx/gradle/model/ResolvedConfiguration.java deleted file mode 100644 index 385bbadf..00000000 --- a/src/main/java/org/cyclonedx/gradle/model/ResolvedConfiguration.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * This file is part of CycloneDX Gradle Plugin. - * - * Licensed 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 - * - * http://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. - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright (c) OWASP Foundation. All Rights Reserved. - */ -package org.cyclonedx.gradle.model; - -import org.gradle.api.artifacts.result.ResolvedComponentResult; -import org.gradle.api.provider.Provider; - -public class ResolvedConfiguration { - - private final String configurationName; - private final Provider dependencyGraph; - - public ResolvedConfiguration( - final String configurationName, final Provider dependencyGraph) { - this.configurationName = configurationName; - this.dependencyGraph = dependencyGraph; - } - - public String getConfigurationName() { - return configurationName; - } - - public Provider getDependencyGraph() { - return dependencyGraph; - } -} diff --git a/src/main/java/org/cyclonedx/gradle/model/SerializableComponent.java b/src/main/java/org/cyclonedx/gradle/model/SerializableComponent.java new file mode 100644 index 00000000..3c65ebcb --- /dev/null +++ b/src/main/java/org/cyclonedx/gradle/model/SerializableComponent.java @@ -0,0 +1,91 @@ +/* + * This file is part of CycloneDX Gradle Plugin. + * + * Licensed 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 + * + * http://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. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) OWASP Foundation. All Rights Reserved. + */ +package org.cyclonedx.gradle.model; + +import java.io.File; +import java.io.Serializable; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +public final class SerializableComponent implements Serializable { + + private final String group; + private final String name; + private final String version; + private final File artifactFile; + private final Set inScopeConfigurations; + + public SerializableComponent( + final String group, + final String name, + final String version, + final Set inScopeConfigurations) { + this(group, name, version, inScopeConfigurations, null); + } + + public SerializableComponent( + final String group, + final String name, + final String version, + final Set inScopeConfigurations, + final File artifactFile) { + this.group = group; + this.name = name; + this.version = version; + this.artifactFile = artifactFile; + this.inScopeConfigurations = inScopeConfigurations; + } + + public String getGroup() { + return group; + } + + public String getName() { + return name; + } + + public String getVersion() { + return version; + } + + public Set getInScopeConfigurations() { + return inScopeConfigurations; + } + + public Optional getArtifactFile() { + return Optional.ofNullable(artifactFile); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SerializableComponent that = (SerializableComponent) o; + return Objects.equals(group, that.group) + && Objects.equals(name, that.name) + && Objects.equals(version, that.version) + && Objects.equals(artifactFile, that.artifactFile); + } + + @Override + public int hashCode() { + return Objects.hash(group, name, version, artifactFile); + } +} diff --git a/src/main/java/org/cyclonedx/gradle/model/SerializableComponents.java b/src/main/java/org/cyclonedx/gradle/model/SerializableComponents.java new file mode 100644 index 00000000..77089ae6 --- /dev/null +++ b/src/main/java/org/cyclonedx/gradle/model/SerializableComponents.java @@ -0,0 +1,44 @@ +/* + * This file is part of CycloneDX Gradle Plugin. + * + * Licensed 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 + * + * http://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. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) OWASP Foundation. All Rights Reserved. + */ +package org.cyclonedx.gradle.model; + +import java.io.Serializable; +import java.util.Map; +import java.util.Set; + +public class SerializableComponents implements Serializable { + + private final Map> serializableComponents; + private final SerializableComponent rootComponent; + + public SerializableComponents( + Map> serializableComponents, + SerializableComponent rootComponent) { + this.serializableComponents = serializableComponents; + this.rootComponent = rootComponent; + } + + public Map> getSerializableComponents() { + return serializableComponents; + } + + public SerializableComponent getRootComponent() { + return rootComponent; + } +} diff --git a/src/main/java/org/cyclonedx/gradle/utils/DependencyUtils.java b/src/main/java/org/cyclonedx/gradle/utils/DependencyUtils.java index 016b6185..68e353ce 100644 --- a/src/main/java/org/cyclonedx/gradle/utils/DependencyUtils.java +++ b/src/main/java/org/cyclonedx/gradle/utils/DependencyUtils.java @@ -21,18 +21,18 @@ import com.github.packageurl.MalformedPackageURLException; import com.github.packageurl.PackageURL; import java.util.TreeMap; -import org.gradle.api.artifacts.ModuleVersionIdentifier; +import org.cyclonedx.gradle.model.SerializableComponent; public class DependencyUtils { public static String generatePackageUrl( - final ModuleVersionIdentifier version, final TreeMap qualifiers) + final SerializableComponent component, final TreeMap qualifiers) throws MalformedPackageURLException { return new PackageURL( PackageURL.StandardTypes.MAVEN, - version.getGroup(), - version.getName(), - version.getVersion(), + component.getGroup(), + component.getName(), + component.getVersion(), qualifiers, null) .canonicalize(); diff --git a/src/test/groovy/org/cyclonedx/gradle/PluginConfigurationSpec.groovy b/src/test/groovy/org/cyclonedx/gradle/PluginConfigurationSpec.groovy index 2ccd86b6..cef7b0db 100644 --- a/src/test/groovy/org/cyclonedx/gradle/PluginConfigurationSpec.groovy +++ b/src/test/groovy/org/cyclonedx/gradle/PluginConfigurationSpec.groovy @@ -159,6 +159,7 @@ class PluginConfigurationSpec extends Specification { cyclonedxBom { // No componentName override -> Use rootProject.name } + dependencies { implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version:'2.8.11' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web', version:'1.5.18.RELEASE'