Skip to content

Commit

Permalink
feat: support build configuration cache
Browse files Browse the repository at this point in the history
Signed-off-by: Gordon <[email protected]>
  • Loading branch information
gordonrousselle committed Oct 28, 2024
1 parent 010b8d1 commit e016f01
Show file tree
Hide file tree
Showing 13 changed files with 373 additions and 347 deletions.
69 changes: 69 additions & 0 deletions src/main/java/org/cyclonedx/gradle/ComponentProvider.java
Original file line number Diff line number Diff line change
@@ -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<SerializableComponents> {

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())));
}
}
107 changes: 56 additions & 51 deletions src/main/java/org/cyclonedx/gradle/CycloneDxBomBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -56,30 +66,29 @@ public CycloneDxBomBuilder(final Logger logger) {
}

public Bom buildBom(
final Map<GraphNode, Set<GraphNode>> resultGraph,
final GraphNode parentNode,
final Map<ComponentIdentifier, File> resolvedArtifacts) {
final Map<SerializableComponent, Set<SerializableComponent>> resultGraph,
final SerializableComponent parentComponent) {

final Set<Dependency> dependencies = new TreeSet<>(new DependencyComparator());
final Set<Component> 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);
}
Expand All @@ -88,78 +97,74 @@ private Metadata buildMetadata(final GraphNode parentNode) {

private void addDependency(
final Set<Dependency> dependencies,
final Set<GraphNode> dependencyNodes,
final GraphNode node,
final Map<ComponentIdentifier, File> resolvedArtifacts) {
final Set<SerializableComponent> 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<ComponentIdentifier, File> 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<Component> components,
final GraphNode node,
final GraphNode parentNode,
final Map<ComponentIdentifier, File> 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<Property> buildProperties(GraphNode node) {
return node.getInScopeConfigurations().stream()
private List<Property> buildProperties(SerializableComponent component) {
return component.getInScopeConfigurations().stream()
.map(v -> {
Property property = new Property();
property.setName("inScopeConfiguration");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,25 @@
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;
import org.gradle.api.artifacts.result.DependencyResult;
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 {

Expand All @@ -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(
Expand Down Expand Up @@ -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<SerializableComponent, Set<SerializableComponent>> 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) {
Expand All @@ -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<GraphNode> {

private final String ref;
private final ResolvedComponentResult result;
private final Set<ConfigurationScope> 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<ConfigurationScope> 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);
}
}
}
Loading

0 comments on commit e016f01

Please sign in to comment.