Skip to content

Commit

Permalink
WIP - documentation and refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
rudsberg committed Sep 12, 2024
1 parent b245a7b commit 12234ea
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ public class NativeCompileNoForkMojo extends AbstractNativeImageMojo {
@Parameter(property = "skipNativeBuildForPom", defaultValue = "false")
private boolean skipNativeBuildForPom;

@Parameter(property = "enableSBOM", defaultValue = "true")
public static final String enableSBOMParamName = "enableSBOM";
@Parameter(property = enableSBOMParamName, defaultValue = "true")
private boolean enableSBOM;

private PluginParameterExpressionEvaluator evaluator;
Expand Down Expand Up @@ -105,8 +106,7 @@ public void execute() throws MojoExecutionException {
maybeSetMainClassFromPlugin(this::consumeConfigurationNodeValue, "org.apache.maven.plugins:maven-jar-plugin", "archive", "manifest", "mainClass");
maybeAddGeneratedResourcesConfig(buildArgs);

// TODO: use flag
if (true) {
if (enableSBOM) {
var generator = new SBOMGenerator(mavenProject, mavenSession, pluginManager, repositorySystem, mainClass);
generator.generate();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,54 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.graalvm.buildtools.maven.sbom;

import java.net.URI;
import java.util.HashSet;
import java.util.Set;

class ArtifactAdapter {
/**
* Data container that: (I) is an adapter between {@link org.apache.maven.artifact.Artifact} and
* {@link org.eclipse.aether.artifact.Artifact}; and (II) adds fields for the augmented component fields.
*/
final class ArtifactAdapter {
final String groupId;
final String artifactId;
final String version;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -38,7 +38,6 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package org.graalvm.buildtools.maven.sbom;

import org.apache.maven.artifact.Artifact;
Expand All @@ -57,7 +56,7 @@
import java.util.*;
import java.util.stream.Collectors;

class ArtifactToPackageNameResolver {
final class ArtifactToPackageNameResolver {
private final MavenProject mavenProject;
private final RepositorySystem repositorySystem;
private final RepositorySystemSession repositorySystemSession;
Expand All @@ -81,6 +80,7 @@ Set<ArtifactAdapter> getArtifactPackageMappings() throws Exception {
Optional<ArtifactAdapter> optionalArtifact = resolvePackageNamesFromArtifact(artifact);
optionalArtifact.ifPresent(artifactsWithPackageNameMappings::add);
}

Set<ArtifactAdapter> dependencies = artifactsWithPackageNameMappings.stream()
.filter(v -> !v.equals(mavenProject.getArtifact()))
.collect(Collectors.toSet());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,43 @@
/*
* Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.graalvm.buildtools.maven.sbom;

import java.io.File;
Expand All @@ -9,7 +49,7 @@
import java.util.Set;
import java.util.function.Consumer;

class FileWalkerUtility {
final class FileWalkerUtility {
static Optional<Set<String>> walkFileTreeAndCollectPackageNames(Path pathToSearchIn) throws IOException {
return walkFileTreeAndCollectPackageNames(pathToSearchIn, pathToSearchIn);
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -38,7 +38,6 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package org.graalvm.buildtools.maven.sbom;

import com.fasterxml.jackson.databind.JsonNode;
Expand All @@ -50,6 +49,7 @@
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.RepositorySystem;
import org.graalvm.buildtools.maven.NativeCompileNoForkMojo;

import java.io.IOException;
import java.nio.file.Files;
Expand All @@ -63,9 +63,30 @@
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static org.twdata.maven.mojoexecutor.MojoExecutor.*;

// TODO: produced JSON is not formatted nicely
// TODO: remove debug prints
public class SBOMGenerator {
/**
* Generates an enhanced Software Bill of Materials (SBOM) for Native Image consumption and refinement.
* <p>
* Process overview:
* 1. Utilizes the cyclonedx-maven-plugin to create a baseline SBOM.
* 2. Augments the baseline SBOM components with additional metadata (see {@link AugmentedFields}):
* * "packageNames": A list of all package names associated with each component.
* * "jarPath": Path to the component jar.
* * "prunable": Boolean indicating if the component can be pruned. We currently set this to false for
* any dependencies to the main component that are shaded.
* 3. Stores the enhanced SBOM at a known location.
* 4. Native Image then processes this SBOM during its static analysis:
* * Unreachable components are removed.
* * Unnecessary dependency relationships are pruned.
* <p>
* Creating the package-name-to-component mapping in the context of Native Image, without any build-system
* knowledge is difficult, which was the primary motivation for realizing this approach.
* <p>
* Benefits:
* * Great Baseline: Produces an industry-standard SBOM at minimum.
* * Enhanced Accuracy: Native Image static analysis refines the SBOM,
* potentially significantly improving its accuracy.
*/
final public class SBOMGenerator {
private final MavenProject mavenProject;
private final MavenSession mavenSession;
private final BuildPluginManager pluginManager;
Expand All @@ -75,6 +96,12 @@ public class SBOMGenerator {
private static final String SBOM_NAME = "WIP_SBOM";
private static final String FILE_FORMAT = "json";

private static final class AugmentedFields {
static final String packageNames = "packageNames";
static final String jarPath = "jarPath";
static final String prunable = "prunable";
}

public SBOMGenerator(MavenProject mavenProject, MavenSession mavenSession, BuildPluginManager pluginManager, RepositorySystem repositorySystem, String mainClass) {
this.mavenProject = mavenProject;
this.mavenSession = mavenSession;
Expand All @@ -83,9 +110,13 @@ public SBOMGenerator(MavenProject mavenProject, MavenSession mavenSession, Build
this.mainClass = mainClass;
}

/**
* Generates an SBOM that will be further augmented by Native Image. The SBOM is stored in the build directory.
*
* @throws MojoExecutionException if SBOM creation fails.
*/
public void generate() throws MojoExecutionException {
try {
System.out.println("💣 Generating SBOM...");
String outputDirectory = mavenProject.getBuild().getDirectory();
executeMojo(
plugin(
Expand All @@ -104,49 +135,45 @@ public void generate() throws MojoExecutionException {
);

Path sbomPath = Paths.get(outputDirectory, SBOM_NAME + "." + FILE_FORMAT);
// TODO: remove
Files.copy(sbomPath, Paths.get(outputDirectory, "SBOM_UNMODIFIED.json"), REPLACE_EXISTING);
if (!Files.exists(sbomPath)) {
// TODO: warning instead?
throw new MojoExecutionException("SBOM file not found at " + sbomPath);
return;
}
// TODO: debugging only
Files.copy(sbomPath, Paths.get(outputDirectory, "SBOM_UNMODIFIED" + "." + FILE_FORMAT), REPLACE_EXISTING);
System.out.println("✅ CycloneDX SBOM generated successfully: " + sbomPath);

var resolver = new ArtifactToPackageNameResolver(mavenProject, repositorySystem, mavenSession.getRepositorySession(), mainClass);
Set<ArtifactAdapter> artifactsWithPackageNames = resolver.getArtifactPackageMappings();
augmentSBOMWithPackageNames(sbomPath, artifactsWithPackageNames);
} catch (Exception e) {
throw new MojoExecutionException("Failed to generate and augment SBOM", e);
augmentSBOM(sbomPath, artifactsWithPackageNames);
} catch (Exception exception) {
String errorMsg = String.format("Failed to create SBOM. Please try again and report this issue if it persists. " +
"To bypass this failure, disable SBOM generation by setting %s to false.", NativeCompileNoForkMojo.enableSBOMParamName);
throw new MojoExecutionException(errorMsg, exception);
}
}

private void augmentSBOMWithPackageNames(Path sbomPath, Set<ArtifactAdapter> artifactToPackageNames) throws IOException {
private void augmentSBOM(Path sbomPath, Set<ArtifactAdapter> artifactToPackageNames) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode sbomJson = (ObjectNode) objectMapper.readTree(Files.newInputStream(sbomPath));

ArrayNode componentsArray = (ArrayNode) sbomJson.get("components");
if (componentsArray == null) {
System.out.println("⚠️ No components found in the SBOM.");
return;
}

/*
* Iterates over the components and finds the associated artifact by equality checks of the GAV coordinates.
* If a match is found, the component is augmented with "packageNames" and "jarPath" fields.
* If a match is found, the component is augmented.
*/
componentsArray.forEach(componentNode -> augmentComponentNode(componentNode, artifactToPackageNames, objectMapper));

/* Augment the main component in "metadata/component" */
JsonNode metadataNode = sbomJson.get("metadata");
if (metadataNode != null && metadataNode.has("component")) {
augmentComponentNode(metadataNode.get("component"), artifactToPackageNames, objectMapper);
} else {
System.out.println("⚠️ No main component found in metadata.");
}

/* Save the augmented SBOM back to the file */
objectMapper.writerWithDefaultPrettyPrinter().writeValue(Files.newOutputStream(sbomPath), sbomJson);
System.out.println("✅ SBOM updated successfully with package names.");
}

private void augmentComponentNode(JsonNode componentNode, Set<ArtifactAdapter> artifactsWithPackageNames, ObjectMapper objectMapper) {
Expand All @@ -169,23 +196,15 @@ private void augmentComponentNode(JsonNode componentNode, Set<ArtifactAdapter> a
ArrayNode packageNamesArray = objectMapper.createArrayNode();
List<String> sortedPackageNames = artifact.packageNames.stream().sorted().collect(Collectors.toList());
sortedPackageNames.forEach(packageNamesArray::add);
((ObjectNode) componentNode).set("packageNames", packageNamesArray);
((ObjectNode) componentNode).set(AugmentedFields.packageNames, packageNamesArray);

String jarPath = "";
if (artifact.jarPath != null) {
jarPath = artifact.jarPath.toString();
} else {
System.out.printf("⚠️ jarPath is null for %s.%s\n", artifact.groupId, artifact.artifactId);
}
((ObjectNode) componentNode).put("jarPath", jarPath);
((ObjectNode) componentNode).put("prunable", artifact.prunable);

System.out.printf("Added package names and jarPath %s to component %s:%s:%s\n", jarPath, groupId, artifactId, version);
} else {
System.out.printf("⚠️ No matching artifact found for %s:%s:%s\n", groupId, artifactId, version);
((ObjectNode) componentNode).put(AugmentedFields.jarPath, jarPath);
((ObjectNode) componentNode).put(AugmentedFields.prunable, artifact.prunable);
}
} else {
System.out.printf("⚠️ component node missing group or name or version info: %s\n", componentNode);
}
}
}
Loading

0 comments on commit 12234ea

Please sign in to comment.