diff --git a/.github/workflows/deploy-documentation.yml b/.github/workflows/deploy-documentation.yml index 50d7ee871..1943d3024 100644 --- a/.github/workflows/deploy-documentation.yml +++ b/.github/workflows/deploy-documentation.yml @@ -22,7 +22,7 @@ jobs: os: [ ubuntu-20.04 ] steps: - name: "☁ Checkout repository" - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: ssh-key: "${{ secrets.SSH_PRIVATE_KEY }}" - name: "🔧 Prepare environment" diff --git a/.github/workflows/deploy-snapshots.yml b/.github/workflows/deploy-snapshots.yml index 832b4d912..c80f78384 100644 --- a/.github/workflows/deploy-snapshots.yml +++ b/.github/workflows/deploy-snapshots.yml @@ -24,7 +24,7 @@ jobs: os: [ ubuntu-22.04 ] steps: - name: "☁ Checkout repository" - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: ssh-key: "${{ secrets.SSH_PRIVATE_KEY }}" - name: "🔧 Prepare environment" diff --git a/.github/workflows/test-graalvm-metadata.yml b/.github/workflows/test-graalvm-metadata.yml index 1684f7d57..05a9a3f49 100644 --- a/.github/workflows/test-graalvm-metadata.yml +++ b/.github/workflows/test-graalvm-metadata.yml @@ -28,7 +28,7 @@ jobs: os: [ ubuntu-20.04 ] steps: - name: "☁️ Checkout repository" - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: "🔧 Prepare environment" uses: ./.github/actions/prepare-environment with: diff --git a/.github/workflows/test-junit-platform-native.yml b/.github/workflows/test-junit-platform-native.yml index 765017d3b..ea98e338b 100644 --- a/.github/workflows/test-junit-platform-native.yml +++ b/.github/workflows/test-junit-platform-native.yml @@ -28,7 +28,7 @@ jobs: os: [ ubuntu-20.04 ] steps: - name: "☁️ Checkout repository" - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: "🔧 Prepare environment" uses: ./.github/actions/prepare-environment with: diff --git a/.github/workflows/test-native-gradle-plugin.yml b/.github/workflows/test-native-gradle-plugin.yml index 4f0191b10..cfbce0079 100644 --- a/.github/workflows/test-native-gradle-plugin.yml +++ b/.github/workflows/test-native-gradle-plugin.yml @@ -32,7 +32,7 @@ jobs: os: [ ubuntu-20.04 ] steps: - name: "☁️ Checkout repository" - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: "🔧 Prepare environment" uses: ./.github/actions/prepare-environment with: @@ -59,7 +59,7 @@ jobs: os: [ ubuntu-20.04 ] steps: - name: "☁️ Checkout repository" - uses: actions/checkout@v2 + uses: actions/checkout@v3 - uses: ./.github/actions/prepare-environment with: java-version: ${{ matrix.java-version }} @@ -86,7 +86,7 @@ jobs: os: [ windows-latest ] steps: - name: "☁️ Checkout repository" - uses: actions/checkout@v2 + uses: actions/checkout@v3 - uses: ./.github/actions/prepare-environment with: java-version: ${{ matrix.java-version }} diff --git a/.github/workflows/test-native-maven-plugin.yml b/.github/workflows/test-native-maven-plugin.yml index 04ab2038d..e8953121d 100644 --- a/.github/workflows/test-native-maven-plugin.yml +++ b/.github/workflows/test-native-maven-plugin.yml @@ -32,7 +32,7 @@ jobs: os: [ ubuntu-20.04 ] steps: - name: "☁️ Checkout repository" - uses: actions/checkout@v2 + uses: actions/checkout@v3 - uses: ./.github/actions/prepare-environment with: java-version: ${{ matrix.java-version }} @@ -57,7 +57,7 @@ jobs: os: [ windows-latest ] steps: - name: "☁️ Checkout repository" - uses: actions/checkout@v2 + uses: actions/checkout@v3 - uses: ./.github/actions/prepare-environment with: java-version: ${{ matrix.java-version }} diff --git a/common/graalvm-reachability-metadata/gradle/native-image-testing.gradle b/common/graalvm-reachability-metadata/gradle/native-image-testing.gradle index 80afb10b8..40645cbd1 100644 --- a/common/graalvm-reachability-metadata/gradle/native-image-testing.gradle +++ b/common/graalvm-reachability-metadata/gradle/native-image-testing.gradle @@ -98,8 +98,7 @@ abstract class NativeTestArgumentProvider implements CommandLineArgumentProvider "-cp", classpath.asPath, "--no-fallback", "--features=org.graalvm.junit.platform.JUnitPlatformFeature", - "-H:Name=native-image-tests", - "-H:Class=org.graalvm.junit.platform.NativeImageJUnitLauncher", + "-o", "native-image-tests", "-Djunit.platform.listeners.uid.tracking.output.dir=${testIdsDir.get().asFile.absolutePath}" ] if (agentOutputDir.isPresent()) { @@ -110,12 +109,15 @@ abstract class NativeTestArgumentProvider implements CommandLineArgumentProvider } args << "-H:ConfigurationFileDirectories=${outputDir.absolutePath}/agentOutput" - args << "-H:+AllowIncompleteClasspath" } if (discovery.get()) { args << "-DtestDiscovery" } + + // Main class comes last + args << "org.graalvm.junit.platform.NativeImageJUnitLauncher" + args.collect { it.toString() } } } diff --git a/common/junit-platform-native/gradle/native-image-testing.gradle b/common/junit-platform-native/gradle/native-image-testing.gradle index 6a0637ab6..912f88b7c 100644 --- a/common/junit-platform-native/gradle/native-image-testing.gradle +++ b/common/junit-platform-native/gradle/native-image-testing.gradle @@ -96,8 +96,7 @@ abstract class NativeTestArgumentProvider implements CommandLineArgumentProvider "-cp", classpath.asPath, "--no-fallback", "--features=org.graalvm.junit.platform.JUnitPlatformFeature", - "-H:Name=native-image-tests", - "-H:Class=org.graalvm.junit.platform.NativeImageJUnitLauncher", + "-o", "native-image-tests", "-Djunit.platform.listeners.uid.tracking.output.dir=${testIdsDir.get().asFile.absolutePath}" ] if (agentOutputDir.isPresent()) { @@ -108,12 +107,15 @@ abstract class NativeTestArgumentProvider implements CommandLineArgumentProvider } args << "-H:ConfigurationFileDirectories=${outputDir.absolutePath}/agentOutput" - args << "-H:+AllowIncompleteClasspath" } if (discovery.get()) { args << "-DtestDiscovery" } + + // Main class comes last + args << "org.graalvm.junit.platform.NativeImageJUnitLauncher" + args.collect { it.toString() } } } diff --git a/common/utils/src/main/java/org/graalvm/buildtools/utils/NativeImageUtils.java b/common/utils/src/main/java/org/graalvm/buildtools/utils/NativeImageUtils.java index 0f62a8f05..6bf91a53f 100644 --- a/common/utils/src/main/java/org/graalvm/buildtools/utils/NativeImageUtils.java +++ b/common/utils/src/main/java/org/graalvm/buildtools/utils/NativeImageUtils.java @@ -61,6 +61,8 @@ public class NativeImageUtils { private static final Pattern graalvmVersionPattern = Pattern.compile("^(GraalVM|native-image) ([0-9]+)\\.([0-9]+)\\.([0-9]+).*"); + private static final Pattern SAFE_SHELL_ARG = Pattern.compile("[A-Za-z0-9@%_\\-+=:,./]+"); + public static void maybeCreateConfigureUtilSymlink(File configureUtilFile, Path nativeImageExecutablePath) { if (!configureUtilFile.exists()) { // possibly the symlink is missing @@ -102,16 +104,22 @@ public static List convertToArgsFile(List cliArgs, Path outputDi } } + + /** + * See https://github.com/oracle/graal/blob/f011d4d056a7ed78fe9669cc38062e6d09c14bed/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java#L1447C47-L1447C60. + */ public static String escapeArg(String arg) { - if (!(arg.startsWith("\\Q") && arg.endsWith("\\E"))) { - arg = arg.replace("\\", "\\\\"); - if (arg.contains(" ")) { - arg = "\"" + arg + "\""; - } + if (arg.isEmpty()) { + return "''"; } - return arg; + Matcher m = SAFE_SHELL_ARG.matcher(arg); + if (m.matches()) { + return arg; + } + return "'" + arg.replace("'", "'\"'\"'") + "'"; } + /** * * @param requiredVersion Required version can be {@code MAJOR}, {@code MAJOR.MINOR} or {@code MAJOR.MINOR.PATCH} @@ -164,4 +172,12 @@ public static void checkVersion(String requiredVersion, String versionToCheck) { } } } + + public static int getMajorJDKVersion(String versionString) { + Matcher matcher = graalvmVersionPattern.matcher(versionString.trim()); + if (matcher.matches()) { + return Integer.parseInt(matcher.group(2)); + } + return -1; + } } diff --git a/docs/src/docs/asciidoc/index.adoc b/docs/src/docs/asciidoc/index.adoc index af1c282e9..148d4245c 100644 --- a/docs/src/docs/asciidoc/index.adoc +++ b/docs/src/docs/asciidoc/index.adoc @@ -22,6 +22,7 @@ If you are using alternative build systems, see < options; private final Provider executableName; - private final Provider outputDirectory; private final Provider workingDirectory; + private final Provider outputDirectory; private final Provider classpathJar; private final Provider useArgFile; + private final Provider majorJDKVersion; public NativeImageCommandLineProvider(Provider options, Provider executableName, Provider workingDirectory, Provider outputDirectory, Provider classpathJar, - Provider useArgFile) { + Provider useArgFile, + Provider majorJDKVersion) { this.options = options; this.executableName = executableName; this.workingDirectory = workingDirectory; this.outputDirectory = outputDirectory; this.classpathJar = classpathJar; this.useArgFile = useArgFile; + this.majorJDKVersion = majorJDKVersion; } @Nested @@ -119,13 +122,15 @@ public List asArguments() { appendBooleanOption(cliArgs, options.getVerbose(), "--verbose"); appendBooleanOption(cliArgs, options.getSharedLibrary(), "--shared"); appendBooleanOption(cliArgs, options.getQuickBuild(), "-Ob"); - appendBooleanOption(cliArgs, options.getRichOutput(), "-H:+BuildOutputColorful"); + appendBooleanOption(cliArgs, options.getRichOutput(), majorJDKVersion.getOrElse(-1) >= 21 ? "--color" : "-H:+BuildOutputColorful"); appendBooleanOption(cliArgs, options.getPgoInstrument(), "--pgo-instrument"); + String targetOutputPath = getExecutableName().get(); if (getOutputDirectory().isPresent()) { - cliArgs.add("-H:Path=" + getOutputDirectory().get()); + targetOutputPath = getOutputDirectory().get() + File.separator + targetOutputPath; } - cliArgs.add("-H:Name=" + getExecutableName().get()); + cliArgs.add("-o"); + cliArgs.add(targetOutputPath); options.getSystemProperties().get().forEach((n, v) -> { if (v != null) { @@ -145,9 +150,6 @@ public List asArguments() { if (!configFiles.isEmpty()) { cliArgs.add("-H:ConfigurationFileDirectories=" + configFiles); } - if (options.getMainClass().isPresent()) { - cliArgs.add("-H:Class=" + options.getMainClass().get()); - } if (Boolean.FALSE.equals(options.getPgoInstrument().get()) && options.getPgoProfilesDirectory().isPresent()) { FileTree files = options.getPgoProfilesDirectory().get().getAsFileTree(); Set profiles = files.filter(f -> f.getName().endsWith(".iprof")).getFiles(); @@ -156,12 +158,20 @@ public List asArguments() { } } cliArgs.addAll(options.getBuildArgs().get()); + + List actualCliArgs; if (useArgFile.getOrElse(true)) { Path argFileDir = Paths.get(workingDirectory.get()); - return NativeImageUtils.convertToArgsFile(cliArgs, argFileDir, argFileDir); + actualCliArgs = new ArrayList<>(NativeImageUtils.convertToArgsFile(cliArgs, argFileDir, argFileDir)); + } else { + actualCliArgs = cliArgs; } - return Collections.unmodifiableList(cliArgs); + /* Main class comes last. It is kept outside argument files as GraalVM releases before JDK 21 fail to detect the mainClass in these files. */ + if (options.getMainClass().isPresent()) { + actualCliArgs.add(options.getMainClass().get()); + } + return Collections.unmodifiableList(actualCliArgs); } /** diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java index e96ce7d2d..ab6753e6e 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java @@ -179,7 +179,7 @@ public BuildNativeImageTask() { getDisableToolchainDetection().convention(false); } - private List buildActualCommandLineArgs() { + private List buildActualCommandLineArgs(int majorJDKVersion) { getOptions().finalizeValue(); return new NativeImageCommandLineProvider( getOptions(), @@ -189,7 +189,8 @@ private List buildActualCommandLineArgs() { // a mapped value before the task was called, when we are actually calling it... getProviders().provider(() -> getOutputDirectory().getAsFile().get().getAbsolutePath()), getClasspathJar(), - getUseArgFile()).asArguments(); + getUseArgFile(), + getProviders().provider(() -> majorJDKVersion)).asArguments(); } // This property provides access to the service instance @@ -203,10 +204,6 @@ public void exec() { NativeImageOptions options = getOptions().get(); GraalVMLogger logger = GraalVMLogger.of(getLogger()); - List args = buildActualCommandLineArgs(); - if (options.getVerbose().get()) { - logger.lifecycle("Args are: " + args); - } File executablePath = NativeImageExecutableLocator.findNativeImageExecutable( options.getJavaLauncher(), getDisableToolchainDetection(), @@ -214,7 +211,15 @@ public void exec() { getExecOperations(), logger, diagnostics); - checkRequiredVersionIfNeeded(executablePath, options); + String versionString = getVersionString(getExecOperations(), executablePath); + if (options.getRequiredVersion().isPresent()) { + NativeImageUtils.checkVersion(options.getRequiredVersion().get(), versionString); + } + int majorJDKVersion = NativeImageUtils.getMajorJDKVersion(versionString); + List args = buildActualCommandLineArgs(majorJDKVersion); + if (options.getVerbose().get()) { + logger.lifecycle("Args are: " + args); + } for (String diagnostic : diagnostics.getDiagnostics()) { logger.lifecycle(diagnostic); } @@ -240,19 +245,14 @@ public void exec() { } } - private void checkRequiredVersionIfNeeded(File executablePath, NativeImageOptions options) { - if (!options.getRequiredVersion().isPresent()) { - return; - } + public static String getVersionString(ExecOperations execOperations, File executablePath) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - ExecResult execResult = getExecOperations().exec(spec -> { + ExecResult execResult = execOperations.exec(spec -> { spec.setStandardOutput(outputStream); spec.args("--version"); spec.setExecutable(executablePath.getAbsolutePath()); }); execResult.assertNormalExitValue(); - String versionToCheck = new String(outputStream.toByteArray(), StandardCharsets.UTF_8); - NativeImageUtils.checkVersion(options.getRequiredVersion().get(), versionToCheck); + return new String(outputStream.toByteArray(), StandardCharsets.UTF_8); } - } 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 10d0f2aea..4bd725408 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 @@ -214,8 +214,8 @@ protected List getBuildArgs() throws MojoExecutionException { cliArgs.add("-Ob"); } - cliArgs.add("-H:Path=" + outputDirectory.toPath().toAbsolutePath()); - cliArgs.add("-H:Name=" + imageName); + cliArgs.add("-o"); + cliArgs.add(outputDirectory.toPath().toAbsolutePath() + File.separator + imageName); if (systemProperties != null) { for (Map.Entry entry : systemProperties.entrySet()) { @@ -240,21 +240,25 @@ protected List getBuildArgs() throws MojoExecutionException { ); } - if (mainClass != null && !mainClass.equals(".")) { - cliArgs.add("-H:Class=" + mainClass); - } - if (buildArgs != null && !buildArgs.isEmpty()) { for (String buildArg : buildArgs) { cliArgs.addAll(Arrays.asList(buildArg.split("\\s+"))); } } + List actualCliArgs; if (useArgFile) { Path tmpDir = Paths.get("target", "tmp"); - return NativeImageUtils.convertToArgsFile(cliArgs, tmpDir); + actualCliArgs = new ArrayList<>(NativeImageUtils.convertToArgsFile(cliArgs, tmpDir)); + } else { + actualCliArgs = cliArgs; } - return Collections.unmodifiableList(cliArgs); + + /* Main class comes last. It is kept outside argument files as GraalVM releases before JDK 21 fail to detect the mainClass in these files. */ + if (mainClass != null && !mainClass.equals(".")) { + actualCliArgs.add(mainClass); + } + return Collections.unmodifiableList(actualCliArgs); } protected Path processSupportedArtifacts(Artifact artifact) throws MojoExecutionException { @@ -453,12 +457,6 @@ protected void maybeAddGeneratedResourcesConfig(List into) { String value = configDirs.map(File::getAbsolutePath).collect(Collectors.joining(",")); if (!value.isEmpty()) { into.add("-H:ConfigurationFileDirectories=" + value); - if (agentResourceDirectory != null && agentResourceDirectory.isDirectory()) { - // The generated reflect config file contains references to java.* - // and org.apache.maven.surefire that we'd need to remove using - // a proper JSON parser/writer instead - into.add("-H:+AllowIncompleteClasspath"); - } } } } 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 d8ef982d0..de6cf262e 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 @@ -59,7 +59,7 @@ public abstract class NativeImageConfigurationUtils implements SharedConstants { public static final String NATIVE_TESTS_EXE = "native-tests" + EXECUTABLE_EXTENSION; public static final String MAVEN_GROUP_ID = "org.graalvm.buildtools"; - public static Path nativeImageCache; + public static Path nativeImageExeCache; public static Path getJavaHomeNativeImage(String javaHomeVariable, Boolean failFast, Logger logger) throws MojoExecutionException { String graalHome = System.getenv(javaHomeVariable); @@ -68,37 +68,36 @@ public static Path getJavaHomeNativeImage(String javaHomeVariable, Boolean failF } Path graalHomePath = Paths.get(graalHome); - Path graalExe = graalHomePath.resolve("bin").resolve(NATIVE_IMAGE_EXE); - Path guExe = graalHomePath.resolve("bin").resolve(GU_EXE); + Path nativeImageExe = graalHomePath.resolve("bin").resolve(NATIVE_IMAGE_EXE); - if (!Files.exists(graalExe)) { + if (!Files.exists(nativeImageExe)) { + Path guExe = graalHomePath.resolve("bin").resolve(GU_EXE); if (Files.exists(guExe)) { ProcessBuilder processBuilder = new ProcessBuilder(guExe.toString(), "install", "native-image"); processBuilder.inheritIO(); - try { Process nativeImageFetchingProcess = processBuilder.start(); if (nativeImageFetchingProcess.waitFor() != 0) { - throw new MojoExecutionException("Native Image executable wasn't found, and '" + GU_EXE + "' tool failed to install it."); + throw new MojoExecutionException("native-image was not found, and '" + GU_EXE + "' tool failed to install it."); } } catch (MojoExecutionException | IOException | InterruptedException e) { throw new MojoExecutionException("Determining GraalVM installation failed with message: " + e.getMessage()); } } else if (failFast) { - throw new MojoExecutionException("'" + GU_EXE + "' tool wasn't found. This probably means that JDK at isn't a GraalVM distribution."); + throw new MojoExecutionException("'" + GU_EXE + "' tool was not found. This probably means that JDK at is not a GraalVM distribution."); } } - if (!Files.exists(graalExe)) { + if (!Files.exists(nativeImageExe)) { if (failFast) { throw new RuntimeException("native-image is not installed in your " + javaHomeVariable + "." + - "You should install it using `gu install native-image`"); + "This probably means that JDK at is not a GraalVM distribution."); } else { return null; } } logger.info("Found GraalVM installation from " + javaHomeVariable + " variable."); - return graalExe; + return nativeImageExe; } public static Path getNativeImageFromPath() { @@ -110,8 +109,8 @@ public static Path getNativeImageFromPath() { } public static Path getNativeImage(Logger logger) throws MojoExecutionException { - if (nativeImageCache != null) { - return nativeImageCache; + if (nativeImageExeCache != null) { + return nativeImageExeCache; } Path nativeImage = getJavaHomeNativeImage("GRAALVM_HOME", false, logger); @@ -128,11 +127,11 @@ public static Path getNativeImage(Logger logger) throws MojoExecutionException { } if (nativeImage == null) { - throw new RuntimeException("GraalVM native-image is missing from your system.\n " + + throw new RuntimeException("GraalVM native-image is missing on your system. " + System.lineSeparator() + "Make sure that GRAALVM_HOME environment variable is present."); } - nativeImageCache = nativeImage; + nativeImageExeCache = nativeImage; return nativeImage; } }