Skip to content

Commit 1e40cb7

Browse files
author
Aleksandar Gradinac
committed
[GR-28898] Generate conditional configuration using the native-image-configure tool.
PullRequest: graal/11362
2 parents dc6d58f + 2168e31 commit 1e40cb7

File tree

39 files changed

+958
-488
lines changed

39 files changed

+958
-488
lines changed

substratevm/mx.substratevm/mx_substratevm.py

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -495,9 +495,6 @@ def native_unittests_task(extra_build_args=None):
495495

496496
def conditional_config_task(native_image):
497497
agent_path = build_native_image_agent(native_image)
498-
config_dir = join(svmbuild_dir(), 'cond-config-test-config')
499-
if exists(config_dir):
500-
mx.rmtree(config_dir)
501498
conditional_config_filter_path = join(svmbuild_dir(), 'conditional-config-filter.json')
502499
with open(conditional_config_filter_path, 'w') as conditional_config_filter:
503500
conditional_config_filter.write(
@@ -509,11 +506,56 @@ def conditional_config_task(native_image):
509506
}
510507
'''
511508
)
512-
agent_opts = ['config-output-dir=' + config_dir, 'experimental-conditional-config-filter-file=' + conditional_config_filter_path]
509+
510+
run_agent_conditional_config_test(agent_path, conditional_config_filter_path)
511+
512+
run_nic_conditional_config_test(agent_path, conditional_config_filter_path)
513+
514+
515+
def run_nic_conditional_config_test(agent_path, conditional_config_filter_path):
516+
test_cases = [
517+
"createConfigPartOne",
518+
"createConfigPartTwo",
519+
"createConfigPartThree"
520+
]
521+
config_directories = []
522+
nic_test_dir = join(svmbuild_dir(), 'nic-cond-config-test')
523+
if exists(nic_test_dir):
524+
mx.rmtree(nic_test_dir)
525+
for test_case in test_cases:
526+
config_dir = join(nic_test_dir, test_case)
527+
config_directories.append(config_dir)
528+
529+
agent_opts = ['config-output-dir=' + config_dir,
530+
'experimental-conditional-config-part']
531+
jvm_unittest(['-agentpath:' + agent_path + '=' + ','.join(agent_opts),
532+
'-Dcom.oracle.svm.configure.test.conditionalconfig.PartialConfigurationGenerator.enabled=true',
533+
'com.oracle.svm.configure.test.conditionalconfig.PartialConfigurationGenerator#' + test_case])
534+
config_output_dir = join(nic_test_dir, 'config-output')
535+
nic_exe = mx.cmd_suffix(join(mx.JDKConfig(home=mx_sdk_vm_impl.graalvm_output()).home, 'bin', 'native-image-configure'))
536+
nic_command = [nic_exe, 'create-conditional'] \
537+
+ ['--user-code-filter=' + conditional_config_filter_path] \
538+
+ ['--input-dir=' + config_dir for config_dir in config_directories] \
539+
+ ['--output-dir=' + config_output_dir]
540+
mx.run(nic_command)
541+
jvm_unittest(
542+
['-Dcom.oracle.svm.configure.test.conditionalconfig.ConfigurationVerifier.configpath=' + config_output_dir,
543+
"-Dcom.oracle.svm.configure.test.conditionalconfig.ConfigurationVerifier.enabled=true",
544+
'com.oracle.svm.configure.test.conditionalconfig.ConfigurationVerifier'])
545+
546+
547+
def run_agent_conditional_config_test(agent_path, conditional_config_filter_path):
548+
config_dir = join(svmbuild_dir(), 'cond-config-test-config')
549+
if exists(config_dir):
550+
mx.rmtree(config_dir)
551+
552+
agent_opts = ['config-output-dir=' + config_dir,
553+
'experimental-conditional-config-filter-file=' + conditional_config_filter_path]
554+
# This run generates the configuration from different test cases
513555
jvm_unittest(['-agentpath:' + agent_path + '=' + ','.join(agent_opts),
514556
'-Dcom.oracle.svm.configure.test.conditionalconfig.ConfigurationGenerator.enabled=true',
515557
'com.oracle.svm.configure.test.conditionalconfig.ConfigurationGenerator'])
516-
558+
# This run verifies that the generated configuration matches the expected one
517559
jvm_unittest(['-Dcom.oracle.svm.configure.test.conditionalconfig.ConfigurationVerifier.configpath=' + config_dir,
518560
"-Dcom.oracle.svm.configure.test.conditionalconfig.ConfigurationVerifier.enabled=true",
519561
'com.oracle.svm.configure.test.conditionalconfig.ConfigurationVerifier'])

substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java

Lines changed: 69 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
import java.util.function.Supplier;
5151
import java.util.regex.Pattern;
5252

53+
import com.oracle.svm.agent.conditionalconfig.ConditionalConfigurationPartialRunWriter;
54+
import com.oracle.svm.agent.configwithorigins.ConfigurationWithOriginsTracer;
5355
import org.graalvm.nativeimage.Platform;
5456
import org.graalvm.nativeimage.ProcessProperties;
5557
import org.graalvm.nativeimage.hosted.Feature;
@@ -65,7 +67,7 @@
6567
import com.oracle.svm.agent.tracing.TraceFileWriter;
6668
import com.oracle.svm.agent.tracing.core.Tracer;
6769
import com.oracle.svm.agent.tracing.core.TracingResultWriter;
68-
import com.oracle.svm.configure.config.ConditionalConfigurationPredicate;
70+
import com.oracle.svm.configure.config.conditional.ConditionalConfigurationPredicate;
6971
import com.oracle.svm.configure.config.ConfigurationFileCollection;
7072
import com.oracle.svm.configure.config.ConfigurationSet;
7173
import com.oracle.svm.configure.filters.ComplexFilter;
@@ -105,6 +107,18 @@ private static String getTokenValue(String token) {
105107
return token.substring(token.indexOf('=') + 1);
106108
}
107109

110+
private static boolean getBooleanTokenValue(String token) {
111+
int equalsIndex = token.indexOf('=');
112+
if (equalsIndex == -1) {
113+
return true;
114+
}
115+
return Boolean.parseBoolean(token.substring(equalsIndex + 1));
116+
}
117+
118+
private static boolean isBooleanOption(String token, String option) {
119+
return token.equals(option) || token.startsWith(option + "=");
120+
}
121+
108122
@Override
109123
protected int getRequiredJvmtiVersion() {
110124
return JvmtiInterface.JVMTI_VERSION_1_2;
@@ -133,6 +147,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
133147
boolean configurationWithOrigins = false;
134148
List<String> conditionalConfigUserPackageFilterFiles = new ArrayList<>();
135149
List<String> conditionalConfigClassNameFilterFiles = new ArrayList<>();
150+
boolean conditionalConfigPartialRun = false;
136151
int configWritePeriod = -1; // in seconds
137152
int configWritePeriodInitialDelay = 1; // in seconds
138153
boolean trackReflectionMetadata = true;
@@ -156,42 +171,31 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
156171
String omittedConfigDir = getTokenValue(token);
157172
omittedConfigDir = transformPath(omittedConfigDir);
158173
omittedConfigs.addDirectory(Paths.get(omittedConfigDir));
159-
} else if (token.equals("experimental-omit-config-from-classpath")) {
160-
experimentalOmitClasspathConfig = true;
161-
} else if (token.startsWith("experimental-omit-config-from-classpath=")) {
162-
experimentalOmitClasspathConfig = Boolean.parseBoolean(getTokenValue(token));
174+
} else if (isBooleanOption(token, "experimental-omit-config-from-classpath")) {
175+
experimentalOmitClasspathConfig = getBooleanTokenValue(token);
163176
} else if (token.startsWith("restrict-all-dir") || token.equals("restrict") || token.startsWith("restrict=")) {
164177
warn("restrict mode is no longer supported, ignoring option: " + token);
165178
} else if (token.equals("no-builtin-caller-filter")) {
166179
builtinCallerFilter = false;
167-
} else if (token.startsWith("builtin-caller-filter=")) {
168-
builtinCallerFilter = Boolean.parseBoolean(getTokenValue(token));
180+
} else if (isBooleanOption(token, "builtin-caller-filter")) {
181+
builtinCallerFilter = getBooleanTokenValue(token);
169182
} else if (token.equals("no-builtin-heuristic-filter")) {
170183
builtinHeuristicFilter = false;
171-
} else if (token.startsWith("builtin-heuristic-filter=")) {
172-
builtinHeuristicFilter = Boolean.parseBoolean(getTokenValue(token));
173-
} else if (token.equals("no-filter")) { // legacy
174-
builtinCallerFilter = false;
175-
builtinHeuristicFilter = false;
176-
} else if (token.startsWith("no-filter=")) { // legacy
177-
builtinCallerFilter = !Boolean.parseBoolean(getTokenValue(token));
184+
} else if (isBooleanOption(token, "builtin-heuristic-filter")) {
185+
builtinHeuristicFilter = getBooleanTokenValue(token);
186+
} else if (isBooleanOption(token, "no-filter")) { // legacy
187+
builtinCallerFilter = !getBooleanTokenValue(token);
178188
builtinHeuristicFilter = builtinCallerFilter;
179189
} else if (token.startsWith("caller-filter-file=")) {
180190
callerFilterFiles.add(getTokenValue(token));
181191
} else if (token.startsWith("access-filter-file=")) {
182192
accessFilterFiles.add(getTokenValue(token));
183-
} else if (token.equals("experimental-class-loader-support")) {
184-
experimentalClassLoaderSupport = true;
185-
} else if (token.startsWith("experimental-class-loader-support=")) {
186-
experimentalClassLoaderSupport = Boolean.parseBoolean(getTokenValue(token));
187-
} else if (token.equals("experimental-class-define-support")) {
188-
experimentalClassDefineSupport = true;
189-
} else if (token.startsWith("experimental-class-define-support=")) {
190-
experimentalClassDefineSupport = Boolean.parseBoolean(getTokenValue(token));
191-
} else if (token.equals("experimental-unsafe-allocation-support")) {
192-
experimentalUnsafeAllocationSupport = Boolean.parseBoolean(getTokenValue(token));
193-
} else if (token.startsWith("experimental-unsafe-allocation-support=")) {
194-
experimentalUnsafeAllocationSupport = Boolean.parseBoolean(getTokenValue(token));
193+
} else if (isBooleanOption(token, "experimental-class-loader-support")) {
194+
experimentalClassLoaderSupport = getBooleanTokenValue(token);
195+
} else if (isBooleanOption(token, "experimental-class-define-support")) {
196+
experimentalClassDefineSupport = getBooleanTokenValue(token);
197+
} else if (isBooleanOption(token, "experimental-unsafe-allocation-support")) {
198+
experimentalUnsafeAllocationSupport = getBooleanTokenValue(token);
195199
} else if (token.startsWith("config-write-period-secs=")) {
196200
configWritePeriod = parseIntegerOrNegative(getTokenValue(token));
197201
if (configWritePeriod <= 0) {
@@ -202,20 +206,18 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
202206
if (configWritePeriodInitialDelay < 0) {
203207
return usage(1, "config-write-initial-delay-secs must be an integer greater or equal to 0");
204208
}
205-
} else if (token.equals("build")) {
206-
build = true;
207-
} else if (token.startsWith("build=")) {
208-
build = Boolean.parseBoolean(getTokenValue(token));
209-
} else if (token.equals("experimental-configuration-with-origins")) {
210-
configurationWithOrigins = true;
209+
} else if (isBooleanOption(token, "build")) {
210+
build = getBooleanTokenValue(token);
211+
} else if (isBooleanOption(token, "experimental-configuration-with-origins")) {
212+
configurationWithOrigins = getBooleanTokenValue(token);
211213
} else if (token.startsWith("experimental-conditional-config-filter-file=")) {
212214
conditionalConfigUserPackageFilterFiles.add(getTokenValue(token));
213215
} else if (token.startsWith("conditional-config-class-filter-file=")) {
214216
conditionalConfigClassNameFilterFiles.add(getTokenValue(token));
215-
} else if (token.equals("track-reflection-metadata")) {
216-
trackReflectionMetadata = true;
217-
} else if (token.startsWith("track-reflection-metadata=")) {
218-
trackReflectionMetadata = Boolean.parseBoolean(getTokenValue(token));
217+
} else if (isBooleanOption(token, "experimental-conditional-config-part")) {
218+
conditionalConfigPartialRun = getBooleanTokenValue(token);
219+
} else if (isBooleanOption(token, "track-reflection-metadata")) {
220+
trackReflectionMetadata = getBooleanTokenValue(token);
219221
} else {
220222
return usage(1, "unknown option: '" + token + "'.");
221223
}
@@ -264,7 +266,12 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
264266
}
265267
}
266268

267-
boolean shouldTraceOriginInformation = configurationWithOrigins || !conditionalConfigUserPackageFilterFiles.isEmpty();
269+
if (!conditionalConfigUserPackageFilterFiles.isEmpty() && conditionalConfigPartialRun) {
270+
return error(6, "The agent can generate conditional configuration either for the current run or in the partial mode but not both at the same time.");
271+
}
272+
273+
boolean isConditionalConfigurationRun = !conditionalConfigUserPackageFilterFiles.isEmpty() || conditionalConfigPartialRun;
274+
boolean shouldTraceOriginInformation = configurationWithOrigins || isConditionalConfigurationRun;
268275
final MethodInfoRecordKeeper recordKeeper = new MethodInfoRecordKeeper(shouldTraceOriginInformation);
269276
final Supplier<InterceptedState> interceptedStateSupplier = shouldTraceOriginInformation ? EagerlyLoadedJavaStackAccess.stackAccessSupplier()
270277
: OnDemandJavaStackAccess.stackAccessSupplier();
@@ -309,29 +316,34 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
309316
shouldExcludeClassesWithHash = omittedConfiguration.getPredefinedClassesConfiguration()::containsClassWithHash;
310317
}
311318

312-
if (configurationWithOrigins) {
313-
ConfigurationWithOriginsWriter writer = new ConfigurationWithOriginsWriter(processor, recordKeeper);
314-
tracer = writer;
315-
tracingResultWriter = writer;
316-
} else if (!conditionalConfigUserPackageFilterFiles.isEmpty()) {
317-
ComplexFilter userCodeFilter = new ComplexFilter(HierarchyFilterNode.createRoot());
318-
if (!parseFilterFiles(userCodeFilter, conditionalConfigUserPackageFilterFiles)) {
319-
return 2;
320-
}
321-
ComplexFilter classNameFilter;
322-
if (!conditionalConfigClassNameFilterFiles.isEmpty()) {
323-
classNameFilter = new ComplexFilter(HierarchyFilterNode.createRoot());
324-
if (!parseFilterFiles(classNameFilter, conditionalConfigClassNameFilterFiles)) {
325-
return 3;
319+
if (shouldTraceOriginInformation) {
320+
ConfigurationWithOriginsTracer configWithOriginsTracer = new ConfigurationWithOriginsTracer(processor, recordKeeper);
321+
tracer = configWithOriginsTracer;
322+
323+
if (isConditionalConfigurationRun) {
324+
if (conditionalConfigPartialRun) {
325+
tracingResultWriter = new ConditionalConfigurationPartialRunWriter(configWithOriginsTracer);
326+
} else {
327+
ComplexFilter userCodeFilter = new ComplexFilter(HierarchyFilterNode.createRoot());
328+
if (!parseFilterFiles(userCodeFilter, conditionalConfigUserPackageFilterFiles)) {
329+
return 2;
330+
}
331+
ComplexFilter classNameFilter;
332+
if (!conditionalConfigClassNameFilterFiles.isEmpty()) {
333+
classNameFilter = new ComplexFilter(HierarchyFilterNode.createRoot());
334+
if (!parseFilterFiles(classNameFilter, conditionalConfigClassNameFilterFiles)) {
335+
return 3;
336+
}
337+
} else {
338+
classNameFilter = new ComplexFilter(HierarchyFilterNode.createInclusiveRoot());
339+
}
340+
341+
ConditionalConfigurationPredicate predicate = new ConditionalConfigurationPredicate(classNameFilter);
342+
tracingResultWriter = new ConditionalConfigurationWriter(configWithOriginsTracer, userCodeFilter, predicate);
326343
}
327344
} else {
328-
classNameFilter = new ComplexFilter(HierarchyFilterNode.createInclusiveRoot());
345+
tracingResultWriter = new ConfigurationWithOriginsWriter(configWithOriginsTracer);
329346
}
330-
331-
ConditionalConfigurationPredicate predicate = new ConditionalConfigurationPredicate(classNameFilter);
332-
ConditionalConfigurationWriter writer = new ConditionalConfigurationWriter(processor, recordKeeper, userCodeFilter, predicate);
333-
tracer = writer;
334-
tracingResultWriter = writer;
335347
} else {
336348
Path[] predefinedClassDestDirs = {Files.createDirectories(configOutputDirPath.resolve(ConfigurationFile.PREDEFINED_CLASSES_AGENT_EXTRACTED_SUBDIR))};
337349
Function<IOException, Exception> handler = e -> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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.agent.conditionalconfig;
26+
27+
import java.io.IOException;
28+
import java.nio.file.Path;
29+
import java.util.Collections;
30+
import java.util.List;
31+
32+
import com.oracle.svm.agent.configwithorigins.ConfigurationWithOriginsTracer;
33+
import com.oracle.svm.agent.tracing.core.TracingResultWriter;
34+
import com.oracle.svm.configure.config.conditional.PartialConfigurationWithOrigins;
35+
import com.oracle.svm.configure.json.JsonWriter;
36+
import com.oracle.svm.core.configure.ConfigurationFile;
37+
38+
public class ConditionalConfigurationPartialRunWriter implements TracingResultWriter {
39+
40+
private final ConfigurationWithOriginsTracer tracer;
41+
42+
public ConditionalConfigurationPartialRunWriter(ConfigurationWithOriginsTracer tracer) {
43+
this.tracer = tracer;
44+
}
45+
46+
@Override
47+
public boolean supportsPeriodicTraceWriting() {
48+
return false;
49+
}
50+
51+
@Override
52+
public boolean supportsOnUnloadTraceWriting() {
53+
return true;
54+
}
55+
56+
@Override
57+
public List<Path> writeToDirectory(Path directoryPath) throws IOException {
58+
Path resolvedPath = directoryPath.resolve(ConfigurationFile.PARTIAL_CONFIGURATION_WITH_ORIGINS);
59+
try (JsonWriter writer = new JsonWriter(resolvedPath)) {
60+
new PartialConfigurationWithOrigins(tracer.getRootNode(), null).printJson(writer);
61+
}
62+
return Collections.singletonList(resolvedPath);
63+
}
64+
}

0 commit comments

Comments
 (0)