Skip to content

Commit

Permalink
Add support for $NATIVE_IMAGE_OPTIONS.
Browse files Browse the repository at this point in the history
  • Loading branch information
fniephaus committed Nov 6, 2023
1 parent b95a9f9 commit 9b89841
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 16 deletions.
6 changes: 6 additions & 0 deletions docs/reference-manual/native-image/BuildOutput.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#### <a name="glossary-picked-up-ni-options"></a>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.
#### <a name="glossary-build-resources"></a>Build Resources
The memory limit and number of threads used by the build process.
Expand Down
1 change: 1 addition & 0 deletions substratevm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<LocatableMultiOptionValue.Paths> 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<String> BuildOutputNativeImageOptionsEnvVarValue = new HostedOptionKey<>(null);

/*
* Object and array allocation options.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> 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);
}

Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -261,6 +262,7 @@ private static <T> String oR(OptionKey<T> 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);
Expand Down Expand Up @@ -854,13 +856,23 @@ protected void registerOptionHandler(OptionHandler<? extends NativeImage> handle
}

private List<String> 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<String> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -254,6 +256,7 @@ public void printInitializeEnd(List<Feature> features, ImageClassLoader classLoa

printFeatures(features);
printExperimentalOptions(classLoader);
printEnvironmentVariableOptions();
printResourceInfo();
}

Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> parseArgsFromEnvVar(String envVarValue, String envVarName, Function<String, Error> errorFunction) {
List<String> 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<String> 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);
}
}

0 comments on commit 9b89841

Please sign in to comment.