diff --git a/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/SBOMFunctionalTest.groovy b/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/SBOMFunctionalTest.groovy
new file mode 100644
index 000000000..96e2c8a4c
--- /dev/null
+++ b/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/SBOMFunctionalTest.groovy
@@ -0,0 +1,181 @@
+/*
+ * 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
+
+import com.fasterxml.jackson.databind.node.ObjectNode
+import org.graalvm.buildtools.maven.sbom.SBOMGenerator
+import org.graalvm.buildtools.utils.NativeImageUtils
+import spock.lang.Requires
+import com.fasterxml.jackson.databind.ObjectMapper
+
+class SBOMFunctionalTest extends AbstractGraalVMMavenFunctionalTest {
+ private static boolean EE() {
+ NativeCompileNoForkMojo.isOracleGraalVM(null)
+ }
+
+ private static boolean CE() {
+ !EE()
+ }
+
+ private static boolean supportedJDKVersion() {
+ NativeImageUtils.getMajorJDKVersion(NativeCompileNoForkMojo.getVersionInformation(null)) >= SBOMGenerator.requiredNativeImageVersion
+ }
+
+ private static boolean unsupportedJDKVersion() {
+ !supportedJDKVersion()
+ }
+
+ private static boolean supportedAugmentedSBOMVersion() {
+ EE() && supportedJDKVersion()
+ }
+
+ @Requires({ EE() })
+ def "sbom is created when buildArg '--enable-sbom=export,embed' is used"() {
+ withSample 'java-application'
+
+ when:
+ /* The 'native-sbom' profile sets the '--enable-sbom' argument. */
+ mvn '-Pnative-sbom', '-DquickBuild', '-DskipTests', 'package', 'exec:exec@native'
+
+ def sbom = file("target/example-app.sbom.json")
+
+ then:
+ buildSucceeded
+ outputContainsPattern".*CycloneDX SBOM with \\d+ component\\(s\\) is embedded in binary \\(.*?\\) and exported as JSON \\(see build artifacts\\)\\."
+ outputDoesNotContain "Use '--enable-sbom' to assemble a Software Bill of Materials (SBOM)"
+ validateSbom sbom
+ !file(String.format("target/%s", SBOMGenerator.SBOM_FILENAME)).exists()
+ outputContains "Hello, native!"
+ }
+
+ /**
+ * If user sets {@link NativeCompileNoForkMojo#AUGMENTED_SBOM_PARAM_NAME} to true then an SBOM should be generated
+ * with default SBOM arguments even if user did not explicitly specify '--enable-sbom' as a buildArg.
+ */
+ @Requires({ supportedAugmentedSBOMVersion() })
+ def "sbom is created when only the augmented sbom parameter is used (but not the '--enable-sbom' buildArg)"() {
+ withSample 'java-application'
+
+ when:
+ mvn '-Pnative-augmentedSBOM-only', '-DquickBuild', '-DskipTests', 'package', 'exec:exec@native'
+
+ def sbom = file("target/example-app.sbom.json")
+
+ then:
+ buildSucceeded
+ outputContainsPattern".*CycloneDX SBOM with \\d+ component\\(s\\) is embedded in binary \\(.*?\\)."
+ outputDoesNotContain "Use '--enable-sbom' to assemble a Software Bill of Materials (SBOM)"
+ validateSbom sbom
+ !file(String.format("target/%s", SBOMGenerator.SBOM_FILENAME)).exists()
+ outputContains "Hello, native!"
+ }
+
+ @Requires({ CE() })
+ def "error is thrown when augmented sbom parameter is used with CE"() {
+ withSample 'java-application'
+
+ when:
+ mvn '-Pnative-augmentedSBOM-only', '-DquickBuild', '-DskipTests', 'package'
+
+ then:
+ buildFailed
+ outputContains "IllegalArgumentException"
+ }
+
+ @Requires({ EE() && unsupportedJDKVersion() })
+ def "error is thrown when augmented sbom parameter is used with EE but not with an unsupported JDK version"() {
+ withSample 'java-application'
+
+ when:
+ mvn '-Pnative-augmentedSBOM-only', '-DquickBuild', '-DskipTests', 'package'
+
+ then:
+ buildFailed
+ outputContains "IllegalArgumentException"
+ }
+
+ private static boolean validateSbom(File sbom) {
+ try {
+ if (!sbom.exists()) {
+ println "SBOM not found: ${sbom}"
+ return false
+ }
+
+ def mapper = new ObjectMapper()
+ def rootNode = mapper.readTree(sbom)
+
+ // Check root fields
+ assert rootNode.has('bomFormat')
+ assert rootNode.get('bomFormat').asText() == 'CycloneDX'
+ assert rootNode.has('specVersion')
+ assert rootNode.has('serialNumber')
+ assert rootNode.has('version')
+ assert rootNode.has('metadata')
+ assert rootNode.has('components')
+ assert rootNode.has('dependencies')
+
+ // Check metadata/component
+ def metadataComponent = rootNode.path('metadata').path('component')
+ assert metadataComponent.has('group')
+ assert metadataComponent.get('group').asText() == 'org.graalvm.buildtools.examples'
+ assert metadataComponent.has('name')
+ assert metadataComponent.get('name').asText() == 'maven'
+
+ // Check that components and dependencies are non-empty
+ assert !rootNode.get('components').isEmpty()
+ assert !rootNode.get('dependencies').isEmpty()
+
+ // Check that the main component has no dependencies
+ def mainComponentId = metadataComponent.get('bom-ref').asText()
+ def mainComponentDependency = rootNode.get('dependencies').find { it.get('ref').asText() == mainComponentId } as ObjectNode
+ assert mainComponentDependency.get('dependsOn').isEmpty()
+
+ // Check that the main component is not found in "components"
+ assert !rootNode.get('components').any { it.get('bom-ref').asText() == mainComponentId }
+
+ return true
+ } catch (AssertionError | Exception e) {
+ println "SBOM validation failed: ${e.message}"
+ return false
+ }
+ }
+}
diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java
index 2ac82d388..b94312175 100644
--- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java
+++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java
@@ -49,33 +49,19 @@
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.toolchain.ToolchainManager;
+import org.codehaus.plexus.logging.Logger;
import org.graalvm.buildtools.maven.config.ExcludeConfigConfiguration;
import org.graalvm.buildtools.utils.NativeImageConfigurationUtils;
import org.graalvm.buildtools.utils.NativeImageUtils;
import org.graalvm.buildtools.utils.SharedConstants;
import javax.inject.Inject;
-import java.io.File;
-import java.io.InputStream;
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.io.IOException;
+import java.io.*;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
-import java.nio.file.FileSystemAlreadyExistsException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.Files;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
+import java.nio.file.*;
+import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -89,7 +75,7 @@ public abstract class AbstractNativeImageMojo extends AbstractNativeMojo {
protected static final String NATIVE_IMAGE_META_INF = "META-INF/native-image";
protected static final String NATIVE_IMAGE_PROPERTIES_FILENAME = "native-image.properties";
protected static final String NATIVE_IMAGE_DRY_RUN = "nativeDryRun";
- private String nativeImageVersionInformation = null;
+ private static String nativeImageVersionInformation = null;
@Parameter(defaultValue = "${plugin}", readonly = true) // Maven 3 only
protected PluginDescriptor plugin;
@@ -440,11 +426,11 @@ protected void checkRequiredVersionIfNeeded() throws MojoExecutionException {
if (requiredVersion == null) {
return;
}
- NativeImageUtils.checkVersion(requiredVersion, getVersionInformation());
+ NativeImageUtils.checkVersion(requiredVersion, getVersionInformation(logger));
}
- protected boolean isOracleGraalVM() throws MojoExecutionException {
- return getVersionInformation().contains(ORACLE_GRAALVM_IDENTIFIER);
+ static protected boolean isOracleGraalVM(Logger logger) throws MojoExecutionException {
+ return getVersionInformation(logger).contains(ORACLE_GRAALVM_IDENTIFIER);
}
/**
@@ -452,7 +438,7 @@ protected boolean isOracleGraalVM() throws MojoExecutionException {
* @return the output as a string joined by "\n".
* @throws MojoExecutionException when any errors occurred.
*/
- protected String getVersionInformation() throws MojoExecutionException {
+ static protected String getVersionInformation(Logger logger) throws MojoExecutionException {
if (nativeImageVersionInformation != null) {
return nativeImageVersionInformation;
}
diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java
index e988084f6..d91117345 100644
--- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java
+++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java
@@ -85,9 +85,6 @@ public class NativeCompileNoForkMojo extends AbstractNativeImageMojo {
private Boolean augmentedSBOM;
public static final String AUGMENTED_SBOM_PARAM_NAME = "augmentedSBOM";
- @Parameter
- private Boolean myBooleanOption;
-
private PluginParameterExpressionEvaluator evaluator;
@Override
@@ -141,7 +138,7 @@ private void generateAugmentedSBOMIfNeeded() throws IllegalArgumentException, Mo
boolean optionWasSet = augmentedSBOM != null;
augmentedSBOM = optionWasSet ? augmentedSBOM : true;
- int detectedJDKVersion = NativeImageUtils.getMajorJDKVersion(getVersionInformation());
+ int detectedJDKVersion = NativeImageUtils.getMajorJDKVersion(getVersionInformation(logger));
String sbomNativeImageFlag = "--enable-sbom";
boolean sbomEnabledForNativeImage = getBuildArgs().stream().anyMatch(v -> v.contains(sbomNativeImageFlag));
if (optionWasSet) {
@@ -150,7 +147,7 @@ private void generateAugmentedSBOMIfNeeded() throws IllegalArgumentException, Mo
return;
}
- if (!isOracleGraalVM()) {
+ if (!isOracleGraalVM(logger)) {
throw new IllegalArgumentException(
String.format("Configuration option %s is only supported in %s.", AUGMENTED_SBOM_PARAM_NAME, ORACLE_GRAALVM_IDENTIFIER));
}
@@ -165,7 +162,7 @@ private void generateAugmentedSBOMIfNeeded() throws IllegalArgumentException, Mo
/* Continue to generate augmented SBOM because parameter option explicitly set and all conditions are met. */
} else {
- if (!isOracleGraalVM() || !sbomEnabledForNativeImage) {
+ if (!isOracleGraalVM(logger) || !sbomEnabledForNativeImage) {
return;
}
diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/sbom/SBOMGenerator.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/sbom/SBOMGenerator.java
index 0e1178875..28ad8cb81 100644
--- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/sbom/SBOMGenerator.java
+++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/sbom/SBOMGenerator.java
@@ -95,9 +95,10 @@ final public class SBOMGenerator {
private static final String SBOM_FILE_FORMAT = "json";
private static final String SBOM_FILENAME_WITHOUT_EXTENSION = "base_sbom";
- private static final String SBOM_FILENAME = SBOM_FILENAME_WITHOUT_EXTENSION + "." + SBOM_FILE_FORMAT;
private final String outputDirectory;
+ public static final String SBOM_FILENAME = SBOM_FILENAME_WITHOUT_EXTENSION + "." + SBOM_FILE_FORMAT;
+
private static final class AddedComponentFields {
/**
* The package names associated with this component.
diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/utils/NativeImageConfigurationUtils.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/utils/NativeImageConfigurationUtils.java
index e3401033b..dbe3cc53b 100644
--- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/utils/NativeImageConfigurationUtils.java
+++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/utils/NativeImageConfigurationUtils.java
@@ -97,7 +97,9 @@ public static Path getJavaHomeNativeImage(String javaHomeVariable, Boolean failF
return null;
}
}
- logger.info("Found GraalVM installation from " + javaHomeVariable + " variable.");
+ if (logger != null) {
+ logger.info("Found GraalVM installation from " + javaHomeVariable + " variable.");
+ }
return nativeImageExe;
}
@@ -122,7 +124,7 @@ public static Path getNativeImage(Logger logger) throws MojoExecutionException {
if (nativeImage == null) {
nativeImage = getNativeImageFromPath();
- if (nativeImage != null) {
+ if (nativeImage != null && logger != null) {
logger.info("Found GraalVM installation from PATH variable.");
}
}
diff --git a/native-maven-plugin/src/testFixtures/groovy/org/graalvm/buildtools/maven/AbstractGraalVMMavenFunctionalTest.groovy b/native-maven-plugin/src/testFixtures/groovy/org/graalvm/buildtools/maven/AbstractGraalVMMavenFunctionalTest.groovy
index 00c593bc6..f4f572ed2 100644
--- a/native-maven-plugin/src/testFixtures/groovy/org/graalvm/buildtools/maven/AbstractGraalVMMavenFunctionalTest.groovy
+++ b/native-maven-plugin/src/testFixtures/groovy/org/graalvm/buildtools/maven/AbstractGraalVMMavenFunctionalTest.groovy
@@ -213,6 +213,12 @@ abstract class AbstractGraalVMMavenFunctionalTest extends Specification {
normalizeString(result.stdOut).contains(normalizeString(text))
}
+ boolean outputContainsPattern(String pattern) {
+ def normalizedOutput = normalizeString(result.stdOut)
+ def lines = normalizedOutput.split('\n')
+ return lines.any { line -> line.trim().matches(pattern) }
+ }
+
String after(String text) {
def out = normalizeString(result.stdOut)
out.substring(out.indexOf(normalizeString(text)))
diff --git a/samples/java-application/pom.xml b/samples/java-application/pom.xml
index 9fda870d9..db0ff12bf 100644
--- a/samples/java-application/pom.xml
+++ b/samples/java-application/pom.xml
@@ -55,6 +55,8 @@
0.10.3-SNAPSHOT
example-app
org.graalvm.demo.Application
+
+ --color=auto
@@ -68,6 +70,15 @@
native
+
+
+ native-sbom
+
+ --enable-sbom=embed,export
+
+
+
+ native-augmentedSBOM-only
@@ -88,6 +99,7 @@
false
${imageName}
false
+ true
@@ -150,6 +162,30 @@
${project.artifactId}
+
+ org.graalvm.buildtools
+ native-maven-plugin
+ ${native.maven.plugin.version}
+ true
+
+
+ build-native
+
+ compile-no-fork
+
+ package
+
+
+
+ false
+ ${imageName}
+ false
+
+ ${native.build.arg}
+
+
+
+
org.apache.maven.plugins
maven-surefire-plugin