diff --git a/java-benchmarks/mx.java-benchmarks/mx_java_benchmarks.py b/java-benchmarks/mx.java-benchmarks/mx_java_benchmarks.py index 3aa455642d0b..e3ddee80bb3e 100644 --- a/java-benchmarks/mx.java-benchmarks/mx_java_benchmarks.py +++ b/java-benchmarks/mx.java-benchmarks/mx_java_benchmarks.py @@ -541,11 +541,11 @@ def extra_image_build_argument(self, benchmark, args): '-J--add-exports=org.graalvm.nativeimage/org.graalvm.nativeimage.impl=ALL-UNNAMED', '-J--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk=ALL-UNNAMED', '--exclude-config' , - 'io\.netty\.netty-codec', - '/META-INF/native-image/io\.netty/netty-codec/generated/handlers/reflect-config\.json', + 'io\\.netty\\.netty-codec', + '/META-INF/native-image/io\\.netty/netty-codec/generated/handlers/reflect-config\\.json', '--exclude-config', - 'io\.netty\.netty-handler', - '/META-INF/native-image/io\.netty/netty-handler/generated/handlers/reflect-config\.json', + 'io\\.netty\\.netty-handler', + '/META-INF/native-image/io\\.netty/netty-handler/generated/handlers/reflect-config\\.json', '-H:-AddAllCharsets', '-H:+ReportExceptionStackTraces', ] + mx_sdk_vm_impl.svm_experimental_options([ diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index a925c670defb..e7a6a40047b0 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -6,6 +6,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-48304) Red Hat added support for the JFR event ThreadAllocationStatistics. * (GR-48343) Red Hat added support for the JFR events AllocationRequiringGC and SystemGC. * (GR-48612) Enable `--strict-image-heap` by default. The option is now deprecated and can be removed from your argument list. A blog post with more information will follow shortly. +* (GR-48354) Remove native-image-agent legacy `build`-option ## 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/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index b9154c059a97..53efb8162b1b 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -23,8 +23,6 @@ # questions. # -from __future__ import print_function - import os import re import tempfile @@ -35,6 +33,7 @@ from argparse import ArgumentParser import fnmatch import collections +from io import StringIO import mx import mx_compiler @@ -53,10 +52,6 @@ import sys -if sys.version_info[0] < 3: - from StringIO import StringIO -else: - from io import StringIO suite = mx.suite('substratevm') svmSuites = [suite] @@ -221,6 +216,27 @@ def vm_executable_path(executable, config=None): return join(_vm_home(config), 'bin', executable) +def _escape_for_args_file(arg): + if not (arg.startswith('\\Q') and arg.endswith('\\E')): + arg = arg.replace('\\', '\\\\') + if ' ' in arg: + arg = '\"' + arg + '\"' + return arg + + +def _maybe_convert_to_args_file(args): + total_command_line_args_length = sum([len(arg) for arg in args]) + if total_command_line_args_length < 80: + # Do not use argument file when total command line length is reasonable, + # so that both code paths are exercised on all platforms + return args + else: + # Use argument file to avoid exceeding the command line length limit on Windows + with tempfile.NamedTemporaryFile(delete=False, mode='w', prefix='ni_args_', suffix='.args') as args_file: + args_file.write('\n'.join([_escape_for_args_file(a) for a in args])) + return ['@' + args_file.name] + + @contextmanager def native_image_context(common_args=None, hosted_assertions=True, native_image_cmd='', config=None, build_if_missing=False): common_args = [] if common_args is None else common_args @@ -251,7 +267,7 @@ def native_image_context(common_args=None, hosted_assertions=True, native_image_ raise mx.abort('The built GraalVM for config ' + str(config) + ' does not contain a native-image command') def _native_image(args, **kwargs): - mx.run([native_image_cmd] + args, **kwargs) + mx.run([native_image_cmd] + _maybe_convert_to_args_file(args), **kwargs) def is_launcher(launcher_path): with open(launcher_path, 'rb') as fp: @@ -1013,7 +1029,10 @@ def _native_image_launcher_extra_jvm_args(): '--features=com.oracle.svm.driver.APIOptionFeature', '--initialize-at-build-time=com.oracle.svm.driver', '--link-at-build-time=com.oracle.svm.driver,com.oracle.svm.driver.metainf', -] + svm_experimental_options([ +] + +driver_exe_build_args = driver_build_args + svm_experimental_options([ + '-H:+AllowJRTFileSystem', '-H:IncludeResources=com/oracle/svm/driver/launcher/.*', '-H:-ParseRuntimeOptions', f'-R:MaxHeapSize={256 * 1024 * 1024}', @@ -1055,7 +1074,7 @@ def _native_image_launcher_extra_jvm_args(): destination="bin/", jar_distributions=["substratevm:SVM_DRIVER"], main_class=_native_image_launcher_main_class(), - build_args=driver_build_args, + build_args=driver_exe_build_args, extra_jvm_args=_native_image_launcher_extra_jvm_args(), home_finder=False, ), diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index c2bd3225190b..98b8deb1e711 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -853,6 +853,11 @@ "requires" : [ "jdk.management", ], + "requiresConcealed" : { + "java.base" : [ + "jdk.internal.jimage", + ], + }, "checkstyle": "com.oracle.svm.hosted", "workingSets": "SVM", "annotationProcessors": [ diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java index 19bbbb5e4d89..1de088372e64 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java @@ -38,7 +38,6 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Arrays; import java.util.ConcurrentModificationException; import java.util.Date; import java.util.List; @@ -76,12 +75,10 @@ import com.oracle.svm.configure.filters.HierarchyFilterNode; import com.oracle.svm.configure.trace.AccessAdvisor; import com.oracle.svm.configure.trace.TraceProcessor; -import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.jni.headers.JNIEnvironment; import com.oracle.svm.core.jni.headers.JNIJavaVM; import com.oracle.svm.core.jni.headers.JNIObjectHandle; -import com.oracle.svm.driver.NativeImage; import com.oracle.svm.driver.metainf.NativeImageMetaInfWalker; import com.oracle.svm.jvmtiagentbase.JNIHandleSet; import com.oracle.svm.jvmtiagentbase.JvmtiAgentBase; @@ -143,7 +140,6 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c boolean experimentalClassDefineSupport = false; boolean experimentalUnsafeAllocationSupport = false; boolean experimentalOmitClasspathConfig = false; - boolean build = false; boolean configurationWithOrigins = false; List conditionalConfigUserPackageFilterFiles = new ArrayList<>(); List conditionalConfigClassNameFilterFiles = new ArrayList<>(); @@ -206,8 +202,6 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c if (configWritePeriodInitialDelay < 0) { return usage(1, "config-write-initial-delay-secs must be an integer greater or equal to 0"); } - } else if (isBooleanOption(token, "build")) { - build = getBooleanTokenValue(token); } else if (isBooleanOption(token, "experimental-configuration-with-origins")) { configurationWithOrigins = getBooleanTokenValue(token); } else if (token.startsWith("experimental-conditional-config-filter-file=")) { @@ -223,9 +217,9 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c } } - if (traceOutputFile == null && configOutputDir == null && !build) { + if (traceOutputFile == null && configOutputDir == null) { configOutputDir = transformPath(AGENT_NAME + "_config-pid{pid}-{datetime}/"); - inform("no output/build options provided, tracking dynamic accesses and writing configuration to directory: " + configOutputDir); + inform("no output options provided, tracking dynamic accesses and writing configuration to directory: " + configOutputDir); } if (configurationWithOrigins && !conditionalConfigUserPackageFilterFiles.isEmpty()) { @@ -377,14 +371,6 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c } } - if (build) { - int status = buildImage(jvmti); - if (status == 0) { - System.exit(status); - } - return status; - } - try { BreakpointInterceptor.onLoad(jvmti, callbacks, tracer, this, interceptedStateSupplier, experimentalClassLoaderSupport, experimentalClassDefineSupport, experimentalUnsafeAllocationSupport, trackReflectionMetadata); @@ -504,46 +490,6 @@ private static void ignoreConfigFromClasspath(JvmtiEnv jvmti, ConfigurationFileC private static final Pattern propertyBlacklist = Pattern.compile("(java\\..*)|(sun\\..*)|(jvmci\\..*)"); private static final Pattern propertyWhitelist = Pattern.compile("(java\\.library\\.path)|(java\\.io\\.tmpdir)"); - private static int buildImage(JvmtiEnv jvmti) { - System.out.println("Building native image ..."); - String classpath = Support.getSystemProperty(jvmti, "java.class.path"); - if (classpath == null) { - return usage(1, "Build mode could not determine classpath."); - } - String javaCommand = Support.getSystemProperty(jvmti, "sun.java.command"); - String mainClassMissing = "Build mode could not determine main class."; - if (javaCommand == null) { - return usage(1, mainClassMissing); - } - String mainClass = SubstrateUtil.split(javaCommand, " ")[0]; - if (mainClass.isEmpty()) { - return usage(1, mainClassMissing); - } - List buildArgs = new ArrayList<>(); - // buildArgs.add("--verbose"); - String[] keys = Support.getSystemProperties(jvmti); - for (String key : keys) { - boolean whitelisted = propertyWhitelist.matcher(key).matches(); - boolean blacklisted = !whitelisted && propertyBlacklist.matcher(key).matches(); - if (blacklisted) { - continue; - } - buildArgs.add("-D" + key + "=" + Support.getSystemProperty(jvmti, key)); - } - if (mainClass.toLowerCase().endsWith(".jar")) { - buildArgs.add("-jar"); - } else { - buildArgs.addAll(Arrays.asList("-cp", classpath)); - } - buildArgs.add(mainClass); - buildArgs.add(AGENT_NAME + ".build"); - // System.out.println(String.join("\n", buildArgs)); - Path javaHome = Paths.get(Support.getSystemProperty(jvmti, "java.home")); - String userDirStr = Support.getSystemProperty(jvmti, "user.dir"); - NativeImage.agentBuild(javaHome, userDirStr == null ? null : Paths.get(userDirStr), buildArgs); - return 0; - } - private static String transformPath(String path) { String result = path; if (result.contains("{pid}")) { 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 0b70065820fa..d4ad37ad9fc3 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 @@ -153,13 +153,10 @@ protected void onValueUpdate(EconomicMap, Object> values, String ol }; @Option(help = "Builds a statically linked executable with libc dynamically linked", type = Expert, stability = OptionStability.EXPERIMENTAL)// - public static final HostedOptionKey StaticExecutableWithDynamicLibC = new HostedOptionKey<>(false) { - @Override - protected void onValueUpdate(EconomicMap, Object> values, Boolean oldValue, Boolean newValue) { - StaticExecutable.update(values, true); - super.onValueUpdate(values, oldValue, newValue); - } - }; + public static final HostedOptionKey StaticExecutableWithDynamicLibC = new HostedOptionKey<>(false); + + @Option(help = "Builds image with libstdc++ statically linked into the image (if needed)", type = Expert, stability = OptionStability.EXPERIMENTAL)// + public static final HostedOptionKey StaticLibStdCpp = new HostedOptionKey<>(false); public static final int ForceFallback = 10; public static final int Automatic = 5; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JRTSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JRTSupport.java index 795cd42e1cd9..a17bbd2e0abd 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JRTSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JRTSupport.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.net.URL; import java.net.URLConnection; +import java.nio.ByteBuffer; import java.nio.file.Path; import java.util.Arrays; import java.util.HashMap; @@ -115,7 +116,7 @@ final class Target_jdk_internal_module_SystemModuleFinders_SystemImage_JRTEnable static volatile Target_jdk_internal_jimage_ImageReader_JRTEnabled READER; @Substitute - static Object reader() { + static Target_jdk_internal_jimage_ImageReader_JRTEnabled reader() { Target_jdk_internal_jimage_ImageReader_JRTEnabled localRef = READER; if (localRef == null) { synchronized (Target_jdk_internal_module_SystemModuleFinders_SystemImage_JRTEnabled.class) { @@ -178,3 +179,22 @@ final class Target_jdk_internal_jrtfs_JrtFileSystemProvider_JRTDisabled { } // endregion Disable jimage/jrtfs + +@TargetClass(className = "jdk.internal.jimage.BasicImageReader") +final class Target_jdk_internal_jimage_BasicImageReader { + /* Ensure NativeImageBuffer never gets used as part of using BasicImageReader */ + @Alias // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias, isFinal = true) // + // Checkstyle: stop + static boolean USE_JVM_MAP = false; + // Checkstyle: resume +} + +@TargetClass(className = "jdk.internal.jimage.NativeImageBuffer") +@Substitute +final class Target_jdk_internal_jimage_NativeImageBuffer { + @Substitute + static ByteBuffer getNativeMap(String imagePath) { + throw VMError.unsupportedFeature("Using jdk.internal.jimage.NativeImageBuffer is not supported"); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java index 9bbd4a564846..482a211194ef 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java @@ -30,6 +30,8 @@ import static org.graalvm.compiler.nodes.extended.BranchProbabilityNode.NOT_FREQUENT_PROBABILITY; import static org.graalvm.compiler.nodes.extended.BranchProbabilityNode.probability; +import java.util.concurrent.locks.ReentrantLock; + import org.graalvm.nativeimage.IsolateThread; import com.oracle.svm.core.Uninterruptible; @@ -37,6 +39,7 @@ import com.oracle.svm.core.jfr.SubstrateJVM; import com.oracle.svm.core.jfr.events.JavaMonitorEnterEvent; import com.oracle.svm.core.thread.JavaThreads; +import com.oracle.svm.core.util.BasedOnJDKClass; import com.oracle.svm.core.util.VMError; import jdk.internal.misc.Unsafe; @@ -55,6 +58,8 @@ * to store the number of lock acquisitions, enabling various optimizations. * */ +@BasedOnJDKClass(ReentrantLock.class) +@BasedOnJDKClass(value = ReentrantLock.class, innerClass = "Sync") public class JavaMonitor extends JavaMonitorQueuedSynchronizer { protected long latestJfrTid; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/BasedOnJDKClass.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/BasedOnJDKClass.java new file mode 100644 index 000000000000..b3989f3ac584 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/BasedOnJDKClass.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023, 2023, 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.core.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.function.Function; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.annotate.TargetClass; + +/** + * Documents that the class is based on a JDK class without {@linkplain TargetClass substituting} + * it. + */ +@Repeatable(BasedOnJDKClass.List.class) +@Retention(RetentionPolicy.RUNTIME) +@Target(value = {ElementType.TYPE}) +@Platforms(Platform.HOSTED_ONLY.class) +public @interface BasedOnJDKClass { + + /** + * @see TargetClass#value() + */ + Class value() default BasedOnJDKClass.class; + + /** + * @see TargetClass#className() + */ + String className() default ""; + + /** + * @see TargetClass#classNameProvider() + */ + Class> classNameProvider() default BasedOnJDKClass.NoClassNameProvider.class; + + /** + * @see TargetClass#innerClass() + */ + String[] innerClass() default {}; + + interface NoClassNameProvider extends Function { + } + + /** + * Support for making {@link BasedOnJDKClass} {@linkplain Repeatable repeatable}. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(value = {ElementType.TYPE}) + @Platforms(Platform.HOSTED_ONLY.class) + @interface List { + BasedOnJDKClass[] value(); + } +} 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 cfa4f4f15caa..98407ad3f4d0 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 @@ -53,7 +53,6 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.Properties; import java.util.Set; @@ -62,7 +61,6 @@ import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.Predicate; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; @@ -106,6 +104,8 @@ import com.oracle.svm.util.ReflectionUtil; import com.oracle.svm.util.StringUtil; +import jdk.internal.jimage.ImageReader; + public class NativeImage { private static final String DEFAULT_GENERATOR_CLASS_NAME = NativeImageGeneratorRunner.class.getName(); @@ -1534,8 +1534,6 @@ protected int buildImage(List javaArgs, LinkedHashSet cp, LinkedHa arguments.addAll(strings); } - String javaExecutable = canonicalize(config.getJavaExecutable()).toString(); - if (useBundle()) { LogUtils.warning("Native Image Bundles are an experimental feature."); } @@ -1548,20 +1546,32 @@ protected int buildImage(List javaArgs, LinkedHashSet cp, LinkedHa List finalImageClassPath = imagecp.stream().map(substituteClassPath).collect(Collectors.toList()); Function substituteModulePath = useBundle() ? bundleSupport::substituteModulePath : Function.identity(); - List substitutedImageModulePath = imagemp.stream().map(substituteModulePath).toList(); + List imageModulePath = imagemp.stream().map(substituteModulePath).collect(Collectors.toList()); + Map applicationModules = getModulesFromPath(imageModulePath); + + if (!applicationModules.isEmpty()) { + // Remove modules that we already have built-in + applicationModules.keySet().removeAll(getBuiltInModules()); + // Remove modules that we get from the builder + applicationModules.keySet().removeAll(getModulesFromPath(mp).keySet()); + } + List finalImageModulePath = applicationModules.values().stream().toList(); - Map modules = listModulesFromPath(javaExecutable, Stream.concat(mp.stream(), imagemp.stream()).distinct().toList()); if (!addModules.isEmpty()) { arguments.add("-D" + ModuleSupport.PROPERTY_IMAGE_EXPLICITLY_ADDED_MODULES + "=" + String.join(",", addModules)); List addModulesForBuilderVM = new ArrayList<>(); - for (String module : addModules) { - Path jarPath = modules.get(module); - if (jarPath == null) { - // boot module - addModulesForBuilderVM.add(module); + for (String moduleNameInAddModules : addModules) { + if (!applicationModules.containsKey(moduleNameInAddModules)) { + /* + * Module names given to native-image --add-modules that are not referring to + * modules that are passed to native-image via -p/--module-path are considered + * to be part of the module-layer that contains the builder itself. Those module + * names need to be passed as --add-modules arguments to the builder VM. + */ + addModulesForBuilderVM.add(moduleNameInAddModules); } } @@ -1580,43 +1590,20 @@ protected int buildImage(List javaArgs, LinkedHashSet cp, LinkedHa arguments.addAll(Arrays.asList(SubstrateOptions.WATCHPID_PREFIX, "" + ProcessProperties.getProcessID())); } - /* - * Workaround for GR-47186: Native image cannot handle modules on the image module path, - * that are also already installed in the JDK as boot module. As a workaround we filter all - * modules from the module-path that are either already installed in the JDK as boot module, - * or were explicitly added to the builder module-path. - * - * First compute all module-jar paths that are not on the builder module-path. - */ - Set nonBuilderModulePaths = modules.values().stream() - .filter(Objects::nonNull) - .filter(Predicate.not(mp::contains)) - .collect(Collectors.toSet()); - - /* - * Now we need to filter the substituted module path list for all the modules that may - * remain on the module-path. - * - * This should normally not be necessary, as the nonBuilderModulePaths should already be the - * set of jar files for the image module path. Nevertheless, we use the original definition - * of the module path to preserve the order of the original module path and as a precaution - * to protect against --list-modules returning too many modules. - */ - List finalImageModulePath = substitutedImageModulePath.stream() - .filter(nonBuilderModulePaths::contains) - .toList(); - List finalImageBuilderArgs = createImageBuilderArgs(finalImageArgs, finalImageClassPath, finalImageModulePath); /* Construct ProcessBuilder command from final arguments */ List command = new ArrayList<>(); List completeCommandList = new ArrayList<>(); + String javaExecutable; if (useBundle() && bundleSupport.useContainer) { ContainerSupport.replacePaths(arguments, config.getJavaHome(), bundleSupport.rootDir); ContainerSupport.replacePaths(finalImageBuilderArgs, config.getJavaHome(), bundleSupport.rootDir); Path binJava = Paths.get("bin", "java"); javaExecutable = ContainerSupport.GRAAL_VM_HOME.resolve(binJava).toString(); + } else { + javaExecutable = canonicalize(config.getJavaExecutable()).toString(); } Path argFile = createVMInvocationArgumentFile(arguments); @@ -1718,65 +1705,34 @@ protected int buildImage(List javaArgs, LinkedHashSet cp, LinkedHa } } - /** - * Resolves and lists all modules given a module path. - * - * @see #callListModules(String, List) - */ - private Map listModulesFromPath(String javaExecutable, Collection modulePath) { - if (modulePath.isEmpty() || !config.modulePathBuild) { - return Map.of(); + private Set getBuiltInModules() { + Path jdkRoot = config.rootDir; + try { + var reader = ImageReader.open(jdkRoot.resolve("lib/modules")); + return new LinkedHashSet<>(List.of(reader.getModuleNames())); + } catch (IOException e) { + throw showError("Unable to determine builtin modules of JDK in " + jdkRoot, e); } - String modulePathEntries = modulePath.stream() - .map(Path::toString) - .collect(Collectors.joining(File.pathSeparator)); - return callListModules(javaExecutable, List.of("-p", modulePathEntries)); } - /** - * Calls java $arguments --list-modules to list all modules and parse the output. - * The output consists of a map with module name as key and {@link Path} to jar file if the - * module is not installed as part of the JDK. If the module is installed as part of the - * jdk/boot-layer then a null path will be returned. - *

- * This is a much more robust solution then trying to parse the JDK file structure manually. - */ - private static Map callListModules(String javaExecutable, List arguments) { - Process listModulesProcess = null; - Map result = new LinkedHashMap<>(); + private Map getModulesFromPath(Collection modulePath) { + if (!config.modulePathBuild || modulePath.isEmpty()) { + return Map.of(); + } + + LinkedHashMap mrefs = new LinkedHashMap<>(); try { - var pb = new ProcessBuilder(javaExecutable); - pb.command().addAll(arguments); - pb.command().add("--list-modules"); - pb.environment().clear(); - listModulesProcess = pb.start(); - - List lines; - try (var br = new BufferedReader(new InputStreamReader(listModulesProcess.getInputStream()))) { - lines = br.lines().toList(); - } - int exitStatus = listModulesProcess.waitFor(); - if (exitStatus != 0) { - throw showError("Determining image-builder observable modules failed (Exit status %d). Process output: %n%s".formatted(exitStatus, String.join(System.lineSeparator(), lines))); - } - for (String line : lines) { - String[] splitString = StringUtil.split(line, " ", 3); - String[] splitModuleNameAndVersion = StringUtil.split(splitString[0], "@", 2); - Path externalPath = null; - if (splitString.length > 1) { - String pathURI = splitString[1]; // url: file://path/to/file - externalPath = Path.of(URI.create(pathURI)).toAbsolutePath(); - } - result.put(splitModuleNameAndVersion[0], externalPath); - } - } catch (IOException | InterruptedException e) { - throw showError(e.getMessage()); - } finally { - if (listModulesProcess != null) { - listModulesProcess.destroy(); + ModuleFinder finder = ModuleFinder.of(modulePath.toArray(Path[]::new)); + for (ModuleReference mref : finder.findAll()) { + String moduleName = mref.descriptor().name(); + VMError.guarantee(moduleName != null && !moduleName.isEmpty(), "Unnamed module on modulePath"); + URI moduleLocation = mref.location().orElseThrow(() -> VMError.shouldNotReachHere("ModuleReference for module " + moduleName + " has no location.")); + mrefs.put(moduleName, Path.of(moduleLocation)); } + } catch (FindException e) { + throw showError("Failed to collect ModuleReferences for module-path entries " + modulePath, e); } - return result; + return mrefs; } /** @@ -1858,14 +1814,6 @@ public static void main(String[] args) { performBuild(new BuildConfiguration(Arrays.asList(args)), defaultNativeImageProvider); } - public static void build(BuildConfiguration config) { - build(config, defaultNativeImageProvider); - } - - public static void agentBuild(Path javaHome, Path workDir, List buildArgs) { - performBuild(new BuildConfiguration(javaHome, workDir, buildArgs), NativeImage::new); - } - public static List translateAPIOptions(List arguments) { var handler = new APIOptionHandler(new NativeImage(new BuildConfiguration(arguments))); var argumentQueue = new ArgumentQueue(OptionOrigin.originDriver); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/CCLinkerInvocation.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/CCLinkerInvocation.java index af8a0291451c..14de7653ec1b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/CCLinkerInvocation.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/CCLinkerInvocation.java @@ -31,13 +31,11 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import com.oracle.svm.core.BuildDirectoryProvider; import org.graalvm.compiler.options.Option; import org.graalvm.compiler.options.OptionStability; import org.graalvm.nativeimage.ImageSingletons; @@ -45,6 +43,7 @@ import com.oracle.objectfile.ObjectFile; import com.oracle.objectfile.macho.MachOSymtab; +import com.oracle.svm.core.BuildDirectoryProvider; import com.oracle.svm.core.LinkerInvocation; import com.oracle.svm.core.OS; import com.oracle.svm.core.SubstrateOptions; @@ -250,8 +249,9 @@ protected List getNativeLinkerOptions() { private static class BinutilsCCLinkerInvocation extends CCLinkerInvocation { - private final boolean staticExecWithDynamicallyLinkLibC = SubstrateOptions.StaticExecutableWithDynamicLibC.getValue(); - private final Set libCLibaries = new HashSet<>(Arrays.asList("pthread", "dl", "rt", "m")); + private final boolean dynamicLibC = SubstrateOptions.StaticExecutableWithDynamicLibC.getValue(); + private final boolean staticLibCpp = SubstrateOptions.StaticLibStdCpp.getValue(); + private final boolean customStaticLibs = dynamicLibC || staticLibCpp; BinutilsCCLinkerInvocation(AbstractImage.NativeImageKind imageKind, NativeLibraries nativeLibs, List symbols) { super(imageKind, nativeLibs, symbols); @@ -314,7 +314,7 @@ protected void setOutputKind(List cmd) { cmd.add("-Wl,--export-dynamic"); break; case STATIC_EXECUTABLE: - if (!staticExecWithDynamicallyLinkLibC) { + if (!customStaticLibs) { cmd.add("-static"); } break; @@ -326,21 +326,32 @@ protected void setOutputKind(List cmd) { } } + private static final Set LIB_C_NAMES = Set.of("pthread", "dl", "rt", "m"); + @Override protected List getLibrariesCommand() { List cmd = new ArrayList<>(); + if (customStaticLibs) { + cmd.add("-Wl,--push-state"); + } for (String lib : libs) { - if (staticExecWithDynamicallyLinkLibC) { - String linkingMode = libCLibaries.contains(lib) - ? "dynamic" - : "static"; + String linkingMode = null; + if (dynamicLibC) { + linkingMode = LIB_C_NAMES.contains(lib) ? "dynamic" : "static"; + } else if (staticLibCpp) { + linkingMode = lib.equals("stdc++") ? "static" : "dynamic"; + } + if (linkingMode != null) { cmd.add("-Wl,-B" + linkingMode); } cmd.add("-l" + lib); } + if (customStaticLibs) { + cmd.add("-Wl,--pop-state"); + } // Make sure libgcc gets statically linked - if (staticExecWithDynamicallyLinkLibC) { + if (customStaticLibs) { cmd.add("-static-libgcc"); } return cmd; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JRTFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JRTFeature.java deleted file mode 100644 index 425aab252ba1..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JRTFeature.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2021, 2021, 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.jdk; - -import com.oracle.svm.core.feature.InternalFeature; -import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; -import com.oracle.svm.hosted.FeatureImpl; - -@AutomaticallyRegisteredFeature -public class JRTFeature implements InternalFeature { - - @Override - public void beforeAnalysis(BeforeAnalysisAccess access) { - access.registerReachabilityHandler(duringAnalysisAccess -> { - FeatureImpl.BeforeAnalysisAccessImpl beforeAnalysisAccess = (FeatureImpl.BeforeAnalysisAccessImpl) access; - beforeAnalysisAccess.getNativeLibraries().addStaticJniLibrary("jimage"); - beforeAnalysisAccess.getNativeLibraries().addDynamicNonJniLibrary("stdc++"); - }, access.findClassByName("jdk.internal.jimage.NativeImageBuffer")); - } -} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java index 12a54326b5b7..b577937e70ba 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java @@ -43,6 +43,7 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BooleanSupplier; +import java.util.function.Function; import java.util.function.Predicate; import org.graalvm.nativeimage.AnnotationAccess; @@ -1043,24 +1044,30 @@ Class findTargetClass(Class annotatedBaseClass, TargetClass target) { } protected Class findTargetClass(Class annotatedBaseClass, TargetClass target, boolean checkOnlyWith) { + return findTargetClass(TargetClass.class, TargetClass.NoClassNameProvider.class, + annotatedBaseClass, target, target.value(), target.className(), target.classNameProvider(), target.innerClass(), checkOnlyWith ? target.onlyWith() : null); + } + + protected Class findTargetClass(Class targetClass, Class noClassNameProviderClass, + Class annotatedBaseClass, T target, Class value, String targetClassName, Class> classNameProvider, String[] innerClasses, Class[] onlyWith) { + String className; - if (target.value() != TargetClass.class) { - guarantee(target.className().isEmpty(), "Both class and class name specified for substitution"); - guarantee(target.classNameProvider() == TargetClass.NoClassNameProvider.class, "Both class and classNameProvider specified for substitution"); - className = target.value().getName(); - } else if (target.classNameProvider() != TargetClass.NoClassNameProvider.class) { + if (value != targetClass) { + guarantee(targetClassName.isEmpty(), "Both class and class name specified for substitution"); + guarantee(classNameProvider == noClassNameProviderClass, "Both class and classNameProvider specified for substitution"); + className = value.getName(); + } else if (classNameProvider != noClassNameProviderClass) { try { - className = ReflectionUtil.newInstance(target.classNameProvider()).apply(target); + className = ReflectionUtil.newInstance(classNameProvider).apply(target); } catch (ReflectionUtilError ex) { - throw UserError.abort(ex.getCause(), "Cannot instantiate classNameProvider: %s. The class must have a parameterless constructor.", target.classNameProvider().getTypeName()); + throw UserError.abort(ex.getCause(), "Cannot instantiate classNameProvider: %s. The class must have a parameterless constructor.", classNameProvider.getTypeName()); } } else { - guarantee(!target.className().isEmpty(), "Neither class, className, nor classNameProvider specified for substitution"); - className = target.className(); + guarantee(!targetClassName.isEmpty(), "Neither class, className, nor classNameProvider specified for substitution"); + className = targetClassName; } - - if (checkOnlyWith) { - for (Class onlyWithClass : target.onlyWith()) { + if (onlyWith != null) { + for (Class onlyWithClass : onlyWith) { Object onlyWithProvider; try { onlyWithProvider = ReflectionUtil.newInstance(onlyWithClass); @@ -1091,8 +1098,8 @@ protected Class findTargetClass(Class annotatedBaseClass, TargetClass targ throw UserError.abort("Substitution target for %s is not loaded. Use field `onlyWith` in the `TargetClass` annotation to make substitution only active when needed.", annotatedBaseClass.getName()); } - if (target.innerClass().length > 0) { - for (String innerClass : target.innerClass()) { + if (innerClasses.length > 0) { + for (String innerClass : innerClasses) { Class prevHolder = holder; holder = findInnerClass(prevHolder, innerClass); if (holder == null) { @@ -1105,7 +1112,7 @@ protected Class findTargetClass(Class annotatedBaseClass, TargetClass targ return holder; } - private static Class findInnerClass(Class outerClass, String innerClassSimpleName) { + protected static Class findInnerClass(Class outerClass, String innerClassSimpleName) { for (Class innerClass : outerClass.getDeclaredClasses()) { // Checkstyle: allow Class.getSimpleName String simpleName = innerClass.getSimpleName();