Skip to content

Commit 4beb8e8

Browse files
committed
Add support for $NATIVE_IMAGE_OPTIONS.
1 parent 074d66e commit 4beb8e8

File tree

7 files changed

+203
-17
lines changed

7 files changed

+203
-17
lines changed

docs/reference-manual/native-image/BuildOutput.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ A list of all active experimental options, including their origin and possible A
128128
Using experimental options should be avoided in production and can change in any release.
129129
If you rely on experimental features and would like an option to be considered stable, please file an issue.
130130
131+
#### <a name="glossary-picked-up-ni-options"></a>Picked up `NATIVE_IMAGE_OPTIONS`
132+
Additional build options picked up via the `NATIVE_IMAGE_OPTIONS` environment variable.
133+
Similar to `JAVA_TOOL_OPTIONS`, the value of the environment variable is prepended to the options supplied to `native-image`.
134+
Argument files are not allowed to be passed via `NATIVE_IMAGE_OPTIONS`.
135+
The `NATIVE_IMAGE_OPTIONS` environment variable is designed to be used by users, build environments, or tools to inject additional build options.
136+
131137
#### <a name="glossary-build-resources"></a>Build Resources
132138
The memory limit and number of threads used by the build process.
133139

substratevm/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This changelog summarizes major changes to GraalVM Native Image.
99
* (GR-48354) Remove native-image-agent legacy `build`-option
1010
* (GR-49221) Support for thread dumps can now be enabled with `--enable-monitoring=threaddump`. The option `-H:±DumpThreadStacksOnSignal` is deprecated and marked for removal.
1111
* (GR-48579) Options ParseOnce, ParseOnceJIT, and InlineBeforeAnalysis are deprecated and no longer have any effect.
12+
* (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`.
1213

1314
## GraalVM for JDK 21 (Internal Version 23.1.0)
1415
* (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.

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,11 @@ public static final boolean hasColorsEnabled(OptionValues values) {
533533
"docs/reference-manual/native-image/assets/build-output-schema-v0.9.2.json", type = OptionType.User)//
534534
public static final HostedOptionKey<LocatableMultiOptionValue.Paths> BuildOutputJSONFile = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.build());
535535

536+
public static final String NATIVE_IMAGE_OPTIONS_ENV_VAR = "NATIVE_IMAGE_OPTIONS";
537+
538+
@Option(help = "Internal option to forward the value of " + NATIVE_IMAGE_OPTIONS_ENV_VAR)//
539+
public static final HostedOptionKey<String> BuildOutputNativeImageOptionsEnvVarValue = new HostedOptionKey<>(null);
540+
536541
/*
537542
* Object and array allocation options.
538543
*/

substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/ArgFilesOptionPreprocessor.java

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,28 @@
3333
import java.util.List;
3434
import java.util.Objects;
3535

36-
class ArgFilesOptionPreprocessor {
36+
import com.oracle.svm.hosted.util.JDKArgsUtils;
37+
38+
public class ArgFilesOptionPreprocessor {
3739

3840
private static final String DISABLE_AT_FILES_OPTION = "--disable-@files";
3941

4042
private boolean disableAtFiles = false;
4143

4244
public List<String> process(String currentArg) {
43-
switch (currentArg) {
44-
case DISABLE_AT_FILES_OPTION:
45-
disableAtFiles = true;
46-
return List.of();
45+
String argWithoutQuotes = currentArg.replaceAll("['\"]*", "");
46+
if (DISABLE_AT_FILES_OPTION.equals(argWithoutQuotes)) {
47+
disableAtFiles = true;
48+
return List.of();
4749
}
4850

49-
if (!disableAtFiles && currentArg.startsWith("@")) {
50-
Path argFile = Paths.get(currentArg.substring(1));
51+
if (!disableAtFiles && argWithoutQuotes.startsWith("@")) {
52+
String argWithoutAt = argWithoutQuotes.substring(1);
53+
if (argWithoutAt.startsWith("@")) {
54+
// escaped @argument
55+
return List.of(argWithoutAt);
56+
}
57+
Path argFile = Paths.get(argWithoutAt);
5158
return readArgFile(argFile);
5259
}
5360

@@ -130,7 +137,7 @@ private static String nextToken(CTX_ARGS ctx) {
130137

131138
// Skip white space characters
132139
if (ctx.state == PARSER_STATE.FIND_NEXT || ctx.state == PARSER_STATE.SKIP_LEAD_WS) {
133-
while (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\f') {
140+
while (JDKArgsUtils.isspace(ch)) {
134141
nextc++;
135142
if (nextc >= eob) {
136143
return null;

substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,6 @@
6969
import java.util.stream.Collectors;
7070
import java.util.stream.Stream;
7171

72-
import jdk.graal.compiler.options.OptionKey;
73-
import jdk.graal.compiler.serviceprovider.JavaVersionUtil;
7472
import org.graalvm.nativeimage.Platform;
7573
import org.graalvm.nativeimage.ProcessProperties;
7674

@@ -98,11 +96,14 @@
9896
import com.oracle.svm.driver.metainf.NativeImageMetaInfWalker;
9997
import com.oracle.svm.hosted.NativeImageGeneratorRunner;
10098
import com.oracle.svm.hosted.NativeImageSystemClassLoader;
99+
import com.oracle.svm.hosted.util.JDKArgsUtils;
101100
import com.oracle.svm.util.LogUtils;
102101
import com.oracle.svm.util.ModuleSupport;
103102
import com.oracle.svm.util.ReflectionUtil;
104103
import com.oracle.svm.util.StringUtil;
105104

105+
import jdk.graal.compiler.options.OptionKey;
106+
import jdk.graal.compiler.serviceprovider.JavaVersionUtil;
106107
import jdk.internal.jimage.ImageReader;
107108

108109
public class NativeImage {
@@ -261,6 +262,7 @@ private static <T> String oR(OptionKey<T> option) {
261262
final String oHCLibraryPath = oH(SubstrateOptions.CLibraryPath);
262263
final String oHFallbackThreshold = oH(SubstrateOptions.FallbackThreshold);
263264
final String oHFallbackExecutorJavaArg = oH(FallbackExecutor.Options.FallbackExecutorJavaArg);
265+
final String oHNativeImageOptionsEnvVar = oH(SubstrateOptions.BuildOutputNativeImageOptionsEnvVarValue, OptionOrigin.originDriver);
264266
final String oRRuntimeJavaArg = oR(Options.FallbackExecutorRuntimeJavaArg);
265267
final String oHTraceClassInitialization = oH(SubstrateOptions.TraceClassInitialization);
266268
final String oHTraceObjectInstantiation = oH(SubstrateOptions.TraceObjectInstantiation);
@@ -854,13 +856,23 @@ protected void registerOptionHandler(OptionHandler<? extends NativeImage> handle
854856
}
855857

856858
private List<String> getDefaultNativeImageArgs() {
857-
String defaultNativeImageArgs = userConfigProperties.get("NativeImageArgs");
858-
if (defaultNativeImageArgs != null && !defaultNativeImageArgs.isEmpty()) {
859-
String optionName = BundleSupport.BundleOptionVariants.apply.optionName();
860-
if (config.getBuildArgs().stream().noneMatch(arg -> arg.startsWith(optionName + "="))) {
861-
return List.of(defaultNativeImageArgs.split(" "));
859+
List<String> defaultNativeImageArgs = new ArrayList<>();
860+
String propertyOptions = userConfigProperties.get("NativeImageArgs");
861+
if (propertyOptions != null) {
862+
Collections.addAll(defaultNativeImageArgs, propertyOptions.split(" "));
863+
}
864+
final String envVarName = SubstrateOptions.NATIVE_IMAGE_OPTIONS_ENV_VAR;
865+
String nativeImageOptionsValue = System.getenv(envVarName);
866+
if (nativeImageOptionsValue != null) {
867+
addPlainImageBuilderArg(oHNativeImageOptionsEnvVar + nativeImageOptionsValue);
868+
defaultNativeImageArgs.addAll(JDKArgsUtils.parseArgsFromEnvVar(nativeImageOptionsValue, envVarName, msg -> showError(msg)));
869+
}
870+
if (!defaultNativeImageArgs.isEmpty()) {
871+
String buildApplyOptionName = BundleSupport.BundleOptionVariants.apply.optionName();
872+
if (config.getBuildArgs().stream().noneMatch(arg -> arg.startsWith(buildApplyOptionName + "="))) {
873+
return List.copyOf(defaultNativeImageArgs);
862874
} else {
863-
LogUtils.warning("Option " + optionName + " in use. Ignoring args from file specified with environment variable " + NativeImage.CONFIG_FILE_ENV_VAR_KEY + ".");
875+
LogUtils.warning("Option " + buildApplyOptionName + " in use. Ignoring args from file specified with environment variable " + NativeImage.CONFIG_FILE_ENV_VAR_KEY + ".");
864876
}
865877
}
866878
return List.of();

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,13 @@
5151
import java.util.function.Consumer;
5252
import java.util.stream.Collectors;
5353

54-
import com.oracle.graal.pointsto.meta.AnalysisType;
5554
import org.graalvm.nativeimage.ImageSingletons;
5655
import org.graalvm.nativeimage.hosted.Feature;
5756
import org.graalvm.nativeimage.impl.ImageSingletonsSupport;
5857

5958
import com.oracle.graal.pointsto.meta.AnalysisField;
6059
import com.oracle.graal.pointsto.meta.AnalysisMethod;
60+
import com.oracle.graal.pointsto.meta.AnalysisType;
6161
import com.oracle.graal.pointsto.meta.AnalysisUniverse;
6262
import com.oracle.graal.pointsto.reports.ReportUtils;
6363
import com.oracle.graal.pointsto.util.Timer;
@@ -77,6 +77,7 @@
7777
import com.oracle.svm.core.option.LocatableMultiOptionValue;
7878
import com.oracle.svm.core.option.OptionOrigin;
7979
import com.oracle.svm.core.option.SubstrateOptionsParser;
80+
import com.oracle.svm.core.util.UserError;
8081
import com.oracle.svm.core.util.VMError;
8182
import com.oracle.svm.core.util.json.JsonWriter;
8283
import com.oracle.svm.hosted.ProgressReporterFeature.UserRecommendation;
@@ -90,6 +91,7 @@
9091
import com.oracle.svm.hosted.reflect.ReflectionHostedSupport;
9192
import com.oracle.svm.hosted.util.CPUType;
9293
import com.oracle.svm.hosted.util.DiagnosticUtils;
94+
import com.oracle.svm.hosted.util.JDKArgsUtils;
9395
import com.oracle.svm.hosted.util.VMErrorReporter;
9496
import com.oracle.svm.util.ImageBuildStatistics;
9597

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

255257
printFeatures(features);
256258
printExperimentalOptions(classLoader);
259+
printEnvironmentVariableOptions();
257260
printResourceInfo();
258261
}
259262

@@ -375,6 +378,17 @@ private static boolean isStableOrInternalOrigin(OptionOrigin origin) {
375378
return origin.isStable() || origin.isInternal();
376379
}
377380

381+
private void printEnvironmentVariableOptions() {
382+
String envVarValue = SubstrateOptions.BuildOutputNativeImageOptionsEnvVarValue.getValue();
383+
if (envVarValue != null && !envVarValue.isEmpty()) {
384+
l().printLineSeparator();
385+
l().yellowBold().a(" ").doclink("Picked up " + SubstrateOptions.NATIVE_IMAGE_OPTIONS_ENV_VAR, "#glossary-picked-up-ni-options").reset().a(":").println();
386+
for (String arg : JDKArgsUtils.parseArgsFromEnvVar(envVarValue, SubstrateOptions.NATIVE_IMAGE_OPTIONS_ENV_VAR, msg -> UserError.abort(msg))) {
387+
l().a(" - '%s'", arg).println();
388+
}
389+
}
390+
}
391+
378392
private void printResourceInfo() {
379393
Runtime runtime = Runtime.getRuntime();
380394
long maxMemory = runtime.maxMemory();
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.hosted.util;
26+
27+
import java.util.ArrayList;
28+
import java.util.List;
29+
import java.util.function.Function;
30+
31+
/** Ported utils from JDK21's java.base/share/native/libjli/args.c. */
32+
public class JDKArgsUtils {
33+
34+
public static List<String> parseArgsFromEnvVar(String envVarValue, String envVarName, Function<String, Error> errorFunction) {
35+
List<String> result = new ArrayList<>();
36+
int envVarValueLength = envVarValue.length();
37+
int i = 0;
38+
while (i < envVarValueLength) {
39+
while (i < envVarValueLength && isspace(envVarValue.charAt(i))) {
40+
i++;
41+
}
42+
43+
// Trailing space
44+
if (i >= envVarValueLength) {
45+
break;
46+
}
47+
48+
char currentChar;
49+
StringBuilder argChars = new StringBuilder();
50+
while (i < envVarValueLength && !isspace(currentChar = envVarValue.charAt(i))) {
51+
if (currentChar == '"' || currentChar == '\'') {
52+
char quote = currentChar;
53+
i++;
54+
while (i < envVarValueLength && envVarValue.charAt(i) != quote) {
55+
argChars.append(envVarValue.charAt(i++));
56+
}
57+
if (i >= envVarValueLength) {
58+
throw errorFunction.apply("Unmatched quote in environment variable " + envVarName);
59+
}
60+
i++;
61+
} else {
62+
argChars.append(envVarValue.charAt(i++));
63+
}
64+
}
65+
String argument = argChars.toString();
66+
// This port is more restrictive as it forbids arg files to be passed via an env var
67+
boolean isArgFileOption = argument.startsWith("@") && !argument.startsWith("@@");
68+
if (isArgFileOption || isTerminalOpt(argument)) {
69+
throw errorFunction.apply("Option '" + argument + "' is not allowed in environment variable " + envVarName);
70+
} else if (!isExpectingNoDashArg(argument, result)) {
71+
throw errorFunction.apply("Cannot specify main class in environment variable " + envVarName);
72+
}
73+
result.add(argument);
74+
assert i >= envVarValueLength || isspace(envVarValue.charAt(i));
75+
}
76+
return result;
77+
}
78+
79+
private static boolean isExpectingNoDashArg(String argument, List<String> previousArgs) {
80+
if (argument.startsWith("-")) {
81+
return true; // Ignore dash args
82+
}
83+
if (previousArgs.isEmpty()) {
84+
return false; // No previous arg means the no-dash arg is unexpected
85+
}
86+
String previousArg = previousArgs.getLast();
87+
// Derivation from port: unpack any flags for JVM running the image generator
88+
previousArg = previousArg.startsWith("-J") ? previousArg.substring(2) : previousArg;
89+
boolean expectingNoDashArg = isWhiteSpaceOption(previousArg);
90+
if ("-jar".equals(previousArg) || "--module".equals(previousArg) || "-m".equals(previousArg)) {
91+
expectingNoDashArg = false;
92+
}
93+
return expectingNoDashArg;
94+
}
95+
96+
public static boolean isspace(char value) {
97+
// \v not supported in Java
98+
return value == ' ' || value == '\f' || value == '\n' || value == '\r' || value == '\t';
99+
}
100+
101+
private static boolean isTerminalOpt(String arg) {
102+
return switch (arg) {
103+
/* JDK terminal options supported by SVM */
104+
case "-jar", "-m", "--module", "--dry-run", "--help", "--help-extra", "--version" -> true;
105+
/* JDK terminal options not (yet) supported by SVM */
106+
case "-h", "-?", "-help", "-X", "-version", "-fullversion", "--full-version" -> true;
107+
/* SVM-only terminal options */
108+
case "--expert-options", "--expert-options-all", "--expert-options-detail" -> true;
109+
default -> arg.startsWith("--module=");
110+
};
111+
}
112+
113+
private static boolean isWhiteSpaceOption(String name) {
114+
return isModuleOption(name) || isLauncherOption(name);
115+
}
116+
117+
private static boolean isModuleOption(String name) {
118+
return switch (name) {
119+
case "--module-path", "-p", "--upgrade-module-path", "--add-modules", "--enable-native-access", "--limit-modules", "--add-exports", "--add-opens", "--add-reads", "--patch-module" -> true;
120+
default -> false;
121+
};
122+
}
123+
124+
private static boolean isLauncherOption(String name) {
125+
return isClassPathOption(name) ||
126+
isLauncherMainOption(name) ||
127+
"--describe-module".equals(name) ||
128+
"-d".equals(name) ||
129+
"--source".equals(name);
130+
}
131+
132+
private static boolean isClassPathOption(String name) {
133+
return "-classpath".equals(name) ||
134+
"-cp".equals(name) ||
135+
"--class-path".equals(name);
136+
}
137+
138+
private static boolean isLauncherMainOption(String name) {
139+
return "--module".equals(name) || "-m".equals(name);
140+
}
141+
}

0 commit comments

Comments
 (0)