Skip to content

Commit 12234ea

Browse files
committedSep 12, 2024
WIP - documentation and refactor
1 parent b245a7b commit 12234ea

File tree

7 files changed

+182
-77
lines changed

7 files changed

+182
-77
lines changed
 

‎native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ public class NativeCompileNoForkMojo extends AbstractNativeImageMojo {
7474
@Parameter(property = "skipNativeBuildForPom", defaultValue = "false")
7575
private boolean skipNativeBuildForPom;
7676

77-
@Parameter(property = "enableSBOM", defaultValue = "true")
77+
public static final String enableSBOMParamName = "enableSBOM";
78+
@Parameter(property = enableSBOMParamName, defaultValue = "true")
7879
private boolean enableSBOM;
7980

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

108-
// TODO: use flag
109-
if (true) {
109+
if (enableSBOM) {
110110
var generator = new SBOMGenerator(mavenProject, mavenSession, pluginManager, repositorySystem, mainClass);
111111
generator.generate();
112112
}

‎native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/sbom/ArtifactAdapter.java

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,54 @@
1+
/*
2+
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
141
package org.graalvm.buildtools.maven.sbom;
242

343
import java.net.URI;
444
import java.util.HashSet;
545
import java.util.Set;
646

7-
class ArtifactAdapter {
47+
/**
48+
* Data container that: (I) is an adapter between {@link org.apache.maven.artifact.Artifact} and
49+
* {@link org.eclipse.aether.artifact.Artifact}; and (II) adds fields for the augmented component fields.
50+
*/
51+
final class ArtifactAdapter {
852
final String groupId;
953
final String artifactId;
1054
final String version;

‎native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/sbom/ArtifactToPackageNameResolver.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -38,7 +38,6 @@
3838
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3939
* SOFTWARE.
4040
*/
41-
4241
package org.graalvm.buildtools.maven.sbom;
4342

4443
import org.apache.maven.artifact.Artifact;
@@ -57,7 +56,7 @@
5756
import java.util.*;
5857
import java.util.stream.Collectors;
5958

60-
class ArtifactToPackageNameResolver {
59+
final class ArtifactToPackageNameResolver {
6160
private final MavenProject mavenProject;
6261
private final RepositorySystem repositorySystem;
6362
private final RepositorySystemSession repositorySystemSession;
@@ -81,6 +80,7 @@ Set<ArtifactAdapter> getArtifactPackageMappings() throws Exception {
8180
Optional<ArtifactAdapter> optionalArtifact = resolvePackageNamesFromArtifact(artifact);
8281
optionalArtifact.ifPresent(artifactsWithPackageNameMappings::add);
8382
}
83+
8484
Set<ArtifactAdapter> dependencies = artifactsWithPackageNameMappings.stream()
8585
.filter(v -> !v.equals(mavenProject.getArtifact()))
8686
.collect(Collectors.toSet());

‎native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/sbom/FileWalkerUtility.java

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,43 @@
1+
/*
2+
* Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
141
package org.graalvm.buildtools.maven.sbom;
242

343
import java.io.File;
@@ -9,7 +49,7 @@
949
import java.util.Set;
1050
import java.util.function.Consumer;
1151

12-
class FileWalkerUtility {
52+
final class FileWalkerUtility {
1353
static Optional<Set<String>> walkFileTreeAndCollectPackageNames(Path pathToSearchIn) throws IOException {
1454
return walkFileTreeAndCollectPackageNames(pathToSearchIn, pathToSearchIn);
1555
}

‎native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/sbom/PackageNameExtractor.java

Lines changed: 0 additions & 38 deletions
This file was deleted.

‎native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/sbom/SBOMGenerator.java

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -38,7 +38,6 @@
3838
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3939
* SOFTWARE.
4040
*/
41-
4241
package org.graalvm.buildtools.maven.sbom;
4342

4443
import com.fasterxml.jackson.databind.JsonNode;
@@ -50,6 +49,7 @@
5049
import org.apache.maven.plugin.MojoExecutionException;
5150
import org.apache.maven.project.MavenProject;
5251
import org.eclipse.aether.RepositorySystem;
52+
import org.graalvm.buildtools.maven.NativeCompileNoForkMojo;
5353

5454
import java.io.IOException;
5555
import java.nio.file.Files;
@@ -63,9 +63,30 @@
6363
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
6464
import static org.twdata.maven.mojoexecutor.MojoExecutor.*;
6565

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

99+
private static final class AugmentedFields {
100+
static final String packageNames = "packageNames";
101+
static final String jarPath = "jarPath";
102+
static final String prunable = "prunable";
103+
}
104+
78105
public SBOMGenerator(MavenProject mavenProject, MavenSession mavenSession, BuildPluginManager pluginManager, RepositorySystem repositorySystem, String mainClass) {
79106
this.mavenProject = mavenProject;
80107
this.mavenSession = mavenSession;
@@ -83,9 +110,13 @@ public SBOMGenerator(MavenProject mavenProject, MavenSession mavenSession, Build
83110
this.mainClass = mainClass;
84111
}
85112

113+
/**
114+
* Generates an SBOM that will be further augmented by Native Image. The SBOM is stored in the build directory.
115+
*
116+
* @throws MojoExecutionException if SBOM creation fails.
117+
*/
86118
public void generate() throws MojoExecutionException {
87119
try {
88-
System.out.println("💣 Generating SBOM...");
89120
String outputDirectory = mavenProject.getBuild().getDirectory();
90121
executeMojo(
91122
plugin(
@@ -104,49 +135,45 @@ public void generate() throws MojoExecutionException {
104135
);
105136

106137
Path sbomPath = Paths.get(outputDirectory, SBOM_NAME + "." + FILE_FORMAT);
138+
// TODO: remove
139+
Files.copy(sbomPath, Paths.get(outputDirectory, "SBOM_UNMODIFIED.json"), REPLACE_EXISTING);
107140
if (!Files.exists(sbomPath)) {
108-
// TODO: warning instead?
109-
throw new MojoExecutionException("SBOM file not found at " + sbomPath);
141+
return;
110142
}
111-
// TODO: debugging only
112-
Files.copy(sbomPath, Paths.get(outputDirectory, "SBOM_UNMODIFIED" + "." + FILE_FORMAT), REPLACE_EXISTING);
113-
System.out.println("✅ CycloneDX SBOM generated successfully: " + sbomPath);
114143

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

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

127158
ArrayNode componentsArray = (ArrayNode) sbomJson.get("components");
128159
if (componentsArray == null) {
129-
System.out.println("⚠️ No components found in the SBOM.");
130160
return;
131161
}
132162

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

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

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

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

174201
String jarPath = "";
175202
if (artifact.jarPath != null) {
176203
jarPath = artifact.jarPath.toString();
177-
} else {
178-
System.out.printf("⚠️ jarPath is null for %s.%s\n", artifact.groupId, artifact.artifactId);
179204
}
180-
((ObjectNode) componentNode).put("jarPath", jarPath);
181-
((ObjectNode) componentNode).put("prunable", artifact.prunable);
182-
183-
System.out.printf("Added package names and jarPath %s to component %s:%s:%s\n", jarPath, groupId, artifactId, version);
184-
} else {
185-
System.out.printf("⚠️ No matching artifact found for %s:%s:%s\n", groupId, artifactId, version);
205+
((ObjectNode) componentNode).put(AugmentedFields.jarPath, jarPath);
206+
((ObjectNode) componentNode).put(AugmentedFields.prunable, artifact.prunable);
186207
}
187-
} else {
188-
System.out.printf("⚠️ component node missing group or name or version info: %s\n", componentNode);
189208
}
190209
}
191210
}

0 commit comments

Comments
 (0)