diff --git a/compiler/mx.compiler/suite.py b/compiler/mx.compiler/suite.py index eadc0cbe98fb..d3e4b9f5d98a 100644 --- a/compiler/mx.compiler/suite.py +++ b/compiler/mx.compiler/suite.py @@ -2039,7 +2039,7 @@ "org.graalvm.compiler.truffle.jfr to jdk.internal.vm.compiler.truffle.jfr", "org.graalvm.libgraal to jdk.internal.vm.compiler.management", "org.graalvm.util to jdk.internal.vm.compiler.management", - "org.graalvm.util.json to org.graalvm.nativeimage.librarysupport,org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.configure", + "org.graalvm.util.json to org.graalvm.nativeimage.librarysupport,org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.configure,org.graalvm.nativeimage.driver", ], "uses" : [ "com.oracle.truffle.api.impl.TruffleLocator", diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 89b6228986b0..b18c1c5ef4a3 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -277,7 +277,7 @@ def query_native_image(all_args, option): stdoutdata = [] def stdout_collector(x): stdoutdata.append(x.rstrip()) - _native_image(['--dry-run'] + all_args, out=stdout_collector) + _native_image(['--dry-run', '--verbose'] + all_args, out=stdout_collector) def remove_quotes(val): if len(val) >= 2 and val.startswith("'") and val.endswith("'"): diff --git a/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/LocatableOption.java b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/LocatableOption.java index 62927c9a3a31..176bd31f98b9 100644 --- a/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/LocatableOption.java +++ b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/LocatableOption.java @@ -27,10 +27,10 @@ public final class LocatableOption { - final String name; - final String origin; + public final String name; + public final String origin; - static LocatableOption from(String rawOptionName) { + public static LocatableOption from(String rawOptionName) { return new LocatableOption(rawOptionName); } @@ -54,6 +54,10 @@ public String toString() { return result + " from '" + origin + "'"; } + public String rawName() { + return origin == null ? name : name + '@' + origin; + } + private static final class LocatableOptionValue { private final Object value; private final String origin; diff --git a/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/MultiOptionValue.java b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/MultiOptionValue.java index 520e34e2c569..6c08fd60ff9c 100644 --- a/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/MultiOptionValue.java +++ b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/option/MultiOptionValue.java @@ -26,6 +26,7 @@ package com.oracle.svm.common.option; import java.util.List; +import java.util.Optional; public interface MultiOptionValue { @@ -38,6 +39,8 @@ public interface MultiOptionValue { */ List values(); + Optional lastValue(); + void valueUpdate(Object value); MultiOptionValue createCopy(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FallbackExecutor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FallbackExecutor.java index decc55655884..29c8909ea14c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FallbackExecutor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FallbackExecutor.java @@ -57,15 +57,15 @@ public class FallbackExecutor { public static class Options { @Option(help = "Internal option used to specify system properties for FallbackExecutor.")// - public static final HostedOptionKey FallbackExecutorSystemProperty = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final HostedOptionKey FallbackExecutorSystemProperty = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); @Option(help = "Internal option used to specify MainClass for FallbackExecutor.")// public static final HostedOptionKey FallbackExecutorMainClass = new HostedOptionKey<>(null); @Option(help = "Internal option used to specify Classpath for FallbackExecutor.")// public static final HostedOptionKey FallbackExecutorClasspath = new HostedOptionKey<>(null); @Option(help = "Internal option used to specify java arguments for FallbackExecutor.")// - public static final HostedOptionKey FallbackExecutorJavaArg = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final HostedOptionKey FallbackExecutorJavaArg = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); @Option(help = "Internal option used to specify runtime java arguments for FallbackExecutor.")// - public static final RuntimeOptionKey FallbackExecutorRuntimeJavaArg = new RuntimeOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final RuntimeOptionKey FallbackExecutorRuntimeJavaArg = new RuntimeOptionKey<>(LocatableMultiOptionValue.Strings.build()); } public static void main(String[] args) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/RuntimeAssertionsSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/RuntimeAssertionsSupport.java index 842fe64bd4c6..682170c625dd 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/RuntimeAssertionsSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/RuntimeAssertionsSupport.java @@ -96,7 +96,7 @@ public static class Options { @APIOption(name = {"-da", "-disableassertions"}, valueSeparator = VALUE_SEPARATOR, valueTransformer = RuntimeAssertionsOptionTransformer.Disable.class, defaultValue = "", // customHelp = "also -da[:[packagename]...|:classname] or -disableassertions[:[packagename]...|:classname]. Disable assertions with specified granularity.")// @Option(help = "Enable or disable Java assert statements at run time") // - public static final HostedOptionKey RuntimeAssertions = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final HostedOptionKey RuntimeAssertions = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); @APIOption(name = {"-esa", "-enablesystemassertions"}, customHelp = "also -enablesystemassertions. Enables assertions in all system classes.") // @APIOption(name = {"-dsa", "-disablesystemassertions"}, kind = APIOption.APIOptionKind.Negated, customHelp = "also -disablesystemassertions. Disables assertions in all system classes.") // 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 b4811a2be9c7..3005a9d7ce4b 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 @@ -60,6 +60,7 @@ import com.oracle.svm.core.heap.ReferenceHandler; import com.oracle.svm.core.option.APIOption; import com.oracle.svm.core.option.APIOptionGroup; +import com.oracle.svm.core.option.BundleMember; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.HostedOptionValues; import com.oracle.svm.core.option.LocatableMultiOptionValue; @@ -81,7 +82,7 @@ public class SubstrateOptions { @Option(help = "Preserve the local variable information for every Java source line to allow line-by-line stepping in the debugger. Allow the lookup of Java-level method information, e.g., in stack traces.")// public static final HostedOptionKey SourceLevelDebug = new HostedOptionKey<>(false); @Option(help = "Constrain debug info generation to the comma-separated list of package prefixes given to this option.")// - public static final HostedOptionKey SourceLevelDebugFilter = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey SourceLevelDebugFilter = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); public static boolean parseOnce() { /* @@ -271,10 +272,11 @@ public static void setDebugInfoValueUpdateHandler(ValueUpdateHandler up public static final HostedOptionKey IncludeNodeSourcePositions = new HostedOptionKey<>(false); @Option(help = "Search path for C libraries passed to the linker (list of comma-separated directories)")// - public static final HostedOptionKey CLibraryPath = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + @BundleMember(role = BundleMember.Role.Input)// + public static final HostedOptionKey CLibraryPath = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Path passed to the linker as the -rpath (list of comma-separated directories)")// - public static final HostedOptionKey LinkerRPath = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey LinkerRPath = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "Directory of the image file to be generated", type = OptionType.User)// public static final HostedOptionKey Path = new HostedOptionKey<>(null); @@ -346,11 +348,11 @@ protected void onValueUpdate(EconomicMap, Object> values, Boolean o @APIOption(name = "trace-class-initialization")// @Option(help = "Comma-separated list of fully-qualified class names that class initialization is traced for.")// - public static final HostedOptionKey TraceClassInitialization = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey TraceClassInitialization = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @APIOption(name = "trace-object-instantiation")// @Option(help = "Comma-separated list of fully-qualified class names that object instantiation is traced for.")// - public static final HostedOptionKey TraceObjectInstantiation = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey TraceObjectInstantiation = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "Trace all native tool invocations as part of image building", type = User)// public static final HostedOptionKey TraceNativeToolUsage = new HostedOptionKey<>(false); @@ -365,10 +367,10 @@ protected void onValueUpdate(EconomicMap, Object> values, Boolean o @APIOption(name = "enable-https", fixedValue = "https", customHelp = "enable https support in the generated image")// @APIOption(name = "enable-url-protocols")// @Option(help = "List of comma separated URL protocols to enable.")// - public static final HostedOptionKey EnableURLProtocols = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey EnableURLProtocols = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "List of comma separated URL protocols that must never be included.")// - public static final HostedOptionKey DisableURLProtocols = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey DisableURLProtocols = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @SuppressWarnings("unused") // @APIOption(name = "enable-all-security-services")// @@ -417,10 +419,11 @@ protected void onValueUpdate(EconomicMap, Object> values, Boolean o @Option(help = "Print GC warnings as part of build output", type = OptionType.User)// public static final HostedOptionKey BuildOutputGCWarnings = new HostedOptionKey<>(true); + @BundleMember(role = BundleMember.Role.Output)// @Option(help = "Print build output statistics as JSON to the specified file. " + "The output conforms to the JSON schema located at: " + "docs/reference-manual/native-image/assets/build-output-schema-v0.9.1.json", type = OptionType.User)// - public static final HostedOptionKey BuildOutputJSONFile = new HostedOptionKey<>(""); + public static final HostedOptionKey BuildOutputJSONFile = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.build()); /* * Object and array allocation options. @@ -485,7 +488,7 @@ public static long getTearDownFailureNanos() { public static final HostedOptionKey AOTTrivialInline = new HostedOptionKey<>(true); @Option(help = "file:doc-files/NeverInlineHelp.txt", type = OptionType.Debug)// - public static final HostedOptionKey NeverInline = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final HostedOptionKey NeverInline = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); @Option(help = "Maximum number of nodes in a method so that it is considered trivial.")// public static final HostedOptionKey MaxNodesInTrivialMethod = new HostedOptionKey<>(20); @@ -506,7 +509,7 @@ public static long getTearDownFailureNanos() { public static final HostedOptionKey UseCompressedFrameEncodings = new HostedOptionKey<>(true); @Option(help = "Report error if [:{,}] is discovered during analysis (valid values for UsageKind: InHeap, Allocated, Reachable).", type = OptionType.Debug)// - public static final HostedOptionKey ReportAnalysisForbiddenType = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final HostedOptionKey ReportAnalysisForbiddenType = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); @Option(help = "Backend used by the compiler", type = OptionType.User)// public static final HostedOptionKey CompilerBackend = new HostedOptionKey<>("lir") { @@ -573,7 +576,7 @@ public static boolean useLIRBackend() { public static final HostedOptionKey CCompilerPath = new HostedOptionKey<>(null); @APIOption(name = "native-compiler-options")// @Option(help = "Provide custom C compiler option used for query code compilation.", type = OptionType.User)// - public static final HostedOptionKey CCompilerOption = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final HostedOptionKey CCompilerOption = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); @Option(help = "Use strict checks when performing query code compilation.", type = OptionType.User)// public static final HostedOptionKey StrictQueryCodeCompilation = new HostedOptionKey<>(true); @@ -635,7 +638,7 @@ public static void defaultDebugInfoValueUpdateHandler(EconomicMap, } @Option(help = "Search path for source files for Application or GraalVM classes (list of comma-separated directories or jar files)")// - public static final HostedOptionKey DebugInfoSourceSearchPath = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey DebugInfoSourceSearchPath = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Directory under which to create source file cache for Application or GraalVM classes")// public static final HostedOptionKey DebugInfoSourceCacheRoot = new HostedOptionKey<>("sources"); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java index 7aa9258e1482..4b9c350cc5e6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java @@ -55,7 +55,7 @@ public final class VMInspectionOptions { @APIOption(name = ENABLE_MONITORING_OPTION, defaultValue = MONITORING_DEFAULT_NAME) // @Option(help = "Enable monitoring features that allow the VM to be inspected at run time. Comma-separated list can contain " + MONITORING_ALLOWED_VALUES + ". " + "For example: `--" + ENABLE_MONITORING_OPTION + "=" + MONITORING_HEAPDUMP_NAME + "," + MONITORING_JFR_NAME + "`.", type = OptionType.User) // - public static final HostedOptionKey EnableMonitoringFeatures = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated(), + public static final HostedOptionKey EnableMonitoringFeatures = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter(), VMInspectionOptions::validateEnableMonitoringFeatures); public static void validateEnableMonitoringFeatures(@SuppressWarnings("unused") OptionKey optionKey) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/ProjectHeaderFile.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/ProjectHeaderFile.java index ff9c346f426d..8e7f4082504d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/ProjectHeaderFile.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/ProjectHeaderFile.java @@ -169,8 +169,8 @@ static class MainHeaderResolver implements HeaderResolver { @Override public HeaderSearchResult resolveHeader(String projectName, String headerFile) { List locations = new ArrayList<>(); - for (String clibPathComponent : SubstrateOptions.CLibraryPath.getValue().values()) { - Path clibPathHeaderFile = Paths.get(clibPathComponent).resolve(headerFile).normalize().toAbsolutePath(); + for (Path clibPathComponent : SubstrateOptions.CLibraryPath.getValue().values()) { + Path clibPathHeaderFile = clibPathComponent.resolve(headerFile).normalize().toAbsolutePath(); locations.add(clibPathHeaderFile.toString()); if (Files.exists(clibPathHeaderFile)) { return new HeaderSearchResult(Optional.of("\"" + clibPathHeaderFile + "\""), locations); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java index 075355ab094a..be86da7c13a3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java @@ -28,7 +28,6 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; @@ -38,6 +37,7 @@ import org.graalvm.compiler.options.Option; import org.graalvm.compiler.options.OptionType; +import com.oracle.svm.core.option.BundleMember; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.LocatableMultiOptionValue; import com.oracle.svm.core.util.UserError; @@ -50,45 +50,55 @@ public final class ConfigurationFiles { public static final class Options { @Option(help = "Directories directly containing configuration files for dynamic features at runtime.", type = OptionType.User)// - static final HostedOptionKey ConfigurationFileDirectories = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + @BundleMember(role = BundleMember.Role.Input)// + static final HostedOptionKey ConfigurationFileDirectories = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resource path above configuration resources for dynamic features at runtime.", type = OptionType.User)// - public static final HostedOptionKey ConfigurationResourceRoots = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey ConfigurationResourceRoots = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "file:doc-files/ReflectionConfigurationFilesHelp.txt", type = OptionType.User)// - public static final HostedOptionKey ReflectionConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.commaSeparated()); + @BundleMember(role = BundleMember.Role.Input)// + public static final HostedOptionKey ReflectionConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resources describing program elements to be made available for reflection (see ReflectionConfigurationFiles).", type = OptionType.User)// - public static final HostedOptionKey ReflectionConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey ReflectionConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "file:doc-files/ProxyConfigurationFilesHelp.txt", type = OptionType.User)// - public static final HostedOptionKey DynamicProxyConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.commaSeparated()); + @BundleMember(role = BundleMember.Role.Input)// + public static final HostedOptionKey DynamicProxyConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resources describing program elements to be made available for reflection (see ProxyConfigurationFiles).", type = OptionType.User)// - public static final HostedOptionKey DynamicProxyConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey DynamicProxyConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "file:doc-files/SerializationConfigurationFilesHelp.txt", type = OptionType.User)// - public static final HostedOptionKey SerializationConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.commaSeparated()); + @BundleMember(role = BundleMember.Role.Input)// + public static final HostedOptionKey SerializationConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resources describing program elements to be made available for serialization (see SerializationConfigurationFiles).", type = OptionType.User)// - public static final HostedOptionKey SerializationConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey SerializationConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "file:doc-files/SerializationConfigurationFilesHelp.txt", type = OptionType.User)// - public static final HostedOptionKey SerializationDenyConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.commaSeparated()); + @BundleMember(role = BundleMember.Role.Input)// + public static final HostedOptionKey SerializationDenyConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resources describing program elements that must not be made available for serialization.", type = OptionType.User)// - public static final HostedOptionKey SerializationDenyConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey SerializationDenyConfigurationResources = new HostedOptionKey<>( + LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "Files describing Java resources to be included in the image.", type = OptionType.User)// - public static final HostedOptionKey ResourceConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.commaSeparated()); + @BundleMember(role = BundleMember.Role.Input)// + public static final HostedOptionKey ResourceConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resources describing Java resources to be included in the image.", type = OptionType.User)// - public static final HostedOptionKey ResourceConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey ResourceConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "Files describing program elements to be made accessible via JNI (for syntax, see ReflectionConfigurationFiles)", type = OptionType.User)// - public static final HostedOptionKey JNIConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.commaSeparated()); + @BundleMember(role = BundleMember.Role.Input)// + public static final HostedOptionKey JNIConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resources describing program elements to be made accessible via JNI (see JNIConfigurationFiles).", type = OptionType.User)// - public static final HostedOptionKey JNIConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey JNIConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "Files describing predefined classes that can be loaded at runtime.", type = OptionType.User)// - public static final HostedOptionKey PredefinedClassesConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.commaSeparated()); + @BundleMember(role = BundleMember.Role.Input)// + public static final HostedOptionKey PredefinedClassesConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resources describing predefined classes that can be loaded at runtime.", type = OptionType.User)// - public static final HostedOptionKey PredefinedClassesConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey PredefinedClassesConfigurationResources = new HostedOptionKey<>( + LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "Causes unknown attributes in configuration objects to abort the image build instead of emitting a warning.")// public static final HostedOptionKey StrictConfiguration = new HostedOptionKey<>(false); @@ -96,11 +106,11 @@ public static final class Options { public static List findConfigurationFiles(String fileName) { List files = new ArrayList<>(); - for (String directory : Options.ConfigurationFileDirectories.getValue().values()) { - if (Files.exists(Paths.get(directory, ConfigurationFile.LOCK_FILE_NAME))) { - throw foundLockFile("Configuration file directory '" + directory + "'"); + for (Path configDir : Options.ConfigurationFileDirectories.getValue().values()) { + if (Files.exists(configDir.resolve(ConfigurationFile.LOCK_FILE_NAME))) { + throw foundLockFile("Configuration file directory '" + configDir + "'"); } - Path path = Paths.get(directory, fileName); + Path path = configDir.resolve(fileName); if (Files.exists(path)) { files.add(path); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/APIOption.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/APIOption.java index 69ad9049159d..21200fd882fe 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/APIOption.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/APIOption.java @@ -122,13 +122,7 @@ enum APIOptionKind { * -{H,R}:-<OptionDescriptor#name>. For other options using * {@code Negated} is not allowed. */ - Negated, - /** - * Denotes that the annotated {@code String} option represents one or more file system - * paths, separated by ','. Relative paths will be resolved against the current working - * directory in which the native image tool is executed. - */ - Paths + Negated } class Utils { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/BundleMember.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/BundleMember.java new file mode 100644 index 000000000000..565feb80b241 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/BundleMember.java @@ -0,0 +1,43 @@ +/* + * 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.core.option; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface BundleMember { + Role role(); + + enum Role { + Input, + Output, + Ignore + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/LocatableMultiOptionValue.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/LocatableMultiOptionValue.java index 30a159062f06..9bb60bcf5289 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/LocatableMultiOptionValue.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/LocatableMultiOptionValue.java @@ -26,8 +26,8 @@ import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -40,34 +40,22 @@ public abstract class LocatableMultiOptionValue implements MultiOptionValue { - private static final String DEFAULT_DELIMITER = ""; + protected static final String NO_DELIMITER = ""; private final String delimiter; private final Class valueType; private final List> values; - private LocatableMultiOptionValue(Class valueType) { - this(valueType, DEFAULT_DELIMITER); - } - - private LocatableMultiOptionValue(Class valueType, String delimiter) { - this.delimiter = delimiter; + private LocatableMultiOptionValue(Class valueType, String delimiter, List defaults) { this.valueType = valueType; + this.delimiter = delimiter; values = new ArrayList<>(); - } - - private LocatableMultiOptionValue(Class valueType, List defaults) { - this(valueType, defaults, DEFAULT_DELIMITER); - } - - private LocatableMultiOptionValue(Class valueType, List defaults, String delimiter) { - this(valueType, delimiter); - values.addAll(defaults.stream().map(val -> Pair.create(val, "default")).collect(Collectors.toList())); + values.addAll(defaults.stream().map(val -> Pair. createLeft(val)).collect(Collectors.toList())); } private LocatableMultiOptionValue(LocatableMultiOptionValue other) { - this.delimiter = other.delimiter; this.valueType = other.valueType; + this.delimiter = other.delimiter; this.values = new ArrayList<>(other.values); } @@ -102,10 +90,20 @@ public void valueUpdate(Object value) { @Override public List values() { + return getValuesWithOrigins().map(Pair::getLeft).collect(Collectors.toList()); + } + + @Override + public Optional lastValue() { + return lastValueWithOrigin().map(Pair::getLeft); + } + + public Optional> lastValueWithOrigin() { if (values.isEmpty()) { - return Collections.emptyList(); + return Optional.empty(); } - return values.stream().map(Pair::getLeft).collect(Collectors.toList()); + Pair pair = values.get(values.size() - 1); + return Optional.of(Pair.create(pair.getLeft(), OptionOrigin.from(pair.getRight()))); } public Stream> getValuesWithOrigins() { @@ -120,7 +118,7 @@ public String toString() { return "<" + ClassUtil.getUnqualifiedName(valueType).toLowerCase() + ">*"; } - public static class Strings extends LocatableMultiOptionValue { + public static final class Strings extends LocatableMultiOptionValue { private Strings(Strings other) { super(other); @@ -131,28 +129,24 @@ public MultiOptionValue createCopy() { return new Strings(this); } - public Strings() { - super(String.class); - } - - public Strings(String delimiter) { - super(String.class, delimiter); + private Strings(String delimiter, List defaultStrings) { + super(String.class, delimiter, defaultStrings); } - public Strings(List defaultStrings) { - super(String.class, defaultStrings); + public static Strings build() { + return new Strings(NO_DELIMITER, List.of()); } - public Strings(List defaultStrings, String delimiter) { - super(String.class, defaultStrings, delimiter); + public static Strings buildWithCommaDelimiter() { + return new Strings(",", List.of()); } - public static Strings commaSeparated() { - return new Strings(","); + public static Strings buildWithDefaults(String... defaultStrings) { + return new Strings(NO_DELIMITER, List.of(defaultStrings)); } } - public static class Paths extends LocatableMultiOptionValue { + public static final class Paths extends LocatableMultiOptionValue { private Paths(Paths other) { super(other); @@ -163,24 +157,24 @@ public MultiOptionValue createCopy() { return new Paths(this); } - public Paths() { - super(Path.class); + private Paths(String delimiter, List defaultPaths) { + super(Path.class, delimiter, defaultPaths); } - public Paths(String delimiter) { - super(Path.class, delimiter); + public static Paths build() { + return new Paths(NO_DELIMITER, List.of()); } - public Paths(List defaultPaths) { - super(Path.class, defaultPaths); + public static Paths buildWithCommaDelimiter() { + return new Paths(",", List.of()); } - public Paths(List defaultPaths, String delimiter) { - super(Path.class, defaultPaths, delimiter); + public static Paths buildWithCustomDelimiter(String delimiter) { + return new Paths(delimiter, List.of()); } - public static Paths commaSeparated() { - return new Paths(","); + public static Paths buildWithDefaults(Path... defaultPaths) { + return new Paths(NO_DELIMITER, List.of(defaultPaths)); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OptionOrigin.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OptionOrigin.java index 54779df61a55..5fe23cc112ef 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OptionOrigin.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OptionOrigin.java @@ -68,6 +68,10 @@ public List getRedirectionValues(@SuppressWarnings("unused") Path values } public static OptionOrigin from(String origin) { + return from(origin, true); + } + + public static OptionOrigin from(String origin, boolean strict) { if (origin == null || origin.startsWith(argFilePrefix)) { return commandLineOptionOriginSingleton; @@ -79,19 +83,25 @@ public static OptionOrigin from(String origin) { if (macroOption != null) { return macroOption; } - throw VMError.shouldNotReachHere("Unsupported OptionOrigin: " + origin); + if (strict) { + throw VMError.shouldNotReachHere("Unsupported OptionOrigin: " + origin); + } + return null; } switch (originURI.getScheme()) { case "jar": return new JarOptionOrigin(originURI); case "file": Path originPath = Path.of(originURI); - if (!Files.isReadable(originPath)) { + if (!Files.isReadable(originPath) && strict) { VMError.shouldNotReachHere("Directory origin with path that cannot be read: " + originPath); } return new DirectoryOptionOrigin(originPath); default: - throw VMError.shouldNotReachHere("OptionOrigin of unsupported scheme: " + originURI); + if (strict) { + throw VMError.shouldNotReachHere("OptionOrigin of unsupported scheme: " + originURI); + } + return null; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ClasspathUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ClasspathUtils.java index 53f335f716dc..e660b98a650c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ClasspathUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ClasspathUtils.java @@ -24,44 +24,13 @@ */ package com.oracle.svm.core.util; -import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.regex.Pattern; - -import com.oracle.svm.core.OS; public final class ClasspathUtils { - public static final String cpWildcardSubstitute = "$JavaCla$$pathWildcard$ubstitute$"; - - public static Path stringToClasspath(String cp) { - String separators = Pattern.quote(File.separator); - if (OS.getCurrent().equals(OS.WINDOWS)) { - separators += "/"; /* on Windows also / is accepted as valid separator */ - } - String[] components = cp.split("[" + separators + "]", Integer.MAX_VALUE); - for (int i = 0; i < components.length; i++) { - if (components[i].equals("*")) { - components[i] = cpWildcardSubstitute; - } - } - return Paths.get(String.join(File.separator, components)); - } - - public static String classpathToString(Path cp) { - String[] components = cp.toString().split(Pattern.quote(File.separator), Integer.MAX_VALUE); - for (int i = 0; i < components.length; i++) { - if (components[i].equals(cpWildcardSubstitute)) { - components[i] = "*"; - } - } - return String.join(File.separator, components); - } - public static boolean isJar(Path p) { Path fn = p.getFileName(); assert fn != null; diff --git a/substratevm/src/com.oracle.svm.driver/resources/HelpExtra.txt b/substratevm/src/com.oracle.svm.driver/resources/HelpExtra.txt index 3690ce0be354..b460584fcac8 100644 --- a/substratevm/src/com.oracle.svm.driver/resources/HelpExtra.txt +++ b/substratevm/src/com.oracle.svm.driver/resources/HelpExtra.txt @@ -16,5 +16,23 @@ Non-standard options help: --diagnostics-mode Enables logging of image-build information to a diagnostics folder. --dry-run output the command line that would be used for building + --bundle-create[=new-bundle.nib] + in addition to image building, create a native image bundle file (*.nib + file) that allows rebuilding of that image again at a later point. If a + bundle-file gets passed the bundle will be created with the given name. + Otherwise, the bundle-file name is derived from the image name. Note + both bundle options can be combined with --dry-run to only perform the + bundle operations without any actual image building. + --bundle-apply=some-bundle.nib + an image will be built from the given bundle file with the exact same + arguments and files that have been passed to native-image originally + to create the bundle. Note that if an extra --bundle-create gets passed + after --bundle-apply, a new bundle will be written based on the given + bundle args plus any additional arguments that haven been passed + afterwards. For example: + > native-image --bundle-apply=app.nib --bundle-create=app_dbg.nib -g + creates a new bundle app_dbg.nib based on the given app.nib bundle. + Both bundles are the same except the new one also uses the -g option. + -V= provide values for placeholders in native-image.properties files 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 b73cf00f9262..fd8da3341a92 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 @@ -24,11 +24,12 @@ */ package com.oracle.svm.driver; +import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -36,6 +37,7 @@ import java.util.ServiceLoader; import java.util.SortedMap; import java.util.TreeMap; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -46,10 +48,12 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; -import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.common.option.LocatableOption; +import com.oracle.svm.common.option.MultiOptionValue; import com.oracle.svm.core.option.APIOption; import com.oracle.svm.core.option.APIOption.APIOptionKind; import com.oracle.svm.core.option.APIOptionGroup; +import com.oracle.svm.core.option.BundleMember; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.OptionOrigin; import com.oracle.svm.core.option.SubstrateOptionsParser; @@ -60,6 +64,7 @@ import com.oracle.svm.util.ModuleSupport; import com.oracle.svm.util.ReflectionUtil; import com.oracle.svm.util.ReflectionUtil.ReflectionUtilError; +import com.oracle.svm.util.StringUtil; class APIOptionHandler extends NativeImage.OptionHandler { @@ -69,7 +74,6 @@ static final class OptionInfo { final String builderOption; final String defaultValue; final String helpText; - final boolean hasPathArguments; final boolean defaultFinal; final String deprecationWarning; final boolean extra; @@ -77,14 +81,13 @@ static final class OptionInfo { final List> valueTransformers; final APIOptionGroup group; - OptionInfo(String[] variants, char[] valueSeparator, String builderOption, String defaultValue, String helpText, boolean hasPathArguments, boolean defaultFinal, String deprecationWarning, + OptionInfo(String[] variants, char[] valueSeparator, String builderOption, String defaultValue, String helpText, boolean defaultFinal, String deprecationWarning, List> valueTransformers, APIOptionGroup group, boolean extra) { this.variants = variants; this.valueSeparator = valueSeparator; this.builderOption = builderOption; this.defaultValue = defaultValue; this.helpText = helpText; - this.hasPathArguments = hasPathArguments; this.defaultFinal = defaultFinal; this.deprecationWarning = deprecationWarning; this.valueTransformers = valueTransformers; @@ -97,28 +100,44 @@ boolean isDeprecated() { } } + static final class PathsOptionInfo { + private final String delimiter; + private final BundleMember.Role role; + + PathsOptionInfo(String delimiter, BundleMember.Role role) { + this.delimiter = delimiter; + this.role = role; + } + } + private final SortedMap apiOptions; private final Map groupInfos; + private final Map pathOptions; APIOptionHandler(NativeImage nativeImage) { super(nativeImage); if (NativeImage.IS_AOT) { APIOptionSupport support = ImageSingletons.lookup(APIOptionSupport.class); groupInfos = support.groupInfos; + pathOptions = support.pathOptions; apiOptions = support.options; } else { groupInfos = new HashMap<>(); - apiOptions = extractOptions(ServiceLoader.load(OptionDescriptors.class, nativeImage.getClass().getClassLoader()), groupInfos); + pathOptions = new HashMap<>(); + apiOptions = extractOptions(ServiceLoader.load(OptionDescriptors.class, nativeImage.getClass().getClassLoader()), groupInfos, pathOptions); } } - static SortedMap extractOptions(ServiceLoader optionDescriptors, Map groupInfos) { + static SortedMap extractOptions(ServiceLoader optionDescriptors, Map groupInfos, Map pathOptions) { EconomicMap hostedOptions = EconomicMap.create(); EconomicMap runtimeOptions = EconomicMap.create(); HostedOptionParser.collectOptions(optionDescriptors, hostedOptions, runtimeOptions); SortedMap apiOptions = new TreeMap<>(); Map, APIOptionGroup> groupInstances = new HashMap<>(); - hostedOptions.getValues().forEach(o -> extractOption(NativeImage.oH, o, apiOptions, groupInfos, groupInstances)); + hostedOptions.getValues().forEach(o -> { + extractOption(NativeImage.oH, o, apiOptions, groupInfos, groupInstances); + extractPathOption(NativeImage.oH, o, pathOptions); + }); runtimeOptions.getValues().forEach(o -> extractOption(NativeImage.oR, o, apiOptions, groupInfos, groupInstances)); groupInfos.forEach((groupName, groupInfo) -> { if (groupInfo.defaultValues.size() > 1) { @@ -131,138 +150,153 @@ static SortedMap extractOptions(ServiceLoader apiOptions, Map groupInfos, Map, APIOptionGroup> groupInstances) { - try { - Field optionField = optionDescriptor.getDeclaringClass().getDeclaredField(optionDescriptor.getFieldName()); - APIOption[] apiAnnotations = optionField.getAnnotationsByType(APIOption.class); - for (APIOption apiAnnotation : apiAnnotations) { - String builderOption = optionPrefix; - if (apiAnnotation.name().length <= 0) { - VMError.shouldNotReachHere(String.format("APIOption for %s does not provide a name entry", optionDescriptor.getLocation())); - } - String apiOptionName = APIOption.Utils.optionName(apiAnnotation.name()[0]); - String rawOptionName = optionDescriptor.getName(); - APIOptionGroup group = null; - String defaultValue = null; - - boolean booleanOption = false; - Class optionValueType = optionDescriptor.getOptionValueType(); - if (optionValueType.isArray()) { - VMError.guarantee(optionDescriptor.getOptionKey() instanceof HostedOptionKey, "Only HostedOptionKeys are allowed to have array type key values."); - optionValueType = optionValueType.getComponentType(); - } - boolean hasFixedValue = apiAnnotation.fixedValue().length > 0; - if (optionValueType.equals(Boolean.class)) { - if (!apiAnnotation.group().equals(APIOption.NullGroup.class)) { - try { - Class groupClass = apiAnnotation.group(); - APIOptionGroup g = group = groupInstances.computeIfAbsent(groupClass, ReflectionUtil::newInstance); - String groupName = APIOption.Utils.groupName(group); - GroupInfo groupInfo = groupInfos.computeIfAbsent(groupName, (n) -> new GroupInfo(g)); - if (group.helpText() == null || group.helpText().isEmpty()) { - VMError.shouldNotReachHere(String.format("APIOptionGroup %s(%s) needs to provide help text", groupClass.getName(), group.name())); - } - String groupMember = apiAnnotation.name()[0]; - groupInfo.supportedValues.add(groupMember); - - apiOptionName = groupName + groupMember; - - Boolean isEnabled = (Boolean) optionDescriptor.getOptionKey().getDefaultValue(); - if (isEnabled) { - groupInfo.defaultValues.add(groupMember); - /* Use OptionInfo.defaultValue to remember group default value */ - defaultValue = groupMember; - } - } catch (ReflectionUtilError ex) { - throw VMError.shouldNotReachHere( - "Class specified as group for @APIOption " + apiOptionName + " cannot be loaded or instantiated: " + apiAnnotation.group().getTypeName(), ex.getCause()); + for (APIOption apiAnnotation : getAnnotationsByType(optionDescriptor, APIOption.class)) { + String builderOption = optionPrefix; + if (apiAnnotation.name().length <= 0) { + VMError.shouldNotReachHere(String.format("APIOption for %s does not provide a name entry", optionDescriptor.getLocation())); + } + String apiOptionName = APIOption.Utils.optionName(apiAnnotation.name()[0]); + String rawOptionName = optionDescriptor.getName(); + APIOptionGroup group = null; + String defaultValue = null; + + boolean booleanOption = false; + Class optionValueType = optionDescriptor.getOptionValueType(); + if (optionValueType.isArray()) { + VMError.guarantee(optionDescriptor.getOptionKey() instanceof HostedOptionKey, "Only HostedOptionKeys are allowed to have array type key values."); + optionValueType = optionValueType.getComponentType(); + } + boolean hasFixedValue = apiAnnotation.fixedValue().length > 0; + if (optionValueType.equals(Boolean.class)) { + if (!apiAnnotation.group().equals(APIOption.NullGroup.class)) { + try { + Class groupClass = apiAnnotation.group(); + APIOptionGroup g = group = groupInstances.computeIfAbsent(groupClass, ReflectionUtil::newInstance); + String groupName = APIOption.Utils.groupName(group); + GroupInfo groupInfo = groupInfos.computeIfAbsent(groupName, (n) -> new GroupInfo(g)); + if (group.helpText() == null || group.helpText().isEmpty()) { + VMError.shouldNotReachHere(String.format("APIOptionGroup %s(%s) needs to provide help text", groupClass.getName(), group.name())); } - } - if (apiAnnotation.kind().equals(APIOptionKind.Paths)) { - VMError.shouldNotReachHere(String.format("Boolean APIOption %s(%s) cannot use APIOptionKind.Paths", apiOptionName, rawOptionName)); - } - if (apiAnnotation.defaultValue().length > 0) { - VMError.shouldNotReachHere(String.format("Boolean APIOption %s(%s) cannot use APIOption.defaultValue", apiOptionName, rawOptionName)); - } - if (hasFixedValue) { - VMError.shouldNotReachHere(String.format("Boolean APIOption %s(%s) cannot use APIOption.fixedValue", apiOptionName, rawOptionName)); - } - builderOption += apiAnnotation.kind().equals(APIOptionKind.Negated) ? "-" : "+"; - builderOption += rawOptionName; - booleanOption = true; - } else { - if (!apiAnnotation.group().equals(APIOption.NullGroup.class)) { - VMError.shouldNotReachHere(String.format("Using @APIOption.group not supported for non-boolean APIOption %s(%s)", apiOptionName, rawOptionName)); - } - if (apiAnnotation.kind().equals(APIOptionKind.Negated)) { - VMError.shouldNotReachHere(String.format("Non-boolean APIOption %s(%s) cannot use APIOptionKind.Negated", apiOptionName, rawOptionName)); - } - if (apiAnnotation.defaultValue().length > 1) { - VMError.shouldNotReachHere(String.format("APIOption %s(%s) cannot have more than one APIOption.defaultValue", apiOptionName, rawOptionName)); - } - if (apiAnnotation.fixedValue().length > 1) { - VMError.shouldNotReachHere(String.format("APIOption %s(%s) cannot have more than one APIOption.fixedValue", apiOptionName, rawOptionName)); - } - if (hasFixedValue && apiAnnotation.defaultValue().length > 0) { - VMError.shouldNotReachHere(String.format("APIOption %s(%s) APIOption.defaultValue and APIOption.fixedValue cannot be combined", apiOptionName, rawOptionName)); - } - if (apiAnnotation.defaultValue().length > 0) { - defaultValue = apiAnnotation.defaultValue()[0]; - } - if (hasFixedValue) { - defaultValue = apiAnnotation.fixedValue()[0]; - } - - builderOption += rawOptionName; - builderOption += "="; - } + String groupMember = apiAnnotation.name()[0]; + groupInfo.supportedValues.add(groupMember); - String helpText = optionDescriptor.getHelp(); - if (!apiAnnotation.customHelp().isEmpty()) { - helpText = apiAnnotation.customHelp(); - } - if (helpText == null || helpText.isEmpty()) { - VMError.shouldNotReachHere(String.format("APIOption %s(%s) needs to provide help text", apiOptionName, rawOptionName)); - } - if (group == null) { - /* Regular help text needs to start with lower-case letter */ - helpText = startLowerCase(helpText); - } + apiOptionName = groupName + groupMember; - List> valueTransformers = new ArrayList<>(apiAnnotation.valueTransformer().length); - for (Class> transformerClass : apiAnnotation.valueTransformer()) { - try { - valueTransformers.add(ReflectionUtil.newInstance(transformerClass)); + Boolean isEnabled = (Boolean) optionDescriptor.getOptionKey().getDefaultValue(); + if (isEnabled) { + groupInfo.defaultValues.add(groupMember); + /* Use OptionInfo.defaultValue to remember group default value */ + defaultValue = groupMember; + } } catch (ReflectionUtilError ex) { throw VMError.shouldNotReachHere( - "Class specified as valueTransformer for @APIOption " + apiOptionName + " cannot be loaded or instantiated: " + transformerClass.getTypeName(), ex.getCause()); + "Class specified as group for @APIOption " + apiOptionName + " cannot be loaded or instantiated: " + apiAnnotation.group().getTypeName(), ex.getCause()); } } - if (apiAnnotation.valueSeparator().length == 0) { - throw VMError.shouldNotReachHere(String.format("APIOption %s(%s) does not specify any valueSeparator", apiOptionName, rawOptionName)); + if (apiAnnotation.defaultValue().length > 0) { + VMError.shouldNotReachHere(String.format("Boolean APIOption %s(%s) cannot use APIOption.defaultValue", apiOptionName, rawOptionName)); } - for (char valueSeparator : apiAnnotation.valueSeparator()) { - if (valueSeparator == APIOption.WHITESPACE_SEPARATOR) { - String msgTail = " cannot use APIOption.WHITESPACE_SEPARATOR as value separator"; - if (booleanOption) { - throw VMError.shouldNotReachHere(String.format("Boolean APIOption %s(%s)" + msgTail, apiOptionName, rawOptionName)); - } - if (hasFixedValue) { - VMError.shouldNotReachHere(String.format("APIOption %s(%s) with fixed value" + msgTail, apiOptionName, rawOptionName)); - } - if (defaultValue != null) { - VMError.shouldNotReachHere(String.format("APIOption %s(%s) with default value" + msgTail, apiOptionName, rawOptionName)); - } + if (hasFixedValue) { + VMError.shouldNotReachHere(String.format("Boolean APIOption %s(%s) cannot use APIOption.fixedValue", apiOptionName, rawOptionName)); + } + builderOption += apiAnnotation.kind().equals(APIOptionKind.Negated) ? "-" : "+"; + builderOption += rawOptionName; + booleanOption = true; + } else { + if (!apiAnnotation.group().equals(APIOption.NullGroup.class)) { + VMError.shouldNotReachHere(String.format("Using @APIOption.group not supported for non-boolean APIOption %s(%s)", apiOptionName, rawOptionName)); + } + if (apiAnnotation.kind().equals(APIOptionKind.Negated)) { + VMError.shouldNotReachHere(String.format("Non-boolean APIOption %s(%s) cannot use APIOptionKind.Negated", apiOptionName, rawOptionName)); + } + if (apiAnnotation.defaultValue().length > 1) { + VMError.shouldNotReachHere(String.format("APIOption %s(%s) cannot have more than one APIOption.defaultValue", apiOptionName, rawOptionName)); + } + if (apiAnnotation.fixedValue().length > 1) { + VMError.shouldNotReachHere(String.format("APIOption %s(%s) cannot have more than one APIOption.fixedValue", apiOptionName, rawOptionName)); + } + if (hasFixedValue && apiAnnotation.defaultValue().length > 0) { + VMError.shouldNotReachHere(String.format("APIOption %s(%s) APIOption.defaultValue and APIOption.fixedValue cannot be combined", apiOptionName, rawOptionName)); + } + if (apiAnnotation.defaultValue().length > 0) { + defaultValue = apiAnnotation.defaultValue()[0]; + } + if (hasFixedValue) { + defaultValue = apiAnnotation.fixedValue()[0]; + } + + builderOption += rawOptionName; + builderOption += "="; + } + + String helpText = optionDescriptor.getHelp(); + if (!apiAnnotation.customHelp().isEmpty()) { + helpText = apiAnnotation.customHelp(); + } + if (helpText == null || helpText.isEmpty()) { + VMError.shouldNotReachHere(String.format("APIOption %s(%s) needs to provide help text", apiOptionName, rawOptionName)); + } + if (group == null) { + /* Regular help text needs to start with lower-case letter */ + helpText = startLowerCase(helpText); + } + + List> valueTransformers = new ArrayList<>(apiAnnotation.valueTransformer().length); + for (Class> transformerClass : apiAnnotation.valueTransformer()) { + try { + valueTransformers.add(ReflectionUtil.newInstance(transformerClass)); + } catch (ReflectionUtilError ex) { + throw VMError.shouldNotReachHere( + "Class specified as valueTransformer for @APIOption " + apiOptionName + " cannot be loaded or instantiated: " + transformerClass.getTypeName(), ex.getCause()); + } + } + if (apiAnnotation.valueSeparator().length == 0) { + throw VMError.shouldNotReachHere(String.format("APIOption %s(%s) does not specify any valueSeparator", apiOptionName, rawOptionName)); + } + for (char valueSeparator : apiAnnotation.valueSeparator()) { + if (valueSeparator == APIOption.WHITESPACE_SEPARATOR) { + String msgTail = " cannot use APIOption.WHITESPACE_SEPARATOR as value separator"; + if (booleanOption) { + throw VMError.shouldNotReachHere(String.format("Boolean APIOption %s(%s)" + msgTail, apiOptionName, rawOptionName)); + } + if (hasFixedValue) { + VMError.shouldNotReachHere(String.format("APIOption %s(%s) with fixed value" + msgTail, apiOptionName, rawOptionName)); + } + if (defaultValue != null) { + VMError.shouldNotReachHere(String.format("APIOption %s(%s) with default value" + msgTail, apiOptionName, rawOptionName)); } } - boolean defaultFinal = booleanOption || hasFixedValue; - apiOptions.put(apiOptionName, - new APIOptionHandler.OptionInfo(apiAnnotation.name(), apiAnnotation.valueSeparator(), builderOption, defaultValue, helpText, - apiAnnotation.kind().equals(APIOptionKind.Paths), - defaultFinal, apiAnnotation.deprecated(), valueTransformers, group, apiAnnotation.extra())); } + boolean defaultFinal = booleanOption || hasFixedValue; + apiOptions.put(apiOptionName, + new APIOptionHandler.OptionInfo(apiAnnotation.name(), apiAnnotation.valueSeparator(), builderOption, defaultValue, helpText, + defaultFinal, apiAnnotation.deprecated(), valueTransformers, group, apiAnnotation.extra())); + } + } + + private static void extractPathOption(String optionPrefix, OptionDescriptor optionDescriptor, Map pathOptions) { + Object defaultValue = optionDescriptor.getOptionKey().getDefaultValue(); + if (defaultValue instanceof MultiOptionValue) { + var multiOptionDefaultValue = ((MultiOptionValue) defaultValue); + if (Path.class.isAssignableFrom(multiOptionDefaultValue.getValueType())) { + String rawOptionName = optionDescriptor.getName(); + String builderOption = optionPrefix + rawOptionName; + BundleMember.Role role = BundleMember.Role.Ignore; + for (BundleMember bundleMember : getAnnotationsByType(optionDescriptor, BundleMember.class)) { + role = bundleMember.role(); + } + pathOptions.put(builderOption, new PathsOptionInfo(multiOptionDefaultValue.getDelimiter(), role)); + } + } + } + + private static List getAnnotationsByType(OptionDescriptor optionDescriptor, Class annotationClass) { + try { + Field optionField = optionDescriptor.getDeclaringClass().getDeclaredField(optionDescriptor.getFieldName()); + return List.of(optionField.getAnnotationsByType(annotationClass)); } catch (NoSuchFieldException e) { - /* Does not qualify as APIOption */ + return List.of(); } } @@ -349,12 +383,6 @@ String translateOption(ArgumentQueue argQueue) { optionValue = optionNameAndOptionValue[1]; } if (optionValue != null) { - if (option.hasPathArguments) { - optionValue = Arrays.stream(SubstrateUtil.split(optionValue, ",")) - .filter(s -> !s.isEmpty()) - .map(this::tryCanonicalize) - .collect(Collectors.joining(",")); - } Object transformed = optionValue; for (Function transformer : option.valueTransformers) { transformed = transformer.apply(transformed); @@ -368,12 +396,82 @@ String translateOption(ArgumentQueue argQueue) { return null; } - private String tryCanonicalize(String path) { + String transformBuilderArgument(String builderArgument, BiFunction transformFunction) { + BuilderArgumentParts argumentParts = BuilderArgumentParts.from(builderArgument); + if (argumentParts.optionValue == null) { + /* Option has no value that could need transforming -> early exit */ + return builderArgument; + } + PathsOptionInfo pathsOptionInfo = pathOptions.get(argumentParts.option.name); + if (pathsOptionInfo == null || pathsOptionInfo.role == BundleMember.Role.Ignore) { + /* Not an option that request value-transforming -> early exit */ + return builderArgument; + } + + /* + * Option requests value-transformations, first split value aggregate into individual values + */ + List rawEntries; + String delimiter = pathsOptionInfo.delimiter; + if (delimiter.isEmpty()) { + rawEntries = List.of(argumentParts.optionValue); + } else { + rawEntries = List.of(StringUtil.split(argumentParts.optionValue, delimiter)); + } + + /* Perform value-transformation on individual values with given transformFunction */ + try { + String transformedOptionValue = rawEntries.stream() + .filter(s -> !s.isEmpty()) + .map(this::tryCanonicalize) + .map(src -> transformFunction.apply(src, pathsOptionInfo.role)) + .map(Path::toString) + .collect(Collectors.joining(delimiter)); + /* Update argumentParts with transformed aggregate value and return as string */ + argumentParts.optionValue = transformedOptionValue; + return argumentParts.toString(); + } catch (BundleSupport.BundlePathSubstitutionError error) { + String originStr = argumentParts.option.origin; + Object optionOrigin = OptionOrigin.from(originStr, false); + if (optionOrigin == null && originStr != null) { + /* If we cannot get an OptionOrigin, fallback to the raw originStr */ + optionOrigin = originStr; + } + String fromPart = optionOrigin != null ? " from '" + optionOrigin + "'" : ""; + throw NativeImage.showError("Failed to prepare path entry '" + error.origPath + "' of option " + argumentParts.option.name + fromPart + " for bundle inclusion.", error); + } + } + + static final class BuilderArgumentParts { + + final LocatableOption option; + String optionValue; + + private BuilderArgumentParts(LocatableOption option, String optionValue) { + this.option = option; + this.optionValue = optionValue; + } + + static BuilderArgumentParts from(String builderArgument) { + String[] nameAndValue = StringUtil.split(builderArgument, "=", 2); + String optionValue = nameAndValue.length != 2 ? null : nameAndValue[1]; + return new BuilderArgumentParts(LocatableOption.from(nameAndValue[0]), optionValue); + } + + @Override + public String toString() { + String optionName = option.rawName(); + return optionValue == null ? optionName : optionName + "=" + optionValue; + } + } + + private Path tryCanonicalize(String path) { + Path origPath = Paths.get(path); try { - return nativeImage.canonicalize(Paths.get(path)).toString(); + return nativeImage.canonicalize(origPath); } catch (NativeImage.NativeImageError e) { /* Allow features to handle the path string. */ - return path; + return origPath; } } @@ -394,7 +492,7 @@ void printOptions(Consumer println, boolean extra) { options.add(option); } else { /* Start with space efficient singletonList */ - optionInfo.put(groupOrOptionName, Collections.singletonList(option)); + optionInfo.put(groupOrOptionName, List.of(option)); } }); optionInfo.forEach((optionName, options) -> { @@ -470,10 +568,12 @@ final class APIOptionSupport { final Map groupInfos; final SortedMap options; + final Map pathOptions; - APIOptionSupport(Map groupInfos, SortedMap options) { + APIOptionSupport(Map groupInfos, SortedMap options, Map pathOptions) { this.groupInfos = groupInfos; this.options = options; + this.pathOptions = pathOptions; } } @@ -489,8 +589,9 @@ public void afterRegistration(AfterRegistrationAccess access) { public void duringSetup(DuringSetupAccess access) { FeatureImpl.DuringSetupAccessImpl accessImpl = (FeatureImpl.DuringSetupAccessImpl) access; Map groupInfos = new HashMap<>(); + Map pathOptions = new HashMap<>(); ServiceLoader optionDescriptors = ServiceLoader.load(OptionDescriptors.class, accessImpl.getImageClassLoader().getClassLoader()); - SortedMap options = APIOptionHandler.extractOptions(optionDescriptors, groupInfos); - ImageSingletons.add(APIOptionSupport.class, new APIOptionSupport(groupInfos, options)); + SortedMap options = APIOptionHandler.extractOptions(optionDescriptors, groupInfos, pathOptions); + ImageSingletons.add(APIOptionSupport.class, new APIOptionSupport(groupInfos, options, pathOptions)); } } diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/BundleSupport.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/BundleSupport.java new file mode 100644 index 000000000000..881a0ddd80b7 --- /dev/null +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/BundleSupport.java @@ -0,0 +1,781 @@ +/* + * 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.driver; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.Reader; +import java.net.URI; +import java.nio.file.CopyOption; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.format.FormatStyle; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Properties; +import java.util.Set; +import java.util.function.Predicate; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.graalvm.util.json.JSONParserException; + +import com.oracle.svm.core.OS; +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.configure.ConfigurationParser; +import com.oracle.svm.core.option.BundleMember; +import com.oracle.svm.core.util.json.JsonPrinter; +import com.oracle.svm.core.util.json.JsonWriter; +import com.oracle.svm.util.ClassUtil; + +final class BundleSupport { + + final NativeImage nativeImage; + + final Path rootDir; + + final Path stageDir; + final Path classPathDir; + final Path modulePathDir; + final Path auxiliaryDir; + final Path outputDir; + final Path imagePathOutputDir; + final Path auxiliaryOutputDir; + + Map pathCanonicalizations = new HashMap<>(); + Map pathSubstitutions = new HashMap<>(); + + private final List buildArgs; + private Collection updatedBuildArgs; + + boolean loadBundle; + boolean writeBundle; + + private static final int BUNDLE_FILE_FORMAT_VERSION_MAJOR = 0; + private static final int BUNDLE_FILE_FORMAT_VERSION_MINOR = 9; + + private static final String BUNDLE_INFO_MESSAGE_PREFIX = "GraalVM Native Image Bundle Support: "; + private static final String BUNDLE_TEMP_DIR_PREFIX = "bundleRoot-"; + private static final String ORIGINAL_DIR_EXTENSION = ".orig"; + + private Path bundlePath; + private String bundleName; + + private final BundleProperties bundleProperties; + + static boolean allowBundleSupport; + static final String UNLOCK_BUNDLE_SUPPORT_OPTION = "--enable-experimental-bundle-support"; + + static final String BUNDLE_OPTION = "--bundle"; + static final String BUNDLE_FILE_EXTENSION = ".nib"; + + private enum BundleOptionVariants { + create(), + apply() + } + + static BundleSupport create(NativeImage nativeImage, String bundleArg, NativeImage.ArgumentQueue args) { + if (!allowBundleSupport) { + throw NativeImage.showError("Bundle support is still experimental and needs to be unlocked with " + UNLOCK_BUNDLE_SUPPORT_OPTION); + } + + if (!nativeImage.userConfigProperties.isEmpty()) { + throw NativeImage.showError("Bundle support cannot be combined with " + NativeImage.CONFIG_FILE_ENV_VAR_KEY + " environment variable use."); + } + + try { + String variant = bundleArg.substring(BUNDLE_OPTION.length() + 1); + String bundleFilename = null; + String[] variantParts = SubstrateUtil.split(variant, "=", 2); + if (variantParts.length == 2) { + variant = variantParts[0]; + bundleFilename = variantParts[1]; + } + String applyOptionStr = BUNDLE_OPTION + "-" + BundleOptionVariants.apply; + String createOptionStr = BUNDLE_OPTION + "-" + BundleOptionVariants.create; + BundleSupport bundleSupport; + switch (BundleOptionVariants.valueOf(variant)) { + case apply: + if (nativeImage.useBundle()) { + if (nativeImage.bundleSupport.loadBundle) { + throw NativeImage.showError(String.format("native-image allows option %s to be specified only once.", applyOptionStr)); + } + if (nativeImage.bundleSupport.writeBundle) { + throw NativeImage.showError(String.format("native-image option %s is not allowed to be used after option %s.", applyOptionStr, createOptionStr)); + } + } + if (bundleFilename == null) { + throw NativeImage.showError(String.format("native-image option %s requires a bundle file argument. E.g. %s=bundle-file.nib.", applyOptionStr, applyOptionStr)); + } + bundleSupport = new BundleSupport(nativeImage, bundleFilename); + /* Inject the command line args from the loaded bundle in-place */ + List buildArgs = bundleSupport.getBuildArgs(); + for (int i = buildArgs.size() - 1; i >= 0; i--) { + args.push(buildArgs.get(i)); + } + nativeImage.showVerboseMessage(nativeImage.isVerbose(), BUNDLE_INFO_MESSAGE_PREFIX + "Inject args: '" + String.join(" ", buildArgs) + "'"); + /* Snapshot args after in-place expansion (includes also args after this one) */ + bundleSupport.updatedBuildArgs = args.snapshot(); + break; + case create: + if (nativeImage.useBundle()) { + if (nativeImage.bundleSupport.writeBundle) { + throw NativeImage.showError(String.format("native-image allows option %s to be specified only once.", bundleArg)); + } else { + bundleSupport = nativeImage.bundleSupport; + bundleSupport.writeBundle = true; + } + } else { + bundleSupport = new BundleSupport(nativeImage); + } + if (bundleFilename != null) { + bundleSupport.updateBundleLocation(Path.of(bundleFilename), true); + } + break; + default: + throw new IllegalArgumentException(); + } + return bundleSupport; + + } catch (StringIndexOutOfBoundsException | IllegalArgumentException e) { + String suggestedVariants = Arrays.stream(BundleOptionVariants.values()) + .map(v -> BUNDLE_OPTION + "-" + v) + .collect(Collectors.joining(", ")); + throw NativeImage.showError("Unknown option " + bundleArg + ". Valid variants are: " + suggestedVariants + "."); + } + } + + private BundleSupport(NativeImage nativeImage) { + Objects.requireNonNull(nativeImage); + this.nativeImage = nativeImage; + + loadBundle = false; + writeBundle = true; + try { + rootDir = Files.createTempDirectory(BUNDLE_TEMP_DIR_PREFIX); + bundleProperties = new BundleProperties(); + + Path inputDir = rootDir.resolve("input"); + stageDir = Files.createDirectories(inputDir.resolve("stage")); + auxiliaryDir = Files.createDirectories(inputDir.resolve("auxiliary")); + Path classesDir = inputDir.resolve("classes"); + classPathDir = Files.createDirectories(classesDir.resolve("cp")); + modulePathDir = Files.createDirectories(classesDir.resolve("p")); + outputDir = rootDir.resolve("output"); + imagePathOutputDir = Files.createDirectories(outputDir.resolve("default")); + auxiliaryOutputDir = Files.createDirectories(outputDir.resolve("other")); + } catch (IOException e) { + throw NativeImage.showError("Unable to create bundle directory layout", e); + } + this.buildArgs = Collections.unmodifiableList(nativeImage.config.getBuildArgs()); + } + + private BundleSupport(NativeImage nativeImage, String bundleFilenameArg) { + Objects.requireNonNull(nativeImage); + this.nativeImage = nativeImage; + + loadBundle = true; + writeBundle = false; + + Objects.requireNonNull(bundleFilenameArg); + updateBundleLocation(Path.of(bundleFilenameArg), false); + + try { + rootDir = Files.createTempDirectory(BUNDLE_TEMP_DIR_PREFIX); + bundleProperties = new BundleProperties(); + + outputDir = rootDir.resolve("output"); + String originalOutputDirName = outputDir.getFileName().toString() + ORIGINAL_DIR_EXTENSION; + + Path bundleFilePath = bundlePath.resolve(bundleName + BUNDLE_FILE_EXTENSION); + try (JarFile archive = new JarFile(bundleFilePath.toFile())) { + archive.stream().forEach(jarEntry -> { + Path bundleEntry = rootDir.resolve(jarEntry.getName()); + if (bundleEntry.startsWith(outputDir)) { + /* Extract original output to different path */ + bundleEntry = rootDir.resolve(originalOutputDirName).resolve(outputDir.relativize(bundleEntry)); + } + try { + Path bundleFileParent = bundleEntry.getParent(); + if (bundleFileParent != null) { + Files.createDirectories(bundleFileParent); + } + Files.copy(archive.getInputStream(jarEntry), bundleEntry); + } catch (IOException e) { + throw NativeImage.showError("Unable to copy " + jarEntry.getName() + " from bundle " + bundleEntry + " to " + bundleEntry, e); + } + }); + } + } catch (IOException e) { + throw NativeImage.showError("Unable to expand bundle directory layout from bundle file " + bundleName + BUNDLE_FILE_EXTENSION, e); + } + + bundleProperties.loadAndVerify(); + + try { + Path inputDir = rootDir.resolve("input"); + stageDir = Files.createDirectories(inputDir.resolve("stage")); + auxiliaryDir = Files.createDirectories(inputDir.resolve("auxiliary")); + Path classesDir = inputDir.resolve("classes"); + classPathDir = Files.createDirectories(classesDir.resolve("cp")); + modulePathDir = Files.createDirectories(classesDir.resolve("p")); + imagePathOutputDir = Files.createDirectories(outputDir.resolve("default")); + auxiliaryOutputDir = Files.createDirectories(outputDir.resolve("other")); + } catch (IOException e) { + throw NativeImage.showError("Unable to create bundle directory layout", e); + } + + Path pathCanonicalizationsFile = stageDir.resolve("path_canonicalizations.json"); + try (Reader reader = Files.newBufferedReader(pathCanonicalizationsFile)) { + new PathMapParser(pathCanonicalizations).parseAndRegister(reader); + } catch (IOException e) { + throw NativeImage.showError("Failed to read bundle-file " + pathCanonicalizationsFile, e); + } + Path pathSubstitutionsFile = stageDir.resolve("path_substitutions.json"); + try (Reader reader = Files.newBufferedReader(pathSubstitutionsFile)) { + new PathMapParser(pathSubstitutions).parseAndRegister(reader); + } catch (IOException e) { + throw NativeImage.showError("Failed to read bundle-file " + pathSubstitutionsFile, e); + } + Path buildArgsFile = stageDir.resolve("build.json"); + try (Reader reader = Files.newBufferedReader(buildArgsFile)) { + List buildArgsFromFile = new ArrayList<>(); + new BuildArgsParser(buildArgsFromFile).parseAndRegister(reader); + buildArgs = Collections.unmodifiableList(buildArgsFromFile); + } catch (IOException e) { + throw NativeImage.showError("Failed to read bundle-file " + pathSubstitutionsFile, e); + } + } + + public List getBuildArgs() { + return buildArgs; + } + + Path recordCanonicalization(Path before, Path after) { + if (before.startsWith(rootDir)) { + nativeImage.showVerboseMessage(nativeImage.isVVerbose(), "RecordCanonicalization Skip: " + before); + return before; + } + if (after.startsWith(nativeImage.config.getJavaHome())) { + return after; + } + nativeImage.showVerboseMessage(nativeImage.isVVerbose(), "RecordCanonicalization src: " + before + ", dst: " + after); + pathCanonicalizations.put(before, after); + return after; + } + + Path restoreCanonicalization(Path before) { + Path after = pathCanonicalizations.get(before); + nativeImage.showVerboseMessage(after != null && nativeImage.isVVerbose(), "RestoreCanonicalization src: " + before + ", dst: " + after); + return after; + } + + Path substituteAuxiliaryPath(Path origPath, BundleMember.Role bundleMemberRole) { + Path destinationDir = switch (bundleMemberRole) { + case Input -> auxiliaryDir; + case Output -> auxiliaryOutputDir; + case Ignore -> null; + }; + if (destinationDir == null) { + return origPath; + } + return substitutePath(origPath, destinationDir); + } + + Path substituteImagePath(Path origPath) { + pathSubstitutions.put(origPath, rootDir.relativize(imagePathOutputDir)); + return imagePathOutputDir; + } + + Path substituteClassPath(Path origPath) { + try { + return substitutePath(origPath, classPathDir); + } catch (BundlePathSubstitutionError error) { + throw NativeImage.showError("Failed to prepare class-path entry '" + error.origPath + "' for bundle inclusion.", error); + } + } + + Path substituteModulePath(Path origPath) { + try { + return substitutePath(origPath, modulePathDir); + } catch (BundlePathSubstitutionError error) { + throw NativeImage.showError("Failed to prepare module-path entry '" + error.origPath + "' for bundle inclusion.", error); + } + } + + @SuppressWarnings("serial") + static final class BundlePathSubstitutionError extends Error { + public final Path origPath; + + BundlePathSubstitutionError(String message, Path origPath) { + super(message); + this.origPath = origPath; + } + } + + @SuppressWarnings("try") + private Path substitutePath(Path origPath, Path destinationDir) { + assert destinationDir.startsWith(rootDir); + + if (origPath.startsWith(rootDir)) { + nativeImage.showVerboseMessage(nativeImage.isVVerbose(), "RecordSubstitution/RestoreSubstitution Skip: " + origPath); + return origPath; + } + + Path previousRelativeSubstitutedPath = pathSubstitutions.get(origPath); + if (previousRelativeSubstitutedPath != null) { + nativeImage.showVerboseMessage(nativeImage.isVVerbose(), "RestoreSubstitution src: " + origPath + ", dst: " + previousRelativeSubstitutedPath); + return rootDir.resolve(previousRelativeSubstitutedPath); + } + + if (origPath.startsWith(nativeImage.config.getJavaHome())) { + /* If origPath comes from native-image itself, substituting is not needed. */ + return origPath; + } + + boolean forbiddenPath = false; + if (!OS.WINDOWS.isCurrent()) { + Path tmpPath = ClassUtil.CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES_ROOT.resolve("tmp"); + boolean subdirInTmp = origPath.startsWith(tmpPath) && !origPath.equals(tmpPath); + if (!subdirInTmp) { + Set forbiddenPaths = new HashSet<>(ClassUtil.CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES); + forbiddenPaths.add(rootDir); + for (Path path : forbiddenPaths) { + if (origPath.startsWith(path)) { + forbiddenPath = true; + break; + } + } + } + } + for (Path rootDirectory : FileSystems.getDefault().getRootDirectories()) { + /* Refuse /, C:, D:, ... */ + if (origPath.equals(rootDirectory)) { + forbiddenPath = true; + break; + } + } + if (forbiddenPath) { + throw new BundlePathSubstitutionError("Bundles do not allow inclusion of directory " + origPath, origPath); + } + + boolean isOutputPath = destinationDir.startsWith(outputDir); + + if (!isOutputPath && !Files.isReadable(origPath)) { + /* Prevent subsequent retries to substitute invalid paths */ + pathSubstitutions.put(origPath, origPath); + return origPath; + } + + // TODO Report error if overlapping dir-trees are passed in + // TODO add .endsWith(ClasspathUtils.cpWildcardSubstitute) handling (copy whole directory) + String origFileName = origPath.getFileName().toString(); + int extensionPos = origFileName.lastIndexOf('.'); + String baseName; + String extension; + if (extensionPos > 0) { + baseName = origFileName.substring(0, extensionPos); + extension = origFileName.substring(extensionPos); + } else { + baseName = origFileName; + extension = ""; + } + + Path substitutedPath = destinationDir.resolve(baseName + extension); + int collisionIndex = 0; + while (Files.exists(substitutedPath)) { + collisionIndex += 1; + substitutedPath = destinationDir.resolve(baseName + "_" + collisionIndex + extension); + } + + if (!isOutputPath) { + copyFiles(origPath, substitutedPath, false); + } + + Path relativeSubstitutedPath = rootDir.relativize(substitutedPath); + nativeImage.showVerboseMessage(nativeImage.isVVerbose(), "RecordSubstitution src: " + origPath + ", dst: " + relativeSubstitutedPath); + pathSubstitutions.put(origPath, relativeSubstitutedPath); + return substitutedPath; + } + + private void copyFiles(Path source, Path target, boolean overwrite) { + nativeImage.showVerboseMessage(nativeImage.isVVerbose(), "> Copy files from " + source + " to " + target); + if (Files.isDirectory(source)) { + try (Stream walk = Files.walk(source)) { + walk.forEach(sourcePath -> copyFile(sourcePath, target.resolve(source.relativize(sourcePath)), overwrite)); + } catch (IOException e) { + throw NativeImage.showError("Failed to iterate through directory " + source, e); + } + } else { + copyFile(source, target, overwrite); + } + } + + private void copyFile(Path sourceFile, Path target, boolean overwrite) { + try { + nativeImage.showVerboseMessage(nativeImage.isVVVerbose(), "> Copy " + sourceFile + " to " + target); + if (overwrite && Files.isDirectory(sourceFile) && Files.isDirectory(target)) { + return; + } + CopyOption[] options = overwrite ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[0]; + Files.copy(sourceFile, target, options); + } catch (IOException e) { + throw NativeImage.showError("Failed to copy " + sourceFile + " to " + target, e); + } + } + + void complete() { + boolean writeOutput; + try (Stream pathOutputFiles = Files.list(imagePathOutputDir); Stream auxiliaryOutputFiles = Files.list(auxiliaryOutputDir)) { + writeOutput = pathOutputFiles.findAny().isPresent() || auxiliaryOutputFiles.findAny().isPresent(); + } catch (IOException e) { + throw NativeImage.showError("Unable to determine if bundle output should be written."); + } + + /* + * In the unlikely case of writing a bundle but no location got specified so far, provide a + * final fallback here. Can happen when something goes wrong in bundle processing itself. + */ + if (bundlePath == null) { + bundlePath = nativeImage.config.getWorkingDirectory(); + bundleName = "unknown"; + } + + if (!nativeImage.isDryRun() && (writeOutput || writeBundle)) { + nativeImage.showNewline(); + } + + if (writeOutput) { + Path externalOutputDir = bundlePath.resolve(bundleName + "." + outputDir.getFileName()); + copyFiles(outputDir, externalOutputDir, true); + nativeImage.showMessage(BUNDLE_INFO_MESSAGE_PREFIX + "Bundle build output written to " + externalOutputDir); + } + + try { + if (writeBundle) { + Path bundleFilePath = writeBundle(); + nativeImage.showMessage(BUNDLE_INFO_MESSAGE_PREFIX + "Bundle written to " + bundleFilePath); + } + } finally { + nativeImage.showNewline(); + nativeImage.deleteAllFiles(rootDir); + } + } + + void updateBundleLocation(Path bundleFile, boolean redefine) { + if (redefine) { + bundlePath = null; + bundleName = null; + } + + if (bundlePath != null) { + Objects.requireNonNull(bundleName); + /* Bundle location is already set */ + return; + } + Path bundleFilePath = bundleFile.toAbsolutePath(); + String bundleFileName = bundleFile.getFileName().toString(); + if (!bundleFileName.endsWith(BUNDLE_FILE_EXTENSION)) { + throw NativeImage.showError("The given bundle file " + bundleFileName + " does not end with '" + BUNDLE_FILE_EXTENSION + "'"); + } + if (Files.isDirectory(bundleFilePath)) { + throw NativeImage.showError("The given bundle file " + bundleFileName + " is a directory and not a file"); + } + if (loadBundle && !redefine) { + if (!Files.isReadable(bundleFilePath)) { + throw NativeImage.showError("The given bundle file " + bundleFileName + " cannot be read."); + } + } + Path newBundlePath = bundleFilePath.getParent(); + if (writeBundle) { + if (!Files.isWritable(newBundlePath)) { + throw NativeImage.showError("The bundle file directory " + newBundlePath + " is not writeable."); + } + if (Files.exists(bundleFilePath) && !Files.isWritable(bundleFilePath)) { + throw NativeImage.showError("The given bundle file " + bundleFileName + " is not writeable."); + } + } + bundlePath = newBundlePath; + bundleName = bundleFileName.substring(0, bundleFileName.length() - BUNDLE_FILE_EXTENSION.length()); + } + + private Path writeBundle() { + String originalOutputDirName = outputDir.getFileName().toString() + ORIGINAL_DIR_EXTENSION; + Path originalOutputDir = rootDir.resolve(originalOutputDirName); + if (Files.exists(originalOutputDir)) { + nativeImage.deleteAllFiles(originalOutputDir); + } + + Path metaInfDir = rootDir.resolve(JarFile.MANIFEST_NAME); + if (Files.exists(metaInfDir)) { + nativeImage.deleteAllFiles(metaInfDir); + } + + Path pathCanonicalizationsFile = stageDir.resolve("path_canonicalizations.json"); + try (JsonWriter writer = new JsonWriter(pathCanonicalizationsFile)) { + /* Printing as list with defined sort-order ensures useful diffs are possible */ + JsonPrinter.printCollection(writer, pathCanonicalizations.entrySet(), Map.Entry.comparingByKey(), BundleSupport::printPathMapping); + } catch (IOException e) { + throw NativeImage.showError("Failed to write bundle-file " + pathCanonicalizationsFile, e); + } + Path pathSubstitutionsFile = stageDir.resolve("path_substitutions.json"); + try (JsonWriter writer = new JsonWriter(pathSubstitutionsFile)) { + /* Printing as list with defined sort-order ensures useful diffs are possible */ + JsonPrinter.printCollection(writer, pathSubstitutions.entrySet(), Map.Entry.comparingByKey(), BundleSupport::printPathMapping); + } catch (IOException e) { + throw NativeImage.showError("Failed to write bundle-file " + pathSubstitutionsFile, e); + } + + Path buildArgsFile = stageDir.resolve("build.json"); + try (JsonWriter writer = new JsonWriter(buildArgsFile)) { + ArrayList cleanBuildArgs = new ArrayList<>(); + for (String buildArg : updatedBuildArgs != null ? updatedBuildArgs : buildArgs) { + if (buildArg.equals(UNLOCK_BUNDLE_SUPPORT_OPTION)) { + continue; + } + if (buildArg.startsWith(BUNDLE_OPTION)) { + continue; + } + if (buildArg.startsWith(nativeImage.oHPath)) { + continue; + } + if (buildArg.equals(CmdLineOptionHandler.VERBOSE_OPTION)) { + continue; + } + if (buildArg.equals(CmdLineOptionHandler.DRY_RUN_OPTION)) { + continue; + } + if (buildArg.startsWith("-Dllvm.bin.dir=")) { + Optional existing = nativeImage.config.getBuildArgs().stream().filter(arg -> arg.startsWith("-Dllvm.bin.dir=")).findFirst(); + if (existing.isPresent() && !existing.get().equals(buildArg)) { + throw NativeImage.showError("Bundle native-image argument '" + buildArg + "' conflicts with existing '" + existing.get() + "'."); + } + continue; + } + cleanBuildArgs.add(buildArg); + } + /* Printing as list with defined sort-order ensures useful diffs are possible */ + JsonPrinter.printCollection(writer, cleanBuildArgs, null, BundleSupport::printBuildArg); + } catch (IOException e) { + throw NativeImage.showError("Failed to write bundle-file " + pathSubstitutionsFile, e); + } + + bundleProperties.write(); + + Path bundleFilePath = bundlePath.resolve(bundleName + BUNDLE_FILE_EXTENSION); + try (JarOutputStream jarOutStream = new JarOutputStream(Files.newOutputStream(bundleFilePath), createManifest())) { + try (Stream walk = Files.walk(rootDir)) { + walk.filter(Predicate.not(Files::isDirectory)).forEach(bundleEntry -> { + String jarEntryName = rootDir.relativize(bundleEntry).toString(); + JarEntry entry = new JarEntry(jarEntryName.replace(File.separator, "/")); + try { + entry.setTime(Files.getLastModifiedTime(bundleEntry).toMillis()); + jarOutStream.putNextEntry(entry); + Files.copy(bundleEntry, jarOutStream); + jarOutStream.closeEntry(); + } catch (IOException e) { + throw NativeImage.showError("Failed to copy " + bundleEntry + " into bundle file " + bundleFilePath.getFileName(), e); + } + }); + } + } catch (IOException e) { + throw NativeImage.showError("Failed to create bundle file " + bundleFilePath.getFileName(), e); + } + + return bundleFilePath; + } + + private static Manifest createManifest() { + Manifest mf = new Manifest(); + Attributes attributes = mf.getMainAttributes(); + attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + /* If we add run-bundle-as-java-application a launcher mainclass would be added here */ + return mf; + } + + private static final String substitutionMapSrcField = "src"; + private static final String substitutionMapDstField = "dst"; + + private static void printPathMapping(Map.Entry entry, JsonWriter w) throws IOException { + w.append('{').quote(substitutionMapSrcField).append(" : ").quote(entry.getKey()); + w.append(',').quote(substitutionMapDstField).append(':').quote(entry.getValue()); + w.append('}'); + } + + private static void printBuildArg(String entry, JsonWriter w) throws IOException { + w.quote(entry); + } + + private static final class PathMapParser extends ConfigurationParser { + + private final Map pathMap; + + private PathMapParser(Map pathMap) { + super(true); + this.pathMap = pathMap; + } + + @Override + public void parseAndRegister(Object json, URI origin) { + for (var rawEntry : asList(json, "Expected a list of path substitution objects")) { + var entry = asMap(rawEntry, "Expected a substitution object"); + Object srcPathString = entry.get(substitutionMapSrcField); + if (srcPathString == null) { + throw new JSONParserException("Expected " + substitutionMapSrcField + "-field in substitution object"); + } + Object dstPathString = entry.get(substitutionMapDstField); + if (dstPathString == null) { + throw new JSONParserException("Expected " + substitutionMapDstField + "-field in substitution object"); + } + pathMap.put(Path.of(srcPathString.toString()), Path.of(dstPathString.toString())); + } + } + } + + private static final class BuildArgsParser extends ConfigurationParser { + + private final List args; + + private BuildArgsParser(List args) { + super(true); + this.args = args; + } + + @Override + public void parseAndRegister(Object json, URI origin) { + for (var arg : asList(json, "Expected a list of arguments")) { + args.add(arg.toString()); + } + } + } + + private static final Path bundlePropertiesFileName = Path.of("META-INF/nibundle.properties"); + + private final class BundleProperties { + + private static final String PROPERTY_KEY_BUNDLE_FILE_VERSION_MAJOR = "BundleFileVersionMajor"; + private static final String PROPERTY_KEY_BUNDLE_FILE_VERSION_MINOR = "BundleFileVersionMinor"; + private static final String PROPERTY_KEY_BUNDLE_FILE_CREATION_TIMESTAMP = "BundleFileCreationTimestamp"; + private static final String PROPERTY_KEY_IMAGE_BUILT = "ImageBuilt"; + private static final String PROPERTY_KEY_BUILT_WITH_CONTAINER = "BuiltWithContainer"; + private static final String PROPERTY_KEY_NATIVE_IMAGE_PLATFORM = "NativeImagePlatform"; + private static final String PROPERTY_KEY_NATIVE_IMAGE_VERSION = "NativeImageVersion"; + + private final Path bundlePropertiesFile; + private final Map properties; + + private BundleProperties() { + Objects.requireNonNull(rootDir); + Objects.requireNonNull(nativeImage); + + bundlePropertiesFile = rootDir.resolve(bundlePropertiesFileName); + properties = new HashMap<>(); + } + + private void loadAndVerify() { + Objects.requireNonNull(bundleName); + + String bundleFileName = bundlePath.resolve(bundleName + BUNDLE_FILE_EXTENSION).toString(); + if (!Files.isReadable(bundlePropertiesFile)) { + throw NativeImage.showError("The given bundle file " + bundleFileName + " does not contain a bundle properties file"); + } + + properties.putAll(NativeImage.loadProperties(bundlePropertiesFile)); + String fileVersionKey = PROPERTY_KEY_BUNDLE_FILE_VERSION_MAJOR; + try { + int major = Integer.parseInt(properties.getOrDefault(fileVersionKey, "-1")); + fileVersionKey = PROPERTY_KEY_BUNDLE_FILE_VERSION_MINOR; + int minor = Integer.parseInt(properties.getOrDefault(fileVersionKey, "-1")); + String message = String.format("The given bundle file %s was created with newer bundle-file-format version %d.%d" + + " (current %d.%d). Update to the latest version of native-image.", bundleFileName, major, minor, BUNDLE_FILE_FORMAT_VERSION_MAJOR, BUNDLE_FILE_FORMAT_VERSION_MINOR); + if (major > BUNDLE_FILE_FORMAT_VERSION_MAJOR) { + throw NativeImage.showError(message); + } else if (major == BUNDLE_FILE_FORMAT_VERSION_MAJOR) { + if (minor > BUNDLE_FILE_FORMAT_VERSION_MINOR) { + NativeImage.showWarning(message); + } + } + } catch (NumberFormatException e) { + throw NativeImage.showError(fileVersionKey + " in " + bundlePropertiesFileName + " is missing or ill-defined", e); + } + String bundleVersion = properties.getOrDefault(PROPERTY_KEY_NATIVE_IMAGE_VERSION, "unknown"); + String currentVersion = bundleVersion.equals(NativeImage.getNativeImageVersion()) ? "" : " != '" + NativeImage.getNativeImageVersion() + "'"; + String bundlePlatform = properties.getOrDefault(PROPERTY_KEY_NATIVE_IMAGE_PLATFORM, "unknown"); + String currentPlatform = bundlePlatform.equals(NativeImage.platform) ? "" : " != '" + NativeImage.platform + "'"; + String bundleCreationTimestamp = properties.getOrDefault(PROPERTY_KEY_BUNDLE_FILE_CREATION_TIMESTAMP, ""); + String localDateStr; + try { + ZonedDateTime dateTime = ZonedDateTime.parse(bundleCreationTimestamp, DateTimeFormatter.ISO_DATE_TIME); + localDateStr = dateTime.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL)); + } catch (DateTimeParseException e) { + localDateStr = "unknown time"; + } + nativeImage.showNewline(); + nativeImage.showMessage(String.format("%sLoaded Bundle from %s", BUNDLE_INFO_MESSAGE_PREFIX, bundleFileName)); + nativeImage.showMessage(String.format("%sBundle created at '%s'", BUNDLE_INFO_MESSAGE_PREFIX, localDateStr)); + nativeImage.showMessage(String.format("%sUsing version: '%s'%s on platform: '%s'%s", BUNDLE_INFO_MESSAGE_PREFIX, bundleVersion, currentVersion, bundlePlatform, currentPlatform)); + } + + private void write() { + properties.put(PROPERTY_KEY_BUNDLE_FILE_VERSION_MAJOR, String.valueOf(BUNDLE_FILE_FORMAT_VERSION_MAJOR)); + properties.put(PROPERTY_KEY_BUNDLE_FILE_VERSION_MINOR, String.valueOf(BUNDLE_FILE_FORMAT_VERSION_MINOR)); + properties.put(PROPERTY_KEY_BUNDLE_FILE_CREATION_TIMESTAMP, ZonedDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)); + boolean imageBuilt = !nativeImage.isDryRun(); + properties.put(PROPERTY_KEY_IMAGE_BUILT, String.valueOf(imageBuilt)); + if (imageBuilt) { + properties.put(PROPERTY_KEY_BUILT_WITH_CONTAINER, String.valueOf(false)); + } + properties.put(PROPERTY_KEY_NATIVE_IMAGE_PLATFORM, NativeImage.platform); + properties.put(PROPERTY_KEY_NATIVE_IMAGE_VERSION, NativeImage.getNativeImageVersion()); + NativeImage.ensureDirectoryExists(bundlePropertiesFile.getParent()); + try (OutputStream outputStream = Files.newOutputStream(bundlePropertiesFile)) { + Properties p = new Properties(); + p.putAll(properties); + p.store(outputStream, "Native Image bundle file properties"); + } catch (IOException e) { + throw NativeImage.showError("Creating bundle properties file " + bundlePropertiesFileName + " failed", e); + } + } + } +} 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 0edb545a979c..2a7aaaeae4b1 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 @@ -26,6 +26,7 @@ import java.io.File; import java.nio.file.Paths; +import java.util.List; import java.util.regex.Pattern; import org.graalvm.compiler.options.OptionType; @@ -37,16 +38,17 @@ class CmdLineOptionHandler extends NativeImage.OptionHandler { - private static final String helpText = NativeImage.getResource("/Help.txt"); - private static final String helpExtraText = NativeImage.getResource("/HelpExtra.txt"); + private static final String HELP_TEXT = NativeImage.getResource("/Help.txt"); + private static final String HELP_EXTRA_TEXT = NativeImage.getResource("/HelpExtra.txt"); + static final String VERBOSE_OPTION = "--verbose"; + static final String DRY_RUN_OPTION = "--dry-run"; + static final String DEBUG_ATTACH_OPTION = "--debug-attach"; /* Defunct legacy options that we have to accept to maintain backward compatibility */ - private static final String verboseServerOption = "--verbose-server"; - private static final String serverOptionPrefix = "--server-"; + private static final String VERBOSE_SERVER_OPTION = "--verbose-server"; + private static final String SERVER_OPTION_PREFIX = "--server-"; - public static final String DEBUG_ATTACH_OPTION = "--debug-attach"; - - private static final String javaRuntimeVersion = System.getProperty("java.runtime.version"); + private static final String JAVA_RUNTIME_VERSION = System.getProperty("java.runtime.version"); boolean useDebugAttach = false; @@ -71,7 +73,7 @@ private boolean consume(ArgumentQueue args, String headArg) { case "--help": args.poll(); singleArgumentCheck(args, headArg); - nativeImage.showMessage(helpText); + nativeImage.showMessage(HELP_TEXT); nativeImage.showNewline(); nativeImage.apiOptionHandler.printOptions(nativeImage::showMessage, false); nativeImage.showNewline(); @@ -82,20 +84,15 @@ private boolean consume(ArgumentQueue args, String headArg) { case "--version": args.poll(); singleArgumentCheck(args, headArg); - String message; - if (NativeImage.IS_AOT) { - message = System.getProperty("java.vm.version"); - } else { - message = "native-image " + NativeImage.graalvmVersion + " " + NativeImage.graalvmConfig; - } - message += " (Java Version " + javaRuntimeVersion + ")"; + String message = NativeImage.getNativeImageVersion(); + message += " (Java Version " + JAVA_RUNTIME_VERSION + ")"; nativeImage.showMessage(message); System.exit(ExitStatus.OK.getValue()); return true; case "--help-extra": args.poll(); singleArgumentCheck(args, headArg); - nativeImage.showMessage(helpExtraText); + nativeImage.showMessage(HELP_EXTRA_TEXT); nativeImage.apiOptionHandler.printOptions(nativeImage::showMessage, true); nativeImage.showNewline(); nativeImage.optionRegistry.showOptions(OptionUtils.MacroOptionKind.Macro, true, nativeImage::showMessage); @@ -109,7 +106,7 @@ private boolean consume(ArgumentQueue args, String headArg) { NativeImage.showError(headArg + " requires a " + File.pathSeparator + " separated list of directories"); } for (String configDir : configPath.split(File.pathSeparator)) { - nativeImage.addMacroOptionRoot(nativeImage.canonicalize(Paths.get(configDir))); + nativeImage.addMacroOptionRoot(Paths.get(configDir)); } return true; case "--exclude-config": @@ -124,11 +121,11 @@ private boolean consume(ArgumentQueue args, String headArg) { } nativeImage.addExcludeConfig(Pattern.compile(excludeJar), Pattern.compile(excludeConfig)); return true; - case DefaultOptionHandler.verboseOption: + case VERBOSE_OPTION: args.poll(); - nativeImage.setVerbose(true); + nativeImage.addVerbose(); return true; - case "--dry-run": + case DRY_RUN_OPTION: args.poll(); nativeImage.setDryRun(true); return true; @@ -145,12 +142,21 @@ private boolean consume(ArgumentQueue args, String headArg) { String optionNames = args.poll(); nativeImage.setPrintFlagsWithExtraHelpOptionQuery(optionNames); return true; - case verboseServerOption: + case BundleSupport.UNLOCK_BUNDLE_SUPPORT_OPTION: + args.poll(); + BundleSupport.allowBundleSupport = true; + return true; + case VERBOSE_SERVER_OPTION: args.poll(); NativeImage.showWarning("Ignoring server-mode native-image argument " + headArg + "."); return true; } + if (headArg.startsWith(BundleSupport.BUNDLE_OPTION)) { + nativeImage.bundleSupport = BundleSupport.create(nativeImage, args.poll(), args); + return true; + } + if (headArg.startsWith(DEBUG_ATTACH_OPTION)) { if (useDebugAttach) { throw NativeImage.showError("The " + DEBUG_ATTACH_OPTION + " option can only be used once."); @@ -166,10 +172,10 @@ private boolean consume(ArgumentQueue args, String headArg) { return true; } - if (headArg.startsWith(serverOptionPrefix)) { + if (headArg.startsWith(SERVER_OPTION_PREFIX)) { args.poll(); NativeImage.showWarning("Ignoring server-mode native-image argument " + headArg + "."); - String serverOptionCommand = headArg.substring(serverOptionPrefix.length()); + String serverOptionCommand = headArg.substring(SERVER_OPTION_PREFIX.length()); if (!serverOptionCommand.startsWith("session=")) { /* * All but the --server-session=... option used to exit(0). We want to simulate that @@ -188,4 +194,11 @@ private static void singleArgumentCheck(ArgumentQueue args, String arg) { NativeImage.showError("Option " + arg + " cannot be combined with other options."); } } + + @Override + void addFallbackBuildArgs(List buildArgs) { + if (nativeImage.isVerbose()) { + buildArgs.add(VERBOSE_OPTION); + } + } } 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 1062c12038d3..1cef750e4dcd 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 @@ -33,12 +33,12 @@ import java.util.ArrayList; import java.util.List; +import com.oracle.svm.core.option.BundleMember; import com.oracle.svm.core.option.OptionOrigin; import com.oracle.svm.driver.NativeImage.ArgumentQueue; class DefaultOptionHandler extends NativeImage.OptionHandler { - static final String verboseOption = "--verbose"; private static final String requireValidJarFileMessage = "-jar requires a valid jarfile"; private static final String newStyleClasspathOptionName = "--class-path"; @@ -188,7 +188,8 @@ public boolean consume(ArgumentQueue args) { if (headArg.startsWith("@") && !disableAtFiles) { args.poll(); headArg = headArg.substring(1); - Path argFile = Paths.get(headArg); + Path origArgFile = Paths.get(headArg); + Path argFile = nativeImage.bundleSupport != null ? nativeImage.bundleSupport.substituteAuxiliaryPath(origArgFile, BundleMember.Role.Input) : origArgFile; NativeImage.NativeImageArgsProcessor processor = nativeImage.new NativeImageArgsProcessor(OptionOrigin.argFilePrefix + argFile); readArgFile(argFile).forEach(processor::accept); List leftoverArgs = processor.apply(false); @@ -413,20 +414,26 @@ private void processModulePathArgs(String mpArgs) { } } - private void handleJarFileArg(Path filePath) { - if (Files.isDirectory(filePath)) { - NativeImage.showError(filePath + " is a directory. (" + requireValidJarFileMessage + ")"); + private void handleJarFileArg(Path jarFilePath) { + if (Files.isDirectory(jarFilePath)) { + NativeImage.showError(jarFilePath + " is a directory. (" + requireValidJarFileMessage + ")"); } - if (!NativeImage.processJarManifestMainAttributes(filePath, nativeImage::handleMainClassAttribute)) { - NativeImage.showError("No manifest in " + filePath); + String jarFileName = jarFilePath.getFileName().toString(); + String jarSuffix = ".jar"; + String jarFileNameBase; + if (jarFileName.endsWith(jarSuffix)) { + jarFileNameBase = jarFileName.substring(0, jarFileName.length() - jarSuffix.length()); + } else { + jarFileNameBase = jarFileName; } - nativeImage.addCustomImageClasspath(filePath); - } - - @Override - void addFallbackBuildArgs(List buildArgs) { - if (nativeImage.isVerbose()) { - buildArgs.add(verboseOption); + if (!jarFileNameBase.isEmpty()) { + String origin = "manifest from " + jarFilePath.toUri(); + nativeImage.addPlainImageBuilderArg(NativeImage.injectHostedOptionOrigin(nativeImage.oHName + jarFileNameBase, origin)); + } + Path finalFilePath = nativeImage.bundleSupport != null ? nativeImage.bundleSupport.substituteClassPath(jarFilePath) : jarFilePath; + if (!NativeImage.processJarManifestMainAttributes(finalFilePath, nativeImage::handleMainClassAttribute)) { + NativeImage.showError("No manifest in " + finalFilePath); } + nativeImage.addCustomImageClasspath(finalFilePath); } } diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MacroOptionHandler.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MacroOptionHandler.java index 5965ddcd1b01..102ff101f804 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MacroOptionHandler.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MacroOptionHandler.java @@ -30,7 +30,6 @@ import com.oracle.svm.core.OS; import com.oracle.svm.core.option.OptionUtils.InvalidMacroException; -import com.oracle.svm.core.util.ClasspathUtils; import com.oracle.svm.driver.MacroOption.AddedTwiceException; import com.oracle.svm.driver.MacroOption.VerboseInvalidMacroException; import com.oracle.svm.driver.NativeImage.ArgumentQueue; @@ -94,12 +93,12 @@ private void applyEnabled(MacroOption.EnabledOption enabledOption, String argume config.modulePathBuild = modulePathBuild; } - enabledOption.forEachPropertyValue(config, "ImageBuilderClasspath", entry -> nativeImage.addImageBuilderClasspath(ClasspathUtils.stringToClasspath(entry)), PATH_SEPARATOR_REGEX); - - boolean explicitImageModulePath = enabledOption.forEachPropertyValue( - config, "ImageModulePath", entry -> nativeImage.addImageModulePath(ClasspathUtils.stringToClasspath(entry)), PATH_SEPARATOR_REGEX); - boolean explicitImageClasspath = enabledOption.forEachPropertyValue( - config, "ImageClasspath", entry -> nativeImage.addImageClasspath(ClasspathUtils.stringToClasspath(entry)), PATH_SEPARATOR_REGEX); + enabledOption.forEachPropertyValue(config, + "ImageBuilderClasspath", entry -> nativeImage.addImageBuilderClasspath(Path.of(entry)), PATH_SEPARATOR_REGEX); + boolean explicitImageModulePath = enabledOption.forEachPropertyValue(config, + "ImageModulePath", entry -> nativeImage.addImageModulePath(Path.of((entry))), PATH_SEPARATOR_REGEX); + boolean explicitImageClasspath = enabledOption.forEachPropertyValue(config, + "ImageClasspath", entry -> NativeImage.expandAsteriskClassPathElement(entry).forEach(nativeImage::addImageClasspath), PATH_SEPARATOR_REGEX); if (!explicitImageModulePath && !explicitImageClasspath) { NativeImage.getJars(imageJarsDirectory).forEach(nativeImage::addImageClasspath); } 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 d2350107d0b8..1a00fa86eaed 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 @@ -77,6 +77,7 @@ import com.oracle.svm.core.OS; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.option.BundleMember; import com.oracle.svm.core.option.OptionUtils; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.ClasspathUtils; @@ -108,6 +109,16 @@ private static String getPlatform() { return (OS.getCurrent().className + "-" + SubstrateUtil.getArchitectureName()).toLowerCase(); } + static String getNativeImageVersion() { + String message; + if (IS_AOT) { + message = System.getProperty("java.vm.version"); + } else { + message = "native-image " + graalvmVersion + " " + graalvmConfig; + } + return message; + } + static final String graalvmVersion = System.getProperty("org.graalvm.version", "dev"); static final String graalvmConfig = System.getProperty("org.graalvm.config", "CE"); static final String graalvmVendor = System.getProperty("org.graalvm.vendor", "Oracle Corporation"); @@ -160,6 +171,10 @@ public String poll() { return queue.poll(); } + public void push(String arg) { + queue.push(arg); + } + public String peek() { return queue.peek(); } @@ -171,6 +186,10 @@ public boolean isEmpty() { public int size() { return queue.size(); } + + public List snapshot() { + return new ArrayList<>(queue); + } } abstract static class OptionHandler { @@ -243,10 +262,10 @@ private static String oR(OptionKey option) { protected final BuildConfiguration config; - private final Map userConfigProperties = new HashMap<>(); + final Map userConfigProperties = new HashMap<>(); private final Map propertyFileSubstitutionValues = new HashMap<>(); - private boolean verbose = Boolean.valueOf(System.getenv("VERBOSE_GRAALVM_LAUNCHERS")); + private int verbose = Boolean.valueOf(System.getenv("VERBOSE_GRAALVM_LAUNCHERS")) ? 1 : 0; private boolean diagnostics = false; String diagnosticsDir; private boolean jarOptionMode = false; @@ -263,6 +282,8 @@ private static String oR(OptionKey option) { private long imageBuilderPid = -1; + BundleSupport bundleSupport; + protected static class BuildConfiguration { /* @@ -542,14 +563,7 @@ public List getImageClasspath() { * @return native-image (i.e. image build) arguments */ public List getBuildArgs() { - if (args.isEmpty()) { - return Collections.emptyList(); - } - List buildArgs = new ArrayList<>(); - buildArgs.addAll(Arrays.asList("--configurations-path", rootDir.toString())); - buildArgs.addAll(Arrays.asList("--configurations-path", rootDir.resolve(Paths.get("lib", "svm")).toString())); - buildArgs.addAll(args); - return buildArgs; + return args; } /** @@ -646,7 +660,7 @@ private ArrayList createFallbackBuildArgs() { buildArgs.add(oH + "+" + SubstrateOptions.ParseRuntimeOptions.getName()); Path imagePathPath; try { - imagePathPath = canonicalize(Paths.get(imagePath)); + imagePathPath = canonicalize(imagePath); } catch (NativeImage.NativeImageError | InvalidPathException e) { throw showError("The given " + oHPath + imagePath + " argument does not specify a valid path", e); } @@ -660,7 +674,7 @@ private ArrayList createFallbackBuildArgs() { return path; } }) - .map(ClasspathUtils::classpathToString) + .map(Path::toString) .collect(Collectors.joining(File.pathSeparator)); if (!isPortable[0]) { showWarning("The produced fallback image will not be portable, because not all classpath entries" + @@ -679,6 +693,7 @@ private ArrayList createFallbackBuildArgs() { buildArgs.add(FallbackExecutor.class.getName()); buildArgs.add(imageName); + defaultOptionHandler.addFallbackBuildArgs(buildArgs); for (OptionHandler handler : optionHandlers) { handler.addFallbackBuildArgs(buildArgs); } @@ -712,17 +727,18 @@ public boolean buildFallbackImage() { private final DriverMetaInfProcessor metaInfProcessor; + static final String CONFIG_FILE_ENV_VAR_KEY = "NATIVE_IMAGE_CONFIG_FILE"; + protected NativeImage(BuildConfiguration config) { this.config = config; this.metaInfProcessor = new DriverMetaInfProcessor(); - String configFileEnvVarKey = "NATIVE_IMAGE_CONFIG_FILE"; - String configFile = System.getenv(configFileEnvVarKey); + String configFile = System.getenv(CONFIG_FILE_ENV_VAR_KEY); if (configFile != null && !configFile.isEmpty()) { try { userConfigProperties.putAll(loadProperties(canonicalize(Paths.get(configFile)))); } catch (NativeImageError | Exception e) { - showError("Invalid environment variable " + configFileEnvVarKey, e); + showError("Invalid environment variable " + CONFIG_FILE_ENV_VAR_KEY, e); } } @@ -731,6 +747,8 @@ protected NativeImage(BuildConfiguration config) { /* Discover supported MacroOptions */ optionRegistry = new MacroOption.Registry(); + optionRegistry.addMacroOptionRoot(config.rootDir); + optionRegistry.addMacroOptionRoot(config.rootDir.resolve(Paths.get("lib", "svm"))); cmdLineOptionHandler = new CmdLineOptionHandler(this); @@ -743,7 +761,9 @@ protected NativeImage(BuildConfiguration config) { } void addMacroOptionRoot(Path configDir) { - optionRegistry.addMacroOptionRoot(canonicalize(configDir)); + Path origRootDir = canonicalize(configDir); + Path rootDir = bundleSupport != null ? bundleSupport.substituteClassPath(origRootDir) : origRootDir; + optionRegistry.addMacroOptionRoot(rootDir); } protected void registerOptionHandler(OptionHandler handler) { @@ -763,7 +783,7 @@ protected Path getUserConfigDir() { return Paths.get(userHomeStr); } - protected static void ensureDirectoryExists(Path dir) { + static void ensureDirectoryExists(Path dir) { if (Files.exists(dir)) { if (!Files.isDirectory(dir)) { throw showError("File " + dir + " is not a directory"); @@ -907,23 +927,6 @@ static String injectHostedOptionOrigin(String option, String origin) { return option; } - static void processManifestMainAttributes(Path path, BiConsumer manifestConsumer) { - if (path.endsWith(ClasspathUtils.cpWildcardSubstitute)) { - if (!Files.isDirectory(path.getParent())) { - throw NativeImage.showError("Cannot expand wildcard: '" + path + "' is not a directory"); - } - try { - Files.list(path.getParent()) - .filter(ClasspathUtils::isJar) - .forEach(p -> processJarManifestMainAttributes(p, manifestConsumer)); - } catch (IOException e) { - throw NativeImage.showError("Error while expanding wildcard for '" + path + "'", e); - } - } else if (ClasspathUtils.isJar(path)) { - processJarManifestMainAttributes(path, manifestConsumer); - } - } - static boolean processJarManifestMainAttributes(Path jarFilePath, BiConsumer manifestConsumer) { try (JarFile jarFile = new JarFile(jarFilePath.toFile())) { Manifest manifest = jarFile.getManifest(); @@ -944,17 +947,6 @@ void handleMainClassAttribute(Path jarFilePath, Attributes mainAttributes) { } String origin = "manifest from " + jarFilePath.toUri(); addPlainImageBuilderArg(NativeImage.injectHostedOptionOrigin(oHClass + mainClassValue, origin)); - String jarFileName = jarFilePath.getFileName().toString(); - String jarSuffix = ".jar"; - String jarFileNameBase; - if (jarFileName.endsWith(jarSuffix)) { - jarFileNameBase = jarFileName.substring(0, jarFileName.length() - jarSuffix.length()); - } else { - jarFileNameBase = jarFileName; - } - if (!jarFileNameBase.isEmpty()) { - addPlainImageBuilderArg(NativeImage.injectHostedOptionOrigin(oHName + jarFileNameBase, origin)); - } } void handleClassPathAttribute(LinkedHashSet destination, Path jarFilePath, Attributes mainAttributes) { @@ -962,7 +954,7 @@ void handleClassPathAttribute(LinkedHashSet destination, Path jarFilePath, /* Missing Class-Path Attribute is tolerable */ if (classPathValue != null) { for (String cp : classPathValue.split(" +")) { - Path manifestClassPath = ClasspathUtils.stringToClasspath(cp); + Path manifestClassPath = Path.of(cp); if (!manifestClassPath.isAbsolute()) { /* Resolve relative manifestClassPath against directory containing jar */ manifestClassPath = jarFilePath.getParent().resolve(manifestClassPath); @@ -1008,6 +1000,9 @@ private int completeImageBuild() { } if (shouldAddCWDToCP()) { + if (useBundle()) { + throw NativeImage.showError("Bundle support requires -cp or -p to be set (implicit current directory classpath unsupported)."); + } addImageClasspath(Paths.get(".")); } imageClasspath.addAll(customImageClasspath); @@ -1045,7 +1040,6 @@ private int completeImageBuild() { imageBuilderJavaArgs.addAll(getAgentArguments()); mainClass = getHostedOptionFinalArgumentValue(imageBuilderArgs, oHClass); - imagePath = getHostedOptionFinalArgumentValue(imageBuilderArgs, oHPath); boolean buildExecutable = imageBuilderArgs.stream().noneMatch(arg -> arg.contains(oHEnableSharedLibraryFlag)); boolean listModules = imageBuilderArgs.stream().anyMatch(arg -> arg.contains(oH + "+" + "ListModules")); boolean printFlags = imageBuilderArgs.stream().anyMatch(arg -> arg.contains(enablePrintFlags) || arg.contains(enablePrintFlagsWithExtraHelp)); @@ -1112,7 +1106,42 @@ private int completeImageBuild() { } } - imageName = getHostedOptionFinalArgumentValue(imageBuilderArgs, oHName); + ArgumentEntry imageNameEntry = getHostedOptionFinalArgument(imageBuilderArgs, oHName).orElseThrow(); + imageName = imageNameEntry.value; + ArgumentEntry imagePathEntry = getHostedOptionFinalArgument(imageBuilderArgs, oHPath).orElseThrow(); + imagePath = Path.of(imagePathEntry.value); + Path imageNamePath = Path.of(imageName); + Path imageNamePathParent = imageNamePath.getParent(); + if (imageNamePathParent != null) { + /* Readjust imageName & imagePath so that imageName is just a simple fileName */ + imageName = imageNamePath.getFileName().toString(); + if (!imageNamePathParent.isAbsolute()) { + imageNamePathParent = imagePath.resolve(imageNamePathParent); + } + if (!Files.isDirectory(imageNamePathParent)) { + throw NativeImage.showError("Writing image to non-existent directory " + imageNamePathParent + " is not allowed."); + } + if (!Files.isWritable(imageNamePathParent)) { + throw NativeImage.showError("Writing image to directory without write access " + imageNamePathParent + " is not possible."); + } + imagePath = imageNamePathParent; + /* Update arguments passed to builder */ + updateArgumentEntryValue(imageBuilderArgs, imageNameEntry, imageName); + updateArgumentEntryValue(imageBuilderArgs, imagePathEntry, imagePath.toString()); + } + if (useBundle()) { + /* + * In creation-mode, we are at the point where we know the final imagePath and imageName + * that we can now use to derive a bundle name in case none was set so far. + */ + String bundleName = imageName.endsWith(BundleSupport.BUNDLE_FILE_EXTENSION) ? imageName : imageName + BundleSupport.BUNDLE_FILE_EXTENSION; + bundleSupport.updateBundleLocation(imagePath.resolve(bundleName), false); + + /* The imagePath has to be redirected to be within the bundle */ + imagePath = bundleSupport.substituteImagePath(imagePath); + /* and we need to adjust the argument that passes the imagePath to the builder */ + updateArgumentEntryValue(imageBuilderArgs, imagePathEntry, imagePath.toString()); + } if (!leftoverArgs.isEmpty()) { String prefix = "Unrecognized option" + (leftoverArgs.size() == 1 ? ": " : "s: "); @@ -1148,30 +1177,51 @@ private int completeImageBuild() { return buildImage(finalImageBuilderJavaArgs, imageBuilderClasspath, imageBuilderModulePath, imageBuilderArgs, finalImageClasspath, finalImageModulePath); } + private static void updateArgumentEntryValue(List argList, ArgumentEntry listEntry, String newValue) { + APIOptionHandler.BuilderArgumentParts argParts = APIOptionHandler.BuilderArgumentParts.from(argList.get(listEntry.index)); + argParts.optionValue = newValue; + argList.set(listEntry.index, argParts.toString()); + } + private static String getLocationAgnosticArgPrefix(String argPrefix) { VMError.guarantee(argPrefix.startsWith(oH) && argPrefix.endsWith("="), "argPrefix has to be a hosted option that ends with \"=\""); return "^" + argPrefix.substring(0, argPrefix.length() - 1) + "(@[^=]*)?" + argPrefix.substring(argPrefix.length() - 1); } - protected static String getHostedOptionFinalArgumentValue(List args, String argPrefix) { - List values = getHostedOptionArgumentValues(args, argPrefix); - return values.isEmpty() ? null : values.get(values.size() - 1); + private static String getHostedOptionFinalArgumentValue(List args, String argPrefix) { + return getHostedOptionFinalArgument(args, argPrefix).map(entry -> entry.value).orElse(null); } - protected static List getHostedOptionArgumentValues(List args, String argPrefix) { - ArrayList values = new ArrayList<>(); + private static Optional getHostedOptionFinalArgument(List args, String argPrefix) { + List values = getHostedOptionArgumentValues(args, argPrefix); + return values.isEmpty() ? Optional.empty() : Optional.of(values.get(values.size() - 1)); + } + + private static List getHostedOptionArgumentValues(List args, String argPrefix) { + ArrayList values = new ArrayList<>(); String locationAgnosticArgPrefix = getLocationAgnosticArgPrefix(argPrefix); Pattern pattern = Pattern.compile(locationAgnosticArgPrefix); - for (String arg : args) { + for (int i = 0; i < args.size(); i++) { + String arg = args.get(i); Matcher matcher = pattern.matcher(arg); if (matcher.find()) { - values.add(arg.substring(matcher.group().length())); + values.add(new ArgumentEntry(i, arg.substring(matcher.group().length()))); } } return values; } + private static final class ArgumentEntry { + private final int index; + private final String value; + + private ArgumentEntry(int index, String value) { + this.index = index; + this.value = value; + } + } + private boolean shouldAddCWDToCP() { if (config.buildFallbackImage() || printFlagsOptionQuery != null || printFlagsWithExtraHelpOptionQuery != null) { return false; @@ -1183,6 +1233,11 @@ private boolean shouldAddCWDToCP() { return false; } + if (useBundle() && bundleSupport.loadBundle) { + /* If bundle was loaded we have valid -cp and/or -p from within the bundle */ + return false; + } + /* If no customImageClasspath was specified put "." on classpath */ return customImageClasspath.isEmpty() && imageModulePath.isEmpty(); } @@ -1190,8 +1245,8 @@ private boolean shouldAddCWDToCP() { private List getAgentArguments() { List args = new ArrayList<>(); String agentOptions = ""; - List traceClassInitializationOpts = getHostedOptionArgumentValues(imageBuilderArgs, oHTraceClassInitialization); - List traceObjectInstantiationOpts = getHostedOptionArgumentValues(imageBuilderArgs, oHTraceObjectInstantiation); + List traceClassInitializationOpts = getHostedOptionArgumentValues(imageBuilderArgs, oHTraceClassInitialization); + List traceObjectInstantiationOpts = getHostedOptionArgumentValues(imageBuilderArgs, oHTraceObjectInstantiation); if (!traceClassInitializationOpts.isEmpty()) { agentOptions = getAgentOptions(traceClassInitializationOpts, "c"); } @@ -1213,8 +1268,8 @@ private List getAgentArguments() { return args; } - private static String getAgentOptions(List options, String optionName) { - return options.stream().flatMap(optValue -> Arrays.stream(optValue.split(","))).map(clazz -> optionName + "=" + clazz).collect(Collectors.joining(",")); + private static String getAgentOptions(List options, String optionName) { + return options.stream().flatMap(optValue -> Arrays.stream(optValue.value.split(","))).map(clazz -> optionName + "=" + clazz).collect(Collectors.joining(",")); } private String targetPlatform = null; @@ -1253,15 +1308,15 @@ private void addTargetArguments() { } } - private String mainClass; - private String imageName; - private String imagePath; + String mainClass; + String imageName; + Path imagePath; - protected static List createImageBuilderArgs(ArrayList imageArgs, LinkedHashSet imagecp, LinkedHashSet imagemp) { + protected static List createImageBuilderArgs(List imageArgs, List imagecp, List imagemp) { List result = new ArrayList<>(); if (!imagecp.isEmpty()) { result.add(SubstrateOptions.IMAGE_CLASSPATH_PREFIX); - result.add(imagecp.stream().map(ClasspathUtils::classpathToString).collect(Collectors.joining(File.pathSeparator))); + result.add(imagecp.stream().map(Path::toString).collect(Collectors.joining(File.pathSeparator))); } if (!imagemp.isEmpty()) { result.add(SubstrateOptions.IMAGE_MODULEPATH_PREFIX); @@ -1332,7 +1387,15 @@ protected int buildImage(List javaArgs, LinkedHashSet cp, LinkedHa */ arguments.addAll(Arrays.asList(SubstrateOptions.WATCHPID_PREFIX, "" + ProcessProperties.getProcessID())); } - List finalImageBuilderArgs = createImageBuilderArgs(imageArgs, imagecp, imagemp); + + BiFunction substituteAuxiliaryPath = useBundle() ? bundleSupport::substituteAuxiliaryPath : (a, b) -> a; + Function imageArgsTransformer = rawArg -> apiOptionHandler.transformBuilderArgument(rawArg, substituteAuxiliaryPath); + List finalImageArgs = imageArgs.stream().map(imageArgsTransformer).collect(Collectors.toList()); + Function substituteClassPath = useBundle() ? bundleSupport::substituteClassPath : Function.identity(); + List finalImageClassPath = imagecp.stream().map(substituteClassPath).collect(Collectors.toList()); + Function substituteModulePath = useBundle() ? bundleSupport::substituteModulePath : Function.identity(); + List finalImageModulePath = imagemp.stream().map(substituteModulePath).collect(Collectors.toList()); + List finalImageBuilderArgs = createImageBuilderArgs(finalImageArgs, finalImageClassPath, finalImageModulePath); /* Construct ProcessBuilder command from final arguments */ List command = new ArrayList<>(); @@ -1347,17 +1410,15 @@ protected int buildImage(List javaArgs, LinkedHashSet cp, LinkedHa // write to the diagnostics dir ReportUtils.report("command line arguments", diagnosticsDir, "command-line", "txt", printWriter -> printWriter.write(commandLine)); } else { - showVerboseMessage(isVerbose() || dryRun, "Executing ["); - showVerboseMessage(isVerbose() || dryRun, commandLine); - showVerboseMessage(isVerbose() || dryRun, "]"); + showVerboseMessage(isVerbose(), "Executing ["); + showVerboseMessage(isVerbose(), commandLine); + showVerboseMessage(isVerbose(), "]"); } if (dryRun) { return ExitStatus.OK.getValue(); } - int exitStatus = ExitStatus.DRIVER_TO_BUILDER_ERROR.getValue(); - Process p = null; try { ProcessBuilder pb = new ProcessBuilder(); @@ -1366,7 +1427,7 @@ protected int buildImage(List javaArgs, LinkedHashSet cp, LinkedHa sanitizeJVMEnvironment(pb.environment()); p = pb.inheritIO().start(); imageBuilderPid = p.pid(); - exitStatus = p.waitFor(); + return p.waitFor(); } catch (IOException | InterruptedException e) { throw showError(e.getMessage()); } finally { @@ -1374,7 +1435,10 @@ protected int buildImage(List javaArgs, LinkedHashSet cp, LinkedHa p.destroy(); } } - return exitStatus; + } + + boolean useBundle() { + return bundleSupport != null; } private static void sanitizeJVMEnvironment(Map environment) { @@ -1443,26 +1507,33 @@ protected static void build(BuildConfiguration config, Function expandAsteriskClassPathElement(String cp) { + String separators = Pattern.quote(File.separator); + if (OS.getCurrent().equals(OS.WINDOWS)) { + separators += "/"; /* on Windows also / is accepted as valid separator */ + } + List components = new ArrayList<>(List.of(cp.split("[" + separators + "]"))); + int lastElementIndex = components.size() - 1; + if (lastElementIndex >= 0 && "*".equals(components.get(lastElementIndex))) { + components.remove(lastElementIndex); + Path searchDir = Path.of(String.join(File.separator, components)); + try (Stream filesInSearchDir = Files.list(searchDir)) { + return filesInSearchDir.filter(NativeImage::hasJarFileSuffix).collect(Collectors.toList()); + } catch (IOException e) { + throw NativeImage.showError("Class path element asterisk (*) expansion failed for directory " + searchDir); + } + } + return List.of(Path.of(cp)); + } + + private static boolean hasJarFileSuffix(Path p) { + return p.getFileName().toString().toLowerCase().endsWith(".jar"); } /** @@ -1645,10 +1738,13 @@ private void addImageClasspathEntry(LinkedHashSet destination, Path classp return; } - if (!imageClasspath.contains(classpathEntry) && !customImageClasspath.contains(classpathEntry)) { - destination.add(classpathEntry); - processManifestMainAttributes(classpathEntry, (jarFilePath, attributes) -> handleClassPathAttribute(destination, jarFilePath, attributes)); - processClasspathNativeImageMetaInf(classpathEntry); + Path classpathEntryFinal = bundleSupport != null ? bundleSupport.substituteModulePath(classpathEntry) : classpathEntry; + if (!imageClasspath.contains(classpathEntryFinal) && !customImageClasspath.contains(classpathEntryFinal)) { + destination.add(classpathEntryFinal); + if (ClasspathUtils.isJar(classpathEntryFinal)) { + processJarManifestMainAttributes(classpathEntryFinal, (jarFilePath, attributes) -> handleClassPathAttribute(destination, jarFilePath, attributes)); + } + processClasspathNativeImageMetaInf(classpathEntryFinal); } } @@ -1656,15 +1752,16 @@ void addCustomJavaArgs(String javaArg) { customJavaArgs.add(javaArg); } - void setVerbose(boolean val) { - verbose = val; + void addVerbose() { + verbose += 1; } void setDiagnostics(boolean val) { diagnostics = val; diagnosticsDir = Paths.get("reports", ReportUtils.timeStampedFileName("diagnostics", "")).toString(); if (val) { - verbose = true; + addVerbose(); + addVerbose(); } } @@ -1685,7 +1782,15 @@ private void enableModulePathBuild() { } boolean isVerbose() { - return verbose; + return verbose > 0; + } + + boolean isVVerbose() { + return verbose > 1; + } + + boolean isVVVerbose() { + return verbose > 2; } boolean isDiagnostics() { diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/metainf/NativeImageMetaInfWalker.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/metainf/NativeImageMetaInfWalker.java index df5d2335411d..389331073061 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/metainf/NativeImageMetaInfWalker.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/metainf/NativeImageMetaInfWalker.java @@ -24,19 +24,20 @@ */ package com.oracle.svm.driver.metainf; -import com.oracle.svm.core.util.ClasspathUtils; import java.io.IOException; import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.FileSystemNotFoundException; import java.nio.file.FileSystems; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.oracle.svm.core.util.ClasspathUtils; public class NativeImageMetaInfWalker { @@ -60,38 +61,25 @@ public static void walkMetaInfForCPEntry(Path classpathEntry, NativeImageMetaInf Path nativeImageMetaInfBase = classpathEntry.resolve(Paths.get(nativeImageMetaInf)); processNativeImageMetaInf(classpathEntry, nativeImageMetaInfBase, metaInfProcessor); } else { - List jarFileMatches = Collections.emptyList(); - if (classpathEntry.endsWith(ClasspathUtils.cpWildcardSubstitute)) { - try { - jarFileMatches = Files.list(classpathEntry.getParent()) - .filter(ClasspathUtils::isJar) - .collect(Collectors.toList()); - } catch (NoSuchFileException e) { - /* Fallthrough */ - } - } else if (ClasspathUtils.isJar(classpathEntry)) { - jarFileMatches = Collections.singletonList(classpathEntry); - } - - for (Path jarFile : jarFileMatches) { - URI jarFileURI = URI.create("jar:" + jarFile.toUri()); + if (ClasspathUtils.isJar(classpathEntry)) { + URI jarFileURI = URI.create("jar:" + classpathEntry.toUri()); FileSystem probeJarFS; try { probeJarFS = FileSystems.newFileSystem(jarFileURI, Collections.emptyMap()); } catch (UnsupportedOperationException e) { probeJarFS = null; - metaInfProcessor.showWarning(ClasspathUtils.classpathToString(classpathEntry) + " does not describe valid jarfile" + (jarFileMatches.size() > 1 ? "s" : "")); + metaInfProcessor.showWarning(classpathEntry + " does not describe valid jar-file"); } if (probeJarFS != null) { try (FileSystem jarFS = probeJarFS) { Path nativeImageMetaInfBase = jarFS.getPath("/" + nativeImageMetaInf); - processNativeImageMetaInf(jarFile, nativeImageMetaInfBase, metaInfProcessor); + processNativeImageMetaInf(classpathEntry, nativeImageMetaInfBase, metaInfProcessor); } } } } } catch (IOException | FileSystemNotFoundException e) { - throw new MetaInfWalkException("Invalid classpath entry " + ClasspathUtils.classpathToString(classpathEntry), e); + throw new MetaInfWalkException("Invalid classpath entry " + classpathEntry, e); } } @@ -99,10 +87,8 @@ private static void processNativeImageMetaInf(Path classpathEntry, Path nativeIm if (Files.isDirectory(nativeImageMetaInfBase)) { for (MetaInfFileType fileType : MetaInfFileType.values()) { List nativeImageMetaInfFiles; - try { - nativeImageMetaInfFiles = Files.walk(nativeImageMetaInfBase) - .filter(p -> p.endsWith(fileType.fileName)) - .collect(Collectors.toList()); + try (Stream pathStream = Files.walk(nativeImageMetaInfBase)) { + nativeImageMetaInfFiles = pathStream.filter(p -> p.endsWith(fileType.fileName)).collect(Collectors.toList()); } catch (IOException e) { throw new MetaInfWalkException("Processing " + nativeImageMetaInfBase.toUri() + " failed.", e); } diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/RuntimeCompilationFeature.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/RuntimeCompilationFeature.java index 98de52d1072b..e578f76cf83a 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/RuntimeCompilationFeature.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/RuntimeCompilationFeature.java @@ -133,7 +133,7 @@ public static class Options { public static final HostedOptionKey PrintRuntimeCompilationCallTree = new HostedOptionKey<>(false); @Option(help = "Maximum number of methods allowed for runtime compilation.")// - public static final HostedOptionKey MaxRuntimeCompileMethods = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final HostedOptionKey MaxRuntimeCompileMethods = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); @Option(help = "Enforce checking of maximum number of methods allowed for runtime compilation. Useful for checking in the gate that the number of methods does not go up without a good reason.")// public static final HostedOptionKey EnforceMaxRuntimeCompileMethods = new HostedOptionKey<>(false); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java index 3009ce09cf21..322aa5bac90b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java @@ -59,6 +59,7 @@ import com.oracle.svm.core.util.ClasspathUtils; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.ClassUtil; import jdk.internal.module.Modules; @@ -110,7 +111,7 @@ public void collectResources(ResourceCollector resourceCollector) { for (Path classpathFile : classLoaderSupport.classpath()) { try { if (Files.isDirectory(classpathFile)) { - scanDirectory(classpathFile, resourceCollector, classLoaderSupport); + scanDirectory(classpathFile, resourceCollector); } else if (ClasspathUtils.isJar(classpathFile)) { scanJar(classpathFile, resourceCollector); } @@ -142,7 +143,7 @@ private static void collectResourceFromModule(ResourceCollector resourceCollecto } } - private static void scanDirectory(Path root, ResourceCollector collector, NativeImageClassLoaderSupport support) throws IOException { + private static void scanDirectory(Path root, ResourceCollector collector) throws IOException { Map> matchedDirectoryResources = new HashMap<>(); Set allEntries = new HashSet<>(); @@ -166,8 +167,8 @@ private static void scanDirectory(Path root, ResourceCollector collector, Native } try (Stream pathStream = Files.list(entry)) { Stream filtered = pathStream; - if (support.excludeDirectoriesRoot.equals(entry)) { - filtered = filtered.filter(Predicate.not(support.excludeDirectories::contains)); + if (ClassUtil.CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES_ROOT.equals(entry)) { + filtered = filtered.filter(Predicate.not(ClassUtil.CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES::contains)); } filtered.forEach(queue::push); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureHandler.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureHandler.java index a181fc315fb4..5292a0bfea9f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureHandler.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureHandler.java @@ -68,7 +68,7 @@ public class FeatureHandler { public static class Options { @APIOption(name = "features") // @Option(help = "A comma-separated list of fully qualified Feature implementation classes")// - public static final HostedOptionKey Features = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey Features = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); private static List userEnabledFeatures() { return Options.Features.getValue().values(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LinkAtBuildTimeSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LinkAtBuildTimeSupport.java index aa8b0d2d6ea7..04c3a8702554 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LinkAtBuildTimeSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LinkAtBuildTimeSupport.java @@ -60,11 +60,11 @@ public final class LinkAtBuildTimeSupport { static final class Options { @APIOption(name = "link-at-build-time", defaultValue = "")// @Option(help = "file:doc-files/LinkAtBuildTimeHelp.txt")// - public static final HostedOptionKey LinkAtBuildTime = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final HostedOptionKey LinkAtBuildTime = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); @APIOption(name = "link-at-build-time-paths")// @Option(help = "file:doc-files/LinkAtBuildTimePathsHelp.txt")// - public static final HostedOptionKey LinkAtBuildTimePaths = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final HostedOptionKey LinkAtBuildTimePaths = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); } private final String javaIdentifier = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderOptions.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderOptions.java index 24a5a3afea53..30067d790e64 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderOptions.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderOptions.java @@ -37,16 +37,16 @@ public class NativeImageClassLoaderOptions { @APIOption(name = "add-exports", extra = true, valueSeparator = {APIOption.WHITESPACE_SEPARATOR, '='})// @Option(help = "Value " + AddExportsAndOpensFormat + " updates to export to , regardless of module declaration." + " can be ALL-UNNAMED to export to all unnamed modules.")// - public static final HostedOptionKey AddExports = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final HostedOptionKey AddExports = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); @APIOption(name = "add-opens", extra = true, valueSeparator = {APIOption.WHITESPACE_SEPARATOR, '='})// @Option(help = "Value " + AddExportsAndOpensFormat + " updates to open to , regardless of module declaration.")// - public static final HostedOptionKey AddOpens = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final HostedOptionKey AddOpens = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); @APIOption(name = "add-reads", extra = true, valueSeparator = {APIOption.WHITESPACE_SEPARATOR, '='})// @Option(help = "Value " + AddReadsFormat + " updates to read , regardless of module declaration." + " can be ALL-UNNAMED to read all unnamed modules.")// - public static final HostedOptionKey AddReads = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final HostedOptionKey AddReads = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); @APIOption(name = "list-modules")// @Option(help = "List observable modules and exit.")// diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java index f02f9823bf3c..686ba5943b43 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java @@ -89,6 +89,7 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.annotation.SubstrateAnnotationExtractor; import com.oracle.svm.hosted.option.HostedOptionParser; +import com.oracle.svm.util.ClassUtil; import com.oracle.svm.util.ModuleSupport; import com.oracle.svm.util.ReflectionUtil; @@ -217,7 +218,7 @@ public boolean noEntryForURI(EconomicSet set) { protected static class Util { static URL[] verifyClassPathAndConvertToURLs(String[] classpath) { - Stream pathStream = new LinkedHashSet<>(Arrays.asList(classpath)).stream().flatMap(Util::toClassPathEntries); + Stream pathStream = new LinkedHashSet<>(Arrays.asList(classpath)).stream().map(Path::of).filter(Util::verifyClassPathEntry); return pathStream.map(v -> { try { return toRealPath(v).toUri().toURL(); @@ -235,19 +236,14 @@ static Path toRealPath(Path p) { } } - static Stream toClassPathEntries(String classPathEntry) { - Path entry = ClasspathUtils.stringToClasspath(classPathEntry); - if (entry.endsWith(ClasspathUtils.cpWildcardSubstitute)) { - try { - return Files.list(entry.getParent()).filter(ClasspathUtils::isJar); - } catch (IOException e) { - return Stream.empty(); - } + private static boolean verifyClassPathEntry(Path cpEntry) { + if (ClasspathUtils.isJar(cpEntry)) { + return true; } - if (Files.isReadable(entry)) { - return Stream.of(entry); + if (Files.isDirectory(cpEntry) && Files.isReadable(cpEntry)) { + return true; } - return Stream.empty(); + return false; } static Path urlToPath(URL url) { @@ -583,14 +579,6 @@ Optional getMainClassFromModule(Object module) { return ((Module) module).getDescriptor().mainClass(); } - final Path excludeDirectoriesRoot = Paths.get("/"); - final Set excludeDirectories = getExcludeDirectories(); - - private Set getExcludeDirectories() { - return Stream.of("dev", "sys", "proc", "etc", "var", "tmp", "boot", "lost+found") - .map(excludeDirectoriesRoot::resolve).collect(Collectors.toUnmodifiableSet()); - } - private final class LoadClassHandler { private final ForkJoinPool executor; @@ -690,7 +678,7 @@ private void loadClassesFromPath(Path path) { } } else { URI container = path.toUri(); - loadClassesFromPath(container, path, excludeDirectoriesRoot, excludeDirectories); + loadClassesFromPath(container, path, ClassUtil.CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES_ROOT, ClassUtil.CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageOptions.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageOptions.java index 9361e116f822..03565b59cb03 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageOptions.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageOptions.java @@ -57,7 +57,7 @@ public class NativeImageOptions { "environment. Note that enabling features not present within the target environment " + "may result in application crashes. The specific options available are target " + "platform dependent. See --list-cpu-features for feature list.", type = User)// - public static final HostedOptionKey CPUFeatures = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey CPUFeatures = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @APIOption(name = "list-cpu-features")// @Option(help = "Show CPU features specific to the target platform and exit.", type = User)// @@ -72,7 +72,7 @@ public class NativeImageOptions { "option to the empty string. The specific options available are target platform " + "dependent. See --list-cpu-features for feature list. The default values are: " + "AMD64: 'AVX,AVX2'; AArch64: ''", type = User)// - public static final HostedOptionKey RuntimeCheckedCPUFeatures = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey RuntimeCheckedCPUFeatures = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "Overrides CPUFeatures and uses the native architecture, i.e., the architecture of a machine that builds an image. NativeArchitecture takes precedence over CPUFeatures", type = User)// public static final HostedOptionKey NativeArchitecture = new HostedOptionKey<>(false); 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 6e5e3f69fa07..9d9a51d2e2d3 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 @@ -195,8 +195,9 @@ public ProgressReporter(OptionValues options) { builderIO = NativeImageSystemIOWrappers.singleton(); } - if (SubstrateOptions.BuildOutputJSONFile.hasBeenSet(options)) { - jsonHelper = new ProgressReporterJsonHelper(SubstrateOptions.BuildOutputJSONFile.getValue(options)); + Optional buildOutputJSONFile = SubstrateOptions.BuildOutputJSONFile.getValue(options).lastValue(); + if (buildOutputJSONFile.isPresent()) { + jsonHelper = new ProgressReporterJsonHelper(buildOutputJSONFile.get()); } else { jsonHelper = null; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporterJsonHelper.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporterJsonHelper.java index 8dad5cf5ad1b..787111c6feae 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporterJsonHelper.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporterJsonHelper.java @@ -25,7 +25,6 @@ package com.oracle.svm.hosted; -import java.io.File; import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.management.OperatingSystemMXBean; @@ -45,9 +44,9 @@ class ProgressReporterJsonHelper { private static final String RESOURCE_USAGE_KEY = "resource_usage"; private final Map statsHolder = new HashMap<>(); - private final String jsonOutputFile; + private final Path jsonOutputFile; - ProgressReporterJsonHelper(String outFile) { + ProgressReporterJsonHelper(Path outFile) { this.jsonOutputFile = outFile; } @@ -105,9 +104,8 @@ public void putResourceUsage(ResourceUsageKey key, Object value) { public Path printToFile() { recordSystemFixedValues(); - final File file = new File(jsonOutputFile); String description = "image statistics in json"; - return ReportUtils.report(description, file.getAbsoluteFile().toPath(), out -> { + return ReportUtils.report(description, jsonOutputFile.toAbsolutePath(), out -> { try { new JsonWriter(out).print(statsHolder); } catch (IOException e) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java index b4c2921f67fd..c397aaf00f60 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java @@ -106,10 +106,10 @@ public final class ResourcesFeature implements InternalFeature { public static class Options { @Option(help = "Regexp to match names of resources to be included in the image.", type = OptionType.User)// - public static final HostedOptionKey IncludeResources = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final HostedOptionKey IncludeResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); @Option(help = "Regexp to match names of resources to be excluded from the image.", type = OptionType.User)// - public static final HostedOptionKey ExcludeResources = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final HostedOptionKey ExcludeResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); } private boolean sealed = false; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index 2d7accdf3088..d5c4e9ea3dba 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -130,11 +130,11 @@ public static class Options { public static final HostedOptionKey TraceSecurityServices = new HostedOptionKey<>(false); @Option(help = "Comma-separated list of additional security service types (fully qualified class names) for automatic registration. Note that these must be JCA compliant.")// - public static final HostedOptionKey AdditionalSecurityServiceTypes = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final HostedOptionKey AdditionalSecurityServiceTypes = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); @Option(help = "Comma-separated list of additional security provider fully qualified class names to mark as used." + "Note that this option is only necessary if you use custom engine classes not available in JCA that are not JCA compliant.")// - public static final HostedOptionKey AdditionalSecurityProviders = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final HostedOptionKey AdditionalSecurityProviders = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); } /* diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java index 06b150233980..d3cd01ac6816 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java @@ -104,10 +104,11 @@ public static class Options { public static final HostedOptionKey TraceServiceLoaderFeature = new HostedOptionKey<>(false); @Option(help = "Comma-separated list of services that should be excluded", type = OptionType.Expert) // - public static final HostedOptionKey ServiceLoaderFeatureExcludeServices = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey ServiceLoaderFeatureExcludeServices = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "Comma-separated list of service providers that should be excluded", type = OptionType.Expert) // - public static final HostedOptionKey ServiceLoaderFeatureExcludeServiceProviders = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey ServiceLoaderFeatureExcludeServiceProviders = new HostedOptionKey<>( + LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/NativeLibraries.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/NativeLibraries.java index 909dfb8e01e2..fd81b9c67349 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/NativeLibraries.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/NativeLibraries.java @@ -329,9 +329,8 @@ private static LinkedHashSet initCLibraryPath() { /* Probe for static JDK libraries in user-specified CLibraryPath directory */ if (staticLibsDir == null) { - for (String clibPathComponent : SubstrateOptions.CLibraryPath.getValue().values()) { - Path path = Paths.get(clibPathComponent); - Predicate hasStaticLibraryCLibraryPath = s -> Files.isRegularFile(path.resolve(getStaticLibraryName(s))); + for (Path clibPathComponent : SubstrateOptions.CLibraryPath.getValue().values()) { + Predicate hasStaticLibraryCLibraryPath = s -> Files.isRegularFile(clibPathComponent.resolve(getStaticLibraryName(s))); if (defaultBuiltInLibraries.stream().allMatch(hasStaticLibraryCLibraryPath)) { return libraryPaths; } @@ -552,7 +551,7 @@ private Class getDirectives(ResolvedJavaType type } public void finish() { - libraryPaths.addAll(SubstrateOptions.CLibraryPath.getValue().values()); + libraryPaths.addAll(SubstrateOptions.CLibraryPath.getValue().values().stream().map(Path::toString).collect(Collectors.toList())); for (NativeCodeContext context : compilationUnitToContext.values()) { if (context.isInConfiguration()) { libraries.addAll(context.getDirectives().getLibraries()); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationOptions.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationOptions.java index 496e30429b3c..4f7ed1eabfb9 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationOptions.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationOptions.java @@ -89,7 +89,7 @@ private static class InitializationValueEager extends InitializationValueTransfo deprecated = "Currently there is no replacement for this option. Try using --initialize-at-run-time or use the non-API option -H:ClassInitialization directly.", // defaultValue = "", customHelp = "A comma-separated list of classes (and implicitly all of their subclasses) that are initialized both at runtime and during image building") // @Option(help = "A comma-separated list of classes appended with their initialization strategy (':build_time', ':rerun', or ':run_time')", type = OptionType.User)// - public static final HostedOptionKey ClassInitialization = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final HostedOptionKey ClassInitialization = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); @Option(help = "Instead of abort, only warn if --initialize-at-build-time= is used.", type = OptionType.Debug)// public static final HostedOptionKey AllowDeprecatedInitializeAllClassesAtBuildTime = new HostedOptionKey<>(false); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/diagnostic/HostedHeapDumpFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/diagnostic/HostedHeapDumpFeature.java index 4777ead34685..e2694178360a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/diagnostic/HostedHeapDumpFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/diagnostic/HostedHeapDumpFeature.java @@ -53,7 +53,7 @@ public class HostedHeapDumpFeature implements InternalFeature { static class Options { @Option(help = "Dump the heap at a specific time during image building." + "The option accepts a list of comma separated phases, any of: during-analysis, after-analysis, before-compilation.")// - public static final HostedOptionKey DumpHeap = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey DumpHeap = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); } enum Phases { 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 ce5e951fc4ee..8b696294e5a3 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 @@ -68,7 +68,7 @@ protected CCLinkerInvocation(AbstractImage.NativeImageKind imageKind, NativeLibr public static class Options { @Option(help = "Pass the provided raw option that will be appended to the linker command to produce the final binary. The possible options are platform specific and passed through without any validation.")// - public static final HostedOptionKey NativeLinkerOption = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final HostedOptionKey NativeLinkerOption = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); } protected final List additionalPreOptions = new ArrayList<>(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceCache.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceCache.java index 68413b39c5a4..2893c9b4a8a3 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceCache.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceCache.java @@ -68,7 +68,7 @@ public class SourceCache { /** * A list of all entries in the source search path specified by the user on the command line. */ - protected static final List sourcePathEntries = new ArrayList<>(); + protected static final List sourcePathEntries = new ArrayList<>(); /** * A list of root directories which may contain source files from which this cache can be @@ -132,7 +132,7 @@ private void addGraalSources() { modulePathEntries.stream() .forEach(modulePathEntry -> addGraalSourceRoot(modulePathEntry, true)); sourcePathEntries.stream() - .forEach(sourcePathEntry -> addGraalSourceRoot(Paths.get(sourcePathEntry), false)); + .forEach(sourcePathEntry -> addGraalSourceRoot(sourcePathEntry, false)); } private void addGraalSourceRoot(Path sourcePath, boolean fromClassPath) { @@ -187,7 +187,7 @@ private void addApplicationSources() { modulePathEntries.stream() .forEach(modulePathEntry -> addApplicationSourceRoot(modulePathEntry, true)); sourcePathEntries.stream() - .forEach(sourcePathEntry -> addApplicationSourceRoot(Paths.get(sourcePathEntry), false)); + .forEach(sourcePathEntry -> addApplicationSourceRoot(sourcePathEntry, false)); } protected void addApplicationSourceRoot(Path sourceRoot, boolean fromClassPath) { @@ -524,7 +524,7 @@ static void addModulePathEntry(Path path) { * * @param path The path to add. */ - static void addSourcePathEntry(String path) { + static void addSourcePathEntry(Path path) { sourcePathEntries.add(path); } } @@ -548,7 +548,7 @@ public void afterAnalysis(AfterAnalysisAccess access) { } // also add any necessary source path entries if (SubstrateOptions.DebugInfoSourceSearchPath.getValue() != null) { - for (String searchPathEntry : SubstrateOptions.DebugInfoSourceSearchPath.getValue().values()) { + for (Path searchPathEntry : SubstrateOptions.DebugInfoSourceSearchPath.getValue().values()) { SourceCache.addSourcePathEntry(searchPathEntry); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java index e51510595e50..77ee830af081 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java @@ -160,7 +160,7 @@ public class LocalizationFeature implements InternalFeature { public static class Options { @Option(help = "Comma separated list of bundles to be included into the image.", type = OptionType.User)// - public static final HostedOptionKey IncludeResourceBundles = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey IncludeResourceBundles = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "Make all hosted charsets available at run time")// public static final HostedOptionKey AddAllCharsets = new HostedOptionKey<>(false); @@ -173,7 +173,7 @@ public static class Options { public static final HostedOptionKey DefaultCharset = new HostedOptionKey<>(Charset.defaultCharset().name()); @Option(help = "Comma separated list of locales to be included into the image. The default locale is included in the list automatically if not present.", type = OptionType.User)// - public static final HostedOptionKey IncludeLocales = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.commaSeparated()); + public static final HostedOptionKey IncludeLocales = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "Make all hosted locales available at run time.", type = OptionType.User)// public static final HostedOptionKey IncludeAllLocales = new HostedOptionKey<>(false); @@ -185,7 +185,7 @@ public static class Options { public static final HostedOptionKey LocalizationSubstituteLoadLookup = new HostedOptionKey<>(true); @Option(help = "Regular expressions matching which bundles should be compressed.", type = OptionType.User)// - public static final HostedOptionKey LocalizationCompressBundles = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + public static final HostedOptionKey LocalizationCompressBundles = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); @Option(help = "Compress the bundles in parallel.", type = OptionType.Expert)// public static final HostedOptionKey LocalizationCompressInParallel = new HostedOptionKey<>(true); diff --git a/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/PermissionsFeature.java b/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/PermissionsFeature.java index 073a2ec49c0a..9e5429a83f5f 100644 --- a/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/PermissionsFeature.java +++ b/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/PermissionsFeature.java @@ -51,8 +51,6 @@ import java.util.function.Predicate; import java.util.stream.Collectors; -import com.oracle.truffle.api.TruffleLanguage; -import jdk.vm.ci.meta.ModifiersProvider; import org.graalvm.compiler.debug.DebugContext; import org.graalvm.compiler.graph.NodeInputList; import org.graalvm.compiler.nodes.Invoke; @@ -74,6 +72,7 @@ import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.option.BundleMember; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.LocatableMultiOptionValue; import com.oracle.svm.core.util.UserError; @@ -83,8 +82,10 @@ import com.oracle.svm.hosted.SVMHost; import com.oracle.svm.hosted.config.ConfigurationParserUtils; import com.oracle.svm.util.ClassUtil; +import com.oracle.truffle.api.TruffleLanguage; import jdk.vm.ci.common.JVMCIError; +import jdk.vm.ci.meta.ModifiersProvider; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; import sun.misc.Unsafe; @@ -106,17 +107,18 @@ public class PermissionsFeature implements Feature { private static final String CONFIG = "truffle-language-permissions-config.json"; public static class Options { - @Option(help = "Path to file where to store report of Truffle language privilege access.") public static final HostedOptionKey TruffleTCKPermissionsReportFile = new HostedOptionKey<>( - null); + @Option(help = "Path to file where to store report of Truffle language privilege access.")// + public static final HostedOptionKey TruffleTCKPermissionsReportFile = new HostedOptionKey<>(null); - @Option(help = "Comma separated list of exclude files.") public static final HostedOptionKey TruffleTCKPermissionsExcludeFiles = new HostedOptionKey<>( - LocatableMultiOptionValue.Paths.commaSeparated()); + @BundleMember(role = BundleMember.Role.Input)// + @Option(help = "Comma separated list of exclude files.")// + public static final HostedOptionKey TruffleTCKPermissionsExcludeFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); - @Option(help = "Maximal depth of a stack trace.", type = OptionType.Expert) public static final HostedOptionKey TruffleTCKPermissionsMaxStackTraceDepth = new HostedOptionKey<>( - -1); + @Option(help = "Maximal depth of a stack trace.", type = OptionType.Expert)// + public static final HostedOptionKey TruffleTCKPermissionsMaxStackTraceDepth = new HostedOptionKey<>(-1); - @Option(help = "Maximum number of erroneous privileged accesses reported.", type = OptionType.Expert) public static final HostedOptionKey TruffleTCKPermissionsMaxErrors = new HostedOptionKey<>( - 100); + @Option(help = "Maximum number of erroneous privileged accesses reported.", type = OptionType.Expert)// + public static final HostedOptionKey TruffleTCKPermissionsMaxErrors = new HostedOptionKey<>(100); } /** @@ -810,7 +812,7 @@ public boolean test(BaseMethodNode methodNode, BaseMethodNode callerNode, Linked private static final class ResourceAsOptionDecorator extends HostedOptionKey { ResourceAsOptionDecorator(String defaultValue) { - super(new LocatableMultiOptionValue.Strings(Collections.singletonList(defaultValue))); + super(LocatableMultiOptionValue.Strings.buildWithDefaults(defaultValue)); } } diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ClassUtil.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ClassUtil.java index 3bda6db8b949..d3653553658d 100644 --- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ClassUtil.java +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ClassUtil.java @@ -24,7 +24,22 @@ */ package com.oracle.svm.util; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + public final class ClassUtil { + + public static final Path CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES_ROOT = Paths.get("/"); + public static final Set CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES = getClassModulePathExcludeDirectories(); + + private static Set getClassModulePathExcludeDirectories() { + return Stream.of("dev", "sys", "proc", "etc", "var", "tmp", "boot", "lost+found") + .map(CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES_ROOT::resolve).collect(Collectors.toUnmodifiableSet()); + } + /** * Alternative to {@link Class#getSimpleName} that does not probe an enclosing class or method, * which can fail when they cannot be loaded.