diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/VerifyDebugUsage.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/VerifyDebugUsage.java index abee8580eabc..340d1da1f6e4 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/VerifyDebugUsage.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/VerifyDebugUsage.java @@ -83,22 +83,24 @@ protected void verify(StructuredGraph graph, CoreProviders context) { ResolvedJavaType nodeType = metaAccess.lookupJavaType(Node.class); ResolvedJavaType stringType = metaAccess.lookupJavaType(String.class); ResolvedJavaType graalErrorType = metaAccess.lookupJavaType(GraalError.class); + ResolvedJavaType errorType = metaAccess.lookupJavaType(Error.class); for (MethodCallTargetNode t : graph.getNodes(MethodCallTargetNode.TYPE)) { ResolvedJavaMethod callee = t.targetMethod(); String calleeName = callee.getName(); - if (callee.getDeclaringClass().equals(debugType)) { + ResolvedJavaType calleeDeclaringClass = callee.getDeclaringClass(); + if (calleeDeclaringClass.equals(debugType)) { boolean isDump = calleeName.equals("dump"); if (calleeName.equals("log") || calleeName.equals("logAndIndent") || calleeName.equals("verify") || isDump) { verifyParameters(metaAccess, t, t.arguments(), stringType, isDump ? 2 : 1); } } - if (callee.getDeclaringClass().isAssignableFrom(nodeType)) { + if (calleeDeclaringClass.isAssignableFrom(nodeType)) { if (calleeName.equals("assertTrue") || calleeName.equals("assertFalse")) { verifyParameters(metaAccess, t, t.arguments(), stringType, 1); } } - if (callee.getDeclaringClass().isAssignableFrom(graalErrorType) && !graph.method().getDeclaringClass().isAssignableFrom(graalErrorType)) { + if (calleeDeclaringClass.isAssignableFrom(graalErrorType) && !calleeDeclaringClass.equals(errorType) && !graph.method().getDeclaringClass().isAssignableFrom(graalErrorType)) { if (calleeName.equals("guarantee")) { verifyParameters(metaAccess, t, t.arguments(), stringType, 0); } diff --git a/docs/reference-manual/native-image/BuildOutput.md b/docs/reference-manual/native-image/BuildOutput.md index b26b3b5abd3c..4ea2a5cde4dd 100644 --- a/docs/reference-manual/native-image/BuildOutput.md +++ b/docs/reference-manual/native-image/BuildOutput.md @@ -128,6 +128,12 @@ A list of all active experimental options, including their origin and possible A Using experimental options should be avoided in production and can change in any release. If you rely on experimental features and would like an option to be considered stable, please file an issue. +#### Picked up `NATIVE_IMAGE_OPTIONS` +Additional build options picked up via the `NATIVE_IMAGE_OPTIONS` environment variable. +Similar to `JAVA_TOOL_OPTIONS`, the value of the environment variable is prepended to the options supplied to `native-image`. +Argument files are not allowed to be passed via `NATIVE_IMAGE_OPTIONS`. +The `NATIVE_IMAGE_OPTIONS` environment variable is designed to be used by users, build environments, or tools to inject additional build options. + #### Build Resources The memory limit and number of threads used by the build process. diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 787b5f3a1c91..bddc1ee578fb 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -9,6 +9,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-48354) Remove native-image-agent legacy `build`-option * (GR-49221) Support for thread dumps can now be enabled with `--enable-monitoring=threaddump`. The option `-H:±DumpThreadStacksOnSignal` is deprecated and marked for removal. * (GR-48579) Options ParseOnce, ParseOnceJIT, and InlineBeforeAnalysis are deprecated and no longer have any effect. +* (GR-39407) Add support for the `NATIVE_IMAGE_OPTIONS` environment variable, which allows users and tools to pass additional arguments via the environment. Similar to `JAVA_TOOL_OPTIONS`, the value of the environment variable is prepended to the options supplied to `native-image`. ## GraalVM for JDK 21 (Internal Version 23.1.0) * (GR-35746) Lower the default aligned chunk size from 1 MB to 512 KB for the serial and epsilon GCs, reducing memory usage and image size in many cases. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 06366f2c1ae8..465ac470c8b1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -533,6 +533,11 @@ public static final boolean hasColorsEnabled(OptionValues values) { "docs/reference-manual/native-image/assets/build-output-schema-v0.9.2.json", type = OptionType.User)// public static final HostedOptionKey BuildOutputJSONFile = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.build()); + public static final String NATIVE_IMAGE_OPTIONS_ENV_VAR = "NATIVE_IMAGE_OPTIONS"; + + @Option(help = "Internal option to forward the value of " + NATIVE_IMAGE_OPTIONS_ENV_VAR)// + public static final HostedOptionKey BuildOutputNativeImageOptionsEnvVarValue = new HostedOptionKey<>(null); + /* * Object and array allocation options. */ diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/APIOptionHandler.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/APIOptionHandler.java index 0d0be3926519..5756f79b243b 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/APIOptionHandler.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/APIOptionHandler.java @@ -303,7 +303,7 @@ boolean consume(ArgumentQueue args) { String translatedOption = translateOption(args); if (translatedOption != null) { args.poll(); - nativeImage.addPlainImageBuilderArg(NativeImage.injectHostedOptionOrigin(translatedOption, args.argumentOrigin + OptionOrigin.isAPISuffix)); + nativeImage.addPlainImageBuilderArg(translatedOption, args.argumentOrigin + OptionOrigin.isAPISuffix); return true; } if (ENTER_UNLOCK_SCOPE.equals(headArg)) { diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/ArgFilesOptionPreprocessor.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/ArgFilesOptionPreprocessor.java index 2707d5b5ba40..99a28e723261 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/ArgFilesOptionPreprocessor.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/ArgFilesOptionPreprocessor.java @@ -33,21 +33,28 @@ import java.util.List; import java.util.Objects; -class ArgFilesOptionPreprocessor { +import com.oracle.svm.hosted.util.JDKArgsUtils; + +public class ArgFilesOptionPreprocessor { private static final String DISABLE_AT_FILES_OPTION = "--disable-@files"; private boolean disableAtFiles = false; public List process(String currentArg) { - switch (currentArg) { - case DISABLE_AT_FILES_OPTION: - disableAtFiles = true; - return List.of(); + String argWithoutQuotes = currentArg.replaceAll("['\"]*", ""); + if (DISABLE_AT_FILES_OPTION.equals(argWithoutQuotes)) { + disableAtFiles = true; + return List.of(); } - if (!disableAtFiles && currentArg.startsWith("@")) { - Path argFile = Paths.get(currentArg.substring(1)); + if (!disableAtFiles && argWithoutQuotes.startsWith("@")) { + String argWithoutAt = argWithoutQuotes.substring(1); + if (argWithoutAt.startsWith("@")) { + // escaped @argument + return List.of(argWithoutAt); + } + Path argFile = Paths.get(argWithoutAt); return readArgFile(argFile); } @@ -130,7 +137,7 @@ private static String nextToken(CTX_ARGS ctx) { // Skip white space characters if (ctx.state == PARSER_STATE.FIND_NEXT || ctx.state == PARSER_STATE.SKIP_LEAD_WS) { - while (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\f') { + while (JDKArgsUtils.isspace(ch)) { nextc++; if (nextc >= eob) { return null; diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/CmdLineOptionHandler.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/CmdLineOptionHandler.java index 4914b9e2db6f..c2b1ca7d5cb2 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/CmdLineOptionHandler.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/CmdLineOptionHandler.java @@ -30,8 +30,6 @@ import java.util.Optional; import java.util.regex.Pattern; -import jdk.graal.compiler.options.OptionType; - import com.oracle.svm.core.VM; import com.oracle.svm.core.option.OptionOrigin; import com.oracle.svm.core.option.OptionUtils; @@ -39,6 +37,8 @@ import com.oracle.svm.driver.NativeImage.ArgumentQueue; import com.oracle.svm.util.LogUtils; +import jdk.graal.compiler.options.OptionType; + class CmdLineOptionHandler extends NativeImage.OptionHandler { private static final String HELP_TEXT = NativeImage.getResource("/Help.txt"); @@ -167,7 +167,7 @@ private boolean consume(ArgumentQueue args, String headArg) { /* Using agentlib to allow interoperability with other agents */ nativeImage.addImageBuilderJavaArgs("-agentlib:jdwp=transport=dt_socket,server=y,address=" + address + ",suspend=y"); /* Disable watchdog mechanism */ - nativeImage.addPlainImageBuilderArg(NativeImage.injectHostedOptionOrigin(nativeImage.oHDeadlockWatchdogInterval + "0", OptionOrigin.originDriver)); + nativeImage.addPlainImageBuilderArg(nativeImage.oHDeadlockWatchdogInterval + "0", OptionOrigin.originDriver); return true; } diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/DefaultOptionHandler.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/DefaultOptionHandler.java index 01635b5d70be..df0cbe0bad92 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/DefaultOptionHandler.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/DefaultOptionHandler.java @@ -85,9 +85,9 @@ public boolean consume(ArgumentQueue args) { } String[] mainClassModuleArgParts = mainClassModuleArg.split("/", 2); if (mainClassModuleArgParts.length > 1) { - nativeImage.addPlainImageBuilderArg(NativeImage.injectHostedOptionOrigin(nativeImage.oHClass + mainClassModuleArgParts[1], OptionOrigin.originDriver)); + nativeImage.addPlainImageBuilderArg(nativeImage.oHClass + mainClassModuleArgParts[1], OptionOrigin.originDriver); } - nativeImage.addPlainImageBuilderArg(NativeImage.injectHostedOptionOrigin(nativeImage.oHModule + mainClassModuleArgParts[0], OptionOrigin.originDriver)); + nativeImage.addPlainImageBuilderArg(nativeImage.oHModule + mainClassModuleArgParts[0], OptionOrigin.originDriver); nativeImage.setModuleOptionMode(true); return true; case addModulesOption: @@ -152,7 +152,7 @@ public boolean consume(ArgumentQueue args) { } if (headArg.startsWith(NativeImage.oH)) { args.poll(); - nativeImage.addPlainImageBuilderArg(NativeImage.injectHostedOptionOrigin(headArg, args.argumentOrigin)); + nativeImage.addPlainImageBuilderArg(headArg, args.argumentOrigin); return true; } if (headArg.startsWith(NativeImage.oR)) { @@ -252,7 +252,7 @@ private void handleJarFileArg(Path jarFilePath) { } if (!jarFileNameBase.isEmpty()) { String origin = "manifest from " + jarFilePath.toUri(); - nativeImage.addPlainImageBuilderArg(NativeImage.injectHostedOptionOrigin(nativeImage.oHName + jarFileNameBase, origin)); + nativeImage.addPlainImageBuilderArg(nativeImage.oHName + jarFileNameBase, origin); } Path finalFilePath = nativeImage.useBundle() ? nativeImage.bundleSupport.substituteClassPath(jarFilePath) : jarFilePath; if (!NativeImage.processJarManifestMainAttributes(finalFilePath, nativeImage::handleManifestFileAttributes)) { diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java index da8ed40f9de7..b26900527e60 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java @@ -69,8 +69,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import jdk.graal.compiler.options.OptionKey; -import jdk.graal.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.ProcessProperties; @@ -98,11 +96,14 @@ import com.oracle.svm.driver.metainf.NativeImageMetaInfWalker; import com.oracle.svm.hosted.NativeImageGeneratorRunner; import com.oracle.svm.hosted.NativeImageSystemClassLoader; +import com.oracle.svm.hosted.util.JDKArgsUtils; import com.oracle.svm.util.LogUtils; import com.oracle.svm.util.ModuleSupport; import com.oracle.svm.util.ReflectionUtil; import com.oracle.svm.util.StringUtil; +import jdk.graal.compiler.options.OptionKey; +import jdk.graal.compiler.serviceprovider.JavaVersionUtil; import jdk.internal.jimage.ImageReader; public class NativeImage { @@ -261,6 +262,7 @@ private static String oR(OptionKey option) { final String oHCLibraryPath = oH(SubstrateOptions.CLibraryPath); final String oHFallbackThreshold = oH(SubstrateOptions.FallbackThreshold); final String oHFallbackExecutorJavaArg = oH(FallbackExecutor.Options.FallbackExecutorJavaArg); + final String oHNativeImageOptionsEnvVar = oH(SubstrateOptions.BuildOutputNativeImageOptionsEnvVarValue, OptionOrigin.originDriver); final String oRRuntimeJavaArg = oR(Options.FallbackExecutorRuntimeJavaArg); final String oHTraceClassInitialization = oH(SubstrateOptions.TraceClassInitialization); final String oHTraceObjectInstantiation = oH(SubstrateOptions.TraceObjectInstantiation); @@ -687,7 +689,7 @@ public boolean processMetaInfResource(Path classpathEntry, Path resourceRoot, Pa if (isNativeImagePropertiesFile) { String imageNameValue = properties.get("ImageName"); if (imageNameValue != null) { - addPlainImageBuilderArg(injectHostedOptionOrigin(oHName + resolver.apply(imageNameValue), resourcePath.toUri().toString())); + addPlainImageBuilderArg(oHName + resolver.apply(imageNameValue), resourcePath.toUri().toString()); } forEachPropertyValue(properties.get("JavaArgs"), NativeImage.this::addImageBuilderJavaArgs, resolver); forEachPropertyValue(properties.get("Args"), args, resolver); @@ -826,7 +828,7 @@ protected NativeImage(BuildConfiguration config) { } // Generate images into the current directory - addPlainImageBuilderArg(injectHostedOptionOrigin(oHPath + config.getWorkingDirectory(), OptionOrigin.originDriver)); + addPlainImageBuilderArg(oHPath + config.getWorkingDirectory(), OptionOrigin.originDriver); /* Discover supported MacroOptions */ optionRegistry = new MacroOption.Registry(); @@ -854,13 +856,23 @@ protected void registerOptionHandler(OptionHandler handle } private List getDefaultNativeImageArgs() { - String defaultNativeImageArgs = userConfigProperties.get("NativeImageArgs"); - if (defaultNativeImageArgs != null && !defaultNativeImageArgs.isEmpty()) { - String optionName = BundleSupport.BundleOptionVariants.apply.optionName(); - if (config.getBuildArgs().stream().noneMatch(arg -> arg.startsWith(optionName + "="))) { - return List.of(defaultNativeImageArgs.split(" ")); + List defaultNativeImageArgs = new ArrayList<>(); + String propertyOptions = userConfigProperties.get("NativeImageArgs"); + if (propertyOptions != null) { + Collections.addAll(defaultNativeImageArgs, propertyOptions.split(" ")); + } + final String envVarName = SubstrateOptions.NATIVE_IMAGE_OPTIONS_ENV_VAR; + String nativeImageOptionsValue = System.getenv(envVarName); + if (nativeImageOptionsValue != null) { + addPlainImageBuilderArg(oHNativeImageOptionsEnvVar + nativeImageOptionsValue); + defaultNativeImageArgs.addAll(JDKArgsUtils.parseArgsFromEnvVar(nativeImageOptionsValue, envVarName, msg -> showError(msg))); + } + if (!defaultNativeImageArgs.isEmpty()) { + String buildApplyOptionName = BundleSupport.BundleOptionVariants.apply.optionName(); + if (config.getBuildArgs().stream().noneMatch(arg -> arg.startsWith(buildApplyOptionName + "="))) { + return List.copyOf(defaultNativeImageArgs); } else { - LogUtils.warning("Option " + optionName + " in use. Ignoring args from file specified with environment variable " + NativeImage.CONFIG_FILE_ENV_VAR_KEY + "."); + LogUtils.warning("Option " + buildApplyOptionName + " in use. Ignoring args from file specified with environment variable " + NativeImage.CONFIG_FILE_ENV_VAR_KEY + "."); } } return List.of(); @@ -907,7 +919,7 @@ private void completeOptionArgs() { LinkedHashSet enabledOptions = optionRegistry.getEnabledOptions(); /* Any use of MacroOptions opts-out of auto-fallback and activates --no-fallback */ if (!enabledOptions.isEmpty()) { - addPlainImageBuilderArg(injectHostedOptionOrigin(oHFallbackThreshold + SubstrateOptions.NoFallback, OptionOrigin.originDriver)); + addPlainImageBuilderArg(oHFallbackThreshold + SubstrateOptions.NoFallback, OptionOrigin.originDriver); } consolidateListArgs(imageBuilderJavaArgs, "-Dpolyglot.engine.PreinitializeContexts=", ",", Function.identity()); // legacy consolidateListArgs(imageBuilderJavaArgs, "-Dpolyglot.image-build-time.PreinitializeContexts=", ",", Function.identity()); @@ -950,7 +962,7 @@ public void addExcludeConfig(Pattern jarPattern, Pattern resourcePattern) { excludedConfigs.add(new ExcludeConfig(jarPattern, resourcePattern)); } - static String injectHostedOptionOrigin(String option, String origin) { + private static String injectHostedOptionOrigin(String option, String origin) { if (origin != null && option.startsWith(oH)) { String optionOriginSeparator = "@"; int eqIndex = option.indexOf('='); @@ -996,7 +1008,7 @@ void handleMainClassAttribute(Path jarFilePath, Attributes mainAttributes) { NativeImage.showError("No main manifest attribute, in " + jarFilePath); } String origin = "manifest from " + jarFilePath.toUri(); - addPlainImageBuilderArg(NativeImage.injectHostedOptionOrigin(oHClass + mainClassValue, origin)); + addPlainImageBuilderArg(oHClass + mainClassValue, origin); } void handleModuleAttributes(Attributes mainAttributes) { @@ -1986,6 +1998,10 @@ List apply(boolean strict) { } } + void addPlainImageBuilderArg(String plainArg, String origin) { + addPlainImageBuilderArg(injectHostedOptionOrigin(plainArg, origin)); + } + void addPlainImageBuilderArg(String plainArg) { assert plainArg.startsWith(NativeImage.oH) || plainArg.startsWith(NativeImage.oR); imageBuilderArgs.add(plainArg); @@ -2320,7 +2336,7 @@ private boolean configureBuildOutput() { useColorfulOutput = true; } else if ("auto".equals(colorValue)) { useColorfulOutput = hasColorSupport(); - addPlainImageBuilderArg(injectHostedOptionOrigin(oHColor + (useColorfulOutput ? "always" : "never"), OptionOrigin.originDriver)); + addPlainImageBuilderArg(oHColor + (useColorfulOutput ? "always" : "never"), OptionOrigin.originDriver); } } else { Boolean buildOutputColorfulValue = getHostedOptionFinalBooleanArgumentValue(imageBuilderArgs, SubstrateOptions.BuildOutputColorful); @@ -2328,7 +2344,7 @@ private boolean configureBuildOutput() { useColorfulOutput = buildOutputColorfulValue; // use value set by user } else if (hasColorSupport()) { useColorfulOutput = true; - addPlainImageBuilderArg(injectHostedOptionOrigin(oHColor + "always", OptionOrigin.originDriver)); + addPlainImageBuilderArg(oHColor + "always", OptionOrigin.originDriver); } } if (getHostedOptionFinalBooleanArgumentValue(imageBuilderArgs, SubstrateOptions.BuildOutputProgress) == null && hasProgressSupport(imageBuilderArgs)) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java index 3399c5ec69c3..8afa083a69d8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java @@ -76,6 +76,7 @@ import com.oracle.svm.core.option.OptionOrigin; import com.oracle.svm.core.option.RuntimeOptionKey; import com.oracle.svm.core.option.SubstrateOptionsParser; +import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; import com.oracle.svm.core.util.json.JsonWriter; import com.oracle.svm.hosted.ProgressReporterFeature.UserRecommendation; @@ -89,6 +90,7 @@ import com.oracle.svm.hosted.reflect.ReflectionHostedSupport; import com.oracle.svm.hosted.util.CPUType; import com.oracle.svm.hosted.util.DiagnosticUtils; +import com.oracle.svm.hosted.util.JDKArgsUtils; import com.oracle.svm.hosted.util.VMErrorReporter; import com.oracle.svm.util.ImageBuildStatistics; @@ -254,6 +256,7 @@ public void printInitializeEnd(List features, ImageClassLoader classLoa printFeatures(features); printExperimentalOptions(classLoader); + printEnvironmentVariableOptions(); printResourceInfo(); } @@ -403,6 +406,17 @@ private static boolean isStableOrInternalOrigin(OptionOrigin origin) { return origin.isStable() || origin.isInternal(); } + private void printEnvironmentVariableOptions() { + String envVarValue = SubstrateOptions.BuildOutputNativeImageOptionsEnvVarValue.getValue(); + if (envVarValue != null && !envVarValue.isEmpty()) { + l().printLineSeparator(); + l().yellowBold().a(" ").doclink("Picked up " + SubstrateOptions.NATIVE_IMAGE_OPTIONS_ENV_VAR, "#glossary-picked-up-ni-options").reset().a(":").println(); + for (String arg : JDKArgsUtils.parseArgsFromEnvVar(envVarValue, SubstrateOptions.NATIVE_IMAGE_OPTIONS_ENV_VAR, msg -> UserError.abort(msg))) { + l().a(" - '%s'", arg).println(); + } + } + } + private void printResourceInfo() { Runtime runtime = Runtime.getRuntime(); long maxMemory = runtime.maxMemory(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/util/JDKArgsUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/util/JDKArgsUtils.java new file mode 100644 index 000000000000..2d04ba1dc62f --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/util/JDKArgsUtils.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +/** Ported utils from JDK21's java.base/share/native/libjli/args.c. */ +public class JDKArgsUtils { + + public static List parseArgsFromEnvVar(String envVarValue, String envVarName, Function errorFunction) { + List result = new ArrayList<>(); + int envVarValueLength = envVarValue.length(); + int i = 0; + while (i < envVarValueLength) { + while (i < envVarValueLength && isspace(envVarValue.charAt(i))) { + i++; + } + + // Trailing space + if (i >= envVarValueLength) { + break; + } + + char currentChar; + StringBuilder argChars = new StringBuilder(); + while (i < envVarValueLength && !isspace(currentChar = envVarValue.charAt(i))) { + if (currentChar == '"' || currentChar == '\'') { + char quote = currentChar; + i++; + while (i < envVarValueLength && envVarValue.charAt(i) != quote) { + argChars.append(envVarValue.charAt(i++)); + } + if (i >= envVarValueLength) { + throw errorFunction.apply("Unmatched quote in environment variable " + envVarName); + } + i++; + } else { + argChars.append(envVarValue.charAt(i++)); + } + } + String argument = argChars.toString(); + // This port is more restrictive as it forbids arg files to be passed via an env var + boolean isArgFileOption = argument.startsWith("@") && !argument.startsWith("@@"); + if (isArgFileOption || isTerminalOpt(argument)) { + throw errorFunction.apply("Option '" + argument + "' is not allowed in environment variable " + envVarName); + } else if (!isExpectingNoDashArg(argument, result)) { + throw errorFunction.apply("Cannot specify main class in environment variable " + envVarName); + } + result.add(argument); + assert i >= envVarValueLength || isspace(envVarValue.charAt(i)); + } + return result; + } + + private static boolean isExpectingNoDashArg(String argument, List previousArgs) { + if (argument.startsWith("-")) { + return true; // Ignore dash args + } + if (previousArgs.isEmpty()) { + return false; // No previous arg means the no-dash arg is unexpected + } + String previousArg = previousArgs.getLast(); + // Derivation from port: unpack any flags for JVM running the image generator + previousArg = previousArg.startsWith("-J") ? previousArg.substring(2) : previousArg; + boolean expectingNoDashArg = isWhiteSpaceOption(previousArg); + if ("-jar".equals(previousArg) || "--module".equals(previousArg) || "-m".equals(previousArg)) { + expectingNoDashArg = false; + } + return expectingNoDashArg; + } + + public static boolean isspace(char value) { + // \v not supported in Java + return value == ' ' || value == '\f' || value == '\n' || value == '\r' || value == '\t'; + } + + private static boolean isTerminalOpt(String arg) { + return switch (arg) { + /* JDK terminal options supported by SVM */ + case "-jar", "-m", "--module", "--dry-run", "--help", "--help-extra", "--version" -> true; + /* JDK terminal options not (yet) supported by SVM */ + case "-h", "-?", "-help", "-X", "-version", "-fullversion", "--full-version" -> true; + /* SVM-only terminal options */ + case "--expert-options", "--expert-options-all", "--expert-options-detail" -> true; + default -> arg.startsWith("--module="); + }; + } + + private static boolean isWhiteSpaceOption(String name) { + return isModuleOption(name) || isLauncherOption(name); + } + + private static boolean isModuleOption(String name) { + return switch (name) { + case "--module-path", "-p", "--upgrade-module-path", "--add-modules", "--enable-native-access", "--limit-modules", "--add-exports", "--add-opens", "--add-reads", "--patch-module" -> true; + default -> false; + }; + } + + private static boolean isLauncherOption(String name) { + return isClassPathOption(name) || + isLauncherMainOption(name) || + "--describe-module".equals(name) || + "-d".equals(name) || + "--source".equals(name); + } + + private static boolean isClassPathOption(String name) { + return "-classpath".equals(name) || + "-cp".equals(name) || + "--class-path".equals(name); + } + + private static boolean isLauncherMainOption(String name) { + return "--module".equals(name) || "-m".equals(name); + } +}