Skip to content

Commit 8bc91fe

Browse files
committed
Add SBOM integration tests
1 parent da3952a commit 8bc91fe

File tree

7 files changed

+241
-32
lines changed

7 files changed

+241
-32
lines changed
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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+
*/
41+
42+
package org.graalvm.buildtools.maven
43+
44+
import com.fasterxml.jackson.databind.node.ObjectNode
45+
import org.graalvm.buildtools.maven.sbom.SBOMGenerator
46+
import org.graalvm.buildtools.utils.NativeImageUtils
47+
import spock.lang.Requires
48+
import com.fasterxml.jackson.databind.ObjectMapper
49+
50+
class SBOMFunctionalTest extends AbstractGraalVMMavenFunctionalTest {
51+
private static boolean EE() {
52+
NativeCompileNoForkMojo.isOracleGraalVM(null)
53+
}
54+
55+
private static boolean CE() {
56+
!EE()
57+
}
58+
59+
private static boolean supportedJDKVersion() {
60+
NativeImageUtils.getMajorJDKVersion(NativeCompileNoForkMojo.getVersionInformation(null)) >= SBOMGenerator.requiredNativeImageVersion
61+
}
62+
63+
private static boolean unsupportedJDKVersion() {
64+
!supportedJDKVersion()
65+
}
66+
67+
private static boolean supportedAugmentedSBOMVersion() {
68+
EE() && supportedJDKVersion()
69+
}
70+
71+
@Requires({ EE() })
72+
def "sbom is created when buildArg '--enable-sbom=export,embed' is used"() {
73+
withSample 'java-application'
74+
75+
when:
76+
/* The 'native-sbom' profile sets the '--enable-sbom' argument. */
77+
mvn '-Pnative-sbom', '-DquickBuild', '-DskipTests', 'package', 'exec:exec@native'
78+
79+
def sbom = file("target/example-app.sbom.json")
80+
81+
then:
82+
buildSucceeded
83+
outputContainsPattern".*CycloneDX SBOM with \\d+ component\\(s\\) is embedded in binary \\(.*?\\) and exported as JSON \\(see build artifacts\\)\\."
84+
outputDoesNotContain "Use '--enable-sbom' to assemble a Software Bill of Materials (SBOM)"
85+
validateSbom sbom
86+
!file(String.format("target/%s", SBOMGenerator.SBOM_FILENAME)).exists()
87+
outputContains "Hello, native!"
88+
}
89+
90+
/**
91+
* If user sets {@link NativeCompileNoForkMojo#AUGMENTED_SBOM_PARAM_NAME} to true then an SBOM should be generated
92+
* with default SBOM arguments even if user did not explicitly specify '--enable-sbom' as a buildArg.
93+
*/
94+
@Requires({ supportedAugmentedSBOMVersion() })
95+
def "sbom is created when only the augmented sbom parameter is used (but not the '--enable-sbom' buildArg)"() {
96+
withSample 'java-application'
97+
98+
when:
99+
mvn '-Pnative-augmentedSBOM-only', '-DquickBuild', '-DskipTests', 'package', 'exec:exec@native'
100+
101+
def sbom = file("target/example-app.sbom.json")
102+
103+
then:
104+
buildSucceeded
105+
outputContainsPattern".*CycloneDX SBOM with \\d+ component\\(s\\) is embedded in binary \\(.*?\\)."
106+
outputDoesNotContain "Use '--enable-sbom' to assemble a Software Bill of Materials (SBOM)"
107+
validateSbom sbom
108+
!file(String.format("target/%s", SBOMGenerator.SBOM_FILENAME)).exists()
109+
outputContains "Hello, native!"
110+
}
111+
112+
@Requires({ CE() })
113+
def "error is thrown when augmented sbom parameter is used with CE"() {
114+
withSample 'java-application'
115+
116+
when:
117+
mvn '-Pnative-augmentedSBOM-only', '-DquickBuild', '-DskipTests', 'package'
118+
119+
then:
120+
buildFailed
121+
outputContains "IllegalArgumentException"
122+
}
123+
124+
@Requires({ EE() && unsupportedJDKVersion() })
125+
def "error is thrown when augmented sbom parameter is used with EE but not with an unsupported JDK version"() {
126+
withSample 'java-application'
127+
128+
when:
129+
mvn '-Pnative-augmentedSBOM-only', '-DquickBuild', '-DskipTests', 'package'
130+
131+
then:
132+
buildFailed
133+
outputContains "IllegalArgumentException"
134+
}
135+
136+
private static boolean validateSbom(File sbom) {
137+
try {
138+
if (!sbom.exists()) {
139+
println "SBOM not found: ${sbom}"
140+
return false
141+
}
142+
143+
def mapper = new ObjectMapper()
144+
def rootNode = mapper.readTree(sbom)
145+
146+
// Check root fields
147+
assert rootNode.has('bomFormat')
148+
assert rootNode.get('bomFormat').asText() == 'CycloneDX'
149+
assert rootNode.has('specVersion')
150+
assert rootNode.has('serialNumber')
151+
assert rootNode.has('version')
152+
assert rootNode.has('metadata')
153+
assert rootNode.has('components')
154+
assert rootNode.has('dependencies')
155+
156+
// Check metadata/component
157+
def metadataComponent = rootNode.path('metadata').path('component')
158+
assert metadataComponent.has('group')
159+
assert metadataComponent.get('group').asText() == 'org.graalvm.buildtools.examples'
160+
assert metadataComponent.has('name')
161+
assert metadataComponent.get('name').asText() == 'maven'
162+
163+
// Check that components and dependencies are non-empty
164+
assert !rootNode.get('components').isEmpty()
165+
assert !rootNode.get('dependencies').isEmpty()
166+
167+
// Check that the main component has no dependencies
168+
def mainComponentId = metadataComponent.get('bom-ref').asText()
169+
def mainComponentDependency = rootNode.get('dependencies').find { it.get('ref').asText() == mainComponentId } as ObjectNode
170+
assert mainComponentDependency.get('dependsOn').isEmpty()
171+
172+
// Check that the main component is not found in "components"
173+
assert !rootNode.get('components').any { it.get('bom-ref').asText() == mainComponentId }
174+
175+
return true
176+
} catch (AssertionError | Exception e) {
177+
println "SBOM validation failed: ${e.message}"
178+
return false
179+
}
180+
}
181+
}

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

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -49,33 +49,19 @@
4949
import org.apache.maven.plugins.annotations.Component;
5050
import org.apache.maven.plugins.annotations.Parameter;
5151
import org.apache.maven.toolchain.ToolchainManager;
52+
import org.codehaus.plexus.logging.Logger;
5253
import org.graalvm.buildtools.maven.config.ExcludeConfigConfiguration;
5354
import org.graalvm.buildtools.utils.NativeImageConfigurationUtils;
5455
import org.graalvm.buildtools.utils.NativeImageUtils;
5556
import org.graalvm.buildtools.utils.SharedConstants;
5657

5758
import javax.inject.Inject;
58-
import java.io.File;
59-
import java.io.InputStream;
60-
import java.io.BufferedReader;
61-
import java.io.InputStreamReader;
62-
import java.io.IOException;
59+
import java.io.*;
6360
import java.net.URI;
6461
import java.nio.charset.StandardCharsets;
6562
import java.nio.file.FileSystem;
66-
import java.nio.file.FileSystems;
67-
import java.nio.file.FileSystemAlreadyExistsException;
68-
import java.nio.file.Path;
69-
import java.nio.file.Paths;
70-
import java.nio.file.Files;
71-
import java.util.ArrayList;
72-
import java.util.Arrays;
73-
import java.util.HashSet;
74-
import java.util.List;
75-
import java.util.Collections;
76-
import java.util.Map;
77-
import java.util.Optional;
78-
import java.util.Set;
63+
import java.nio.file.*;
64+
import java.util.*;
7965
import java.util.regex.Pattern;
8066
import java.util.stream.Collectors;
8167
import java.util.stream.Stream;
@@ -89,7 +75,7 @@ public abstract class AbstractNativeImageMojo extends AbstractNativeMojo {
8975
protected static final String NATIVE_IMAGE_META_INF = "META-INF/native-image";
9076
protected static final String NATIVE_IMAGE_PROPERTIES_FILENAME = "native-image.properties";
9177
protected static final String NATIVE_IMAGE_DRY_RUN = "nativeDryRun";
92-
private String nativeImageVersionInformation = null;
78+
private static String nativeImageVersionInformation = null;
9379

9480
@Parameter(defaultValue = "${plugin}", readonly = true) // Maven 3 only
9581
protected PluginDescriptor plugin;
@@ -440,19 +426,19 @@ protected void checkRequiredVersionIfNeeded() throws MojoExecutionException {
440426
if (requiredVersion == null) {
441427
return;
442428
}
443-
NativeImageUtils.checkVersion(requiredVersion, getVersionInformation());
429+
NativeImageUtils.checkVersion(requiredVersion, getVersionInformation(logger));
444430
}
445431

446-
protected boolean isOracleGraalVM() throws MojoExecutionException {
447-
return getVersionInformation().contains(ORACLE_GRAALVM_IDENTIFIER);
432+
static protected boolean isOracleGraalVM(Logger logger) throws MojoExecutionException {
433+
return getVersionInformation(logger).contains(ORACLE_GRAALVM_IDENTIFIER);
448434
}
449435

450436
/**
451437
* Returns the output of calling "native-image --version".
452438
* @return the output as a string joined by "\n".
453439
* @throws MojoExecutionException when any errors occurred.
454440
*/
455-
protected String getVersionInformation() throws MojoExecutionException {
441+
static protected String getVersionInformation(Logger logger) throws MojoExecutionException {
456442
if (nativeImageVersionInformation != null) {
457443
return nativeImageVersionInformation;
458444
}

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,6 @@ public class NativeCompileNoForkMojo extends AbstractNativeImageMojo {
8585
private Boolean augmentedSBOM;
8686
public static final String AUGMENTED_SBOM_PARAM_NAME = "augmentedSBOM";
8787

88-
@Parameter
89-
private Boolean myBooleanOption;
90-
9188
private PluginParameterExpressionEvaluator evaluator;
9289

9390
@Override
@@ -141,7 +138,7 @@ private void generateAugmentedSBOMIfNeeded() throws IllegalArgumentException, Mo
141138
boolean optionWasSet = augmentedSBOM != null;
142139
augmentedSBOM = optionWasSet ? augmentedSBOM : true;
143140

144-
int detectedJDKVersion = NativeImageUtils.getMajorJDKVersion(getVersionInformation());
141+
int detectedJDKVersion = NativeImageUtils.getMajorJDKVersion(getVersionInformation(logger));
145142
String sbomNativeImageFlag = "--enable-sbom";
146143
boolean sbomEnabledForNativeImage = getBuildArgs().stream().anyMatch(v -> v.contains(sbomNativeImageFlag));
147144
if (optionWasSet) {
@@ -150,7 +147,7 @@ private void generateAugmentedSBOMIfNeeded() throws IllegalArgumentException, Mo
150147
return;
151148
}
152149

153-
if (!isOracleGraalVM()) {
150+
if (!isOracleGraalVM(logger)) {
154151
throw new IllegalArgumentException(
155152
String.format("Configuration option %s is only supported in %s.", AUGMENTED_SBOM_PARAM_NAME, ORACLE_GRAALVM_IDENTIFIER));
156153
}
@@ -165,7 +162,7 @@ private void generateAugmentedSBOMIfNeeded() throws IllegalArgumentException, Mo
165162

166163
/* Continue to generate augmented SBOM because parameter option explicitly set and all conditions are met. */
167164
} else {
168-
if (!isOracleGraalVM() || !sbomEnabledForNativeImage) {
165+
if (!isOracleGraalVM(logger) || !sbomEnabledForNativeImage) {
169166
return;
170167
}
171168

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,10 @@ final public class SBOMGenerator {
9595

9696
private static final String SBOM_FILE_FORMAT = "json";
9797
private static final String SBOM_FILENAME_WITHOUT_EXTENSION = "base_sbom";
98-
private static final String SBOM_FILENAME = SBOM_FILENAME_WITHOUT_EXTENSION + "." + SBOM_FILE_FORMAT;
9998
private final String outputDirectory;
10099

100+
public static final String SBOM_FILENAME = SBOM_FILENAME_WITHOUT_EXTENSION + "." + SBOM_FILE_FORMAT;
101+
101102
private static final class AddedComponentFields {
102103
/**
103104
* The package names associated with this component.

native-maven-plugin/src/main/java/org/graalvm/buildtools/utils/NativeImageConfigurationUtils.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@ public static Path getJavaHomeNativeImage(String javaHomeVariable, Boolean failF
9797
return null;
9898
}
9999
}
100-
logger.info("Found GraalVM installation from " + javaHomeVariable + " variable.");
100+
if (logger != null) {
101+
logger.info("Found GraalVM installation from " + javaHomeVariable + " variable.");
102+
}
101103
return nativeImageExe;
102104
}
103105

@@ -122,7 +124,7 @@ public static Path getNativeImage(Logger logger) throws MojoExecutionException {
122124

123125
if (nativeImage == null) {
124126
nativeImage = getNativeImageFromPath();
125-
if (nativeImage != null) {
127+
if (nativeImage != null && logger != null) {
126128
logger.info("Found GraalVM installation from PATH variable.");
127129
}
128130
}

native-maven-plugin/src/testFixtures/groovy/org/graalvm/buildtools/maven/AbstractGraalVMMavenFunctionalTest.groovy

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,12 @@ abstract class AbstractGraalVMMavenFunctionalTest extends Specification {
213213
normalizeString(result.stdOut).contains(normalizeString(text))
214214
}
215215

216+
boolean outputContainsPattern(String pattern) {
217+
def normalizedOutput = normalizeString(result.stdOut)
218+
def lines = normalizedOutput.split('\n')
219+
return lines.any { line -> line.trim().matches(pattern) }
220+
}
221+
216222
String after(String text) {
217223
def out = normalizeString(result.stdOut)
218224
out.substring(out.indexOf(normalizeString(text)))

samples/java-application/pom.xml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
<junit.platform.native.version>0.10.3-SNAPSHOT</junit.platform.native.version>
5656
<imageName>example-app</imageName>
5757
<mainClass>org.graalvm.demo.Application</mainClass>
58+
<!-- Arbitrary placeholder that can be overridden to provide native image with an argument -->
59+
<native.build.arg>--color=auto</native.build.arg>
5860
</properties>
5961

6062
<dependencies>
@@ -68,6 +70,15 @@
6870
<profiles>
6971
<profile>
7072
<id>native</id>
73+
</profile>
74+
<profile>
75+
<id>native-sbom</id>
76+
<properties>
77+
<native.build.arg>--enable-sbom=embed,export</native.build.arg>
78+
</properties>
79+
</profile>
80+
<profile>
81+
<id>native-augmentedSBOM-only</id>
7182
<build>
7283
<plugins>
7384
<plugin>
@@ -88,6 +99,7 @@
8899
<skip>false</skip>
89100
<imageName>${imageName}</imageName>
90101
<fallback>false</fallback>
102+
<augmentedSBOM>true</augmentedSBOM>
91103
</configuration>
92104
</plugin>
93105
</plugins>
@@ -150,6 +162,30 @@
150162
<build>
151163
<finalName>${project.artifactId}</finalName>
152164
<plugins>
165+
<plugin>
166+
<groupId>org.graalvm.buildtools</groupId>
167+
<artifactId>native-maven-plugin</artifactId>
168+
<version>${native.maven.plugin.version}</version>
169+
<extensions>true</extensions>
170+
<executions>
171+
<execution>
172+
<id>build-native</id>
173+
<goals>
174+
<goal>compile-no-fork</goal>
175+
</goals>
176+
<phase>package</phase>
177+
</execution>
178+
</executions>
179+
<configuration>
180+
<skip>false</skip>
181+
<imageName>${imageName}</imageName>
182+
<fallback>false</fallback>
183+
<buildArgs>
184+
<buildArg>${native.build.arg}</buildArg>
185+
</buildArgs>
186+
</configuration>
187+
</plugin>
188+
153189
<plugin>
154190
<groupId>org.apache.maven.plugins</groupId>
155191
<artifactId>maven-surefire-plugin</artifactId>

0 commit comments

Comments
 (0)