diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/options/OptionsParser.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/options/OptionsParser.java index 2bbd56942f23..2ca22282012d 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/options/OptionsParser.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/options/OptionsParser.java @@ -38,11 +38,12 @@ import java.util.function.Function; import java.util.regex.Pattern; -import jdk.graal.compiler.core.common.LibGraalSupport; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.EconomicSet; import org.graalvm.collections.MapCursor; +import jdk.graal.compiler.core.common.LibGraalSupport; + /** * This class contains methods for parsing Graal options and matching them against a set of * {@link OptionDescriptors}. The {@link OptionDescriptors} are loaded via a {@link ServiceLoader}. @@ -343,6 +344,7 @@ public static boolean collectFuzzyMatches(Iterable toSearch, S * @param toSearch the entries search * @param name the name to search for * @param matches the collection to which fuzzy matches of {@code name} will be added + * @param extractor functor that maps entry to String * @return whether any fuzzy matches were found */ public static boolean collectFuzzyMatches(Iterable toSearch, String name, Collection matches, Function extractor) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ClassInclusionPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ClassInclusionPolicy.java index d2da3fd59396..74759c00ba71 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ClassInclusionPolicy.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ClassInclusionPolicy.java @@ -41,7 +41,7 @@ /** * Policy used to determine which classes, methods and fields need to be included in the image when - * the {@code IncludeAllFromPath} and/or {@code IncludeAllFromModule} options are specified + * {@code LayerCreate} sub-options {@code module}, {@code package} or {@code path} are specified * depending on the configuration. */ public abstract class ClassInclusionPolicy { 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 b8a9f5df9415..1eab692d7cfe 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 @@ -1313,23 +1313,9 @@ public enum ReportingMode { @Option(help = "Deprecated, option no longer has any effect.", deprecated = true, deprecationMessage = "It no longer has any effect, and no replacement is available")// public static final HostedOptionKey UseOldMethodHandleIntrinsics = new HostedOptionKey<>(false); - @Option(help = "Include all classes, methods, and fields from given modules", type = OptionType.Debug) // - public static final HostedOptionKey IncludeAllFromModule = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build()); - - @Option(help = "Include all classes, methods, fields, and resources from given paths", type = OptionType.Debug) // - public static final HostedOptionKey IncludeAllFromPath = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build()); - - @Option(help = "Include all classes, methods and fields from the given packages", type = OptionType.Debug) // - public static final HostedOptionKey IncludeAllFromPackage = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); - @Option(help = "Include all classes, methods, fields, and resources from the class path", type = OptionType.Debug) // public static final HostedOptionKey IncludeAllFromClassPath = new HostedOptionKey<>(false); - public static boolean includeAll() { - return IncludeAllFromModule.hasBeenSet() || IncludeAllFromPath.hasBeenSet() || IncludeAllFromPackage.hasBeenSet() || IncludeAllFromClassPath.hasBeenSet(); - } - @Option(help = "Force include include all public types and methods that can be reached using normal Java access rules.")// public static final HostedOptionKey UseBaseLayerInclusionPolicy = new HostedOptionKey<>(false); 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 f3c23d3f637a..6709316454c5 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 @@ -25,16 +25,19 @@ package com.oracle.svm.core.option; +import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.FileSystem; +import java.nio.file.FileSystemNotFoundException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; import com.oracle.svm.core.option.OptionUtils.MacroOptionKind; @@ -153,7 +156,20 @@ protected static URI originURI(String origin) { static List getRedirectionValuesFromPath(Path normalizedRedirPath) throws IOException { if (Files.isReadable(normalizedRedirPath)) { - return Files.readAllLines(normalizedRedirPath); + try (BufferedReader reader = Files.newBufferedReader(normalizedRedirPath)) { + List values = new ArrayList<>(); + while (true) { + String line = reader.readLine(); + if (line == null) { + break; + } + if (line.isEmpty() || line.startsWith("#")) { + continue; + } + values.add(line); + } + return values; + } } throw new FileNotFoundException("Unable to read file from " + normalizedRedirPath.toUri()); } @@ -338,17 +354,24 @@ protected JarOptionOrigin(boolean isStable, URI rawOrigin) { public List getRedirectionValues(Path valuesFile) throws IOException { URI jarFileURI = URI.create("jar:" + container()); FileSystem probeJarFS; + boolean cleanup = false; try { - probeJarFS = FileSystems.newFileSystem(jarFileURI, Collections.emptyMap()); - } catch (UnsupportedOperationException e) { - probeJarFS = null; + probeJarFS = FileSystems.getFileSystem(jarFileURI); + } catch (FileSystemNotFoundException e) { + probeJarFS = FileSystems.newFileSystem(jarFileURI, Map.of()); + cleanup = true; } if (probeJarFS == null) { throw new IOException("Unable to create jar file system for " + jarFileURI); } - try (FileSystem fs = probeJarFS) { + try { var normalizedRedirPath = location().getParent().resolve(valuesFile).normalize(); - return getRedirectionValuesFromPath(normalizedRedirPath); + List values = getRedirectionValuesFromPath(probeJarFS.getPath("/", normalizedRedirPath.toString())); + return values; + } finally { + if (cleanup) { + probeJarFS.close(); + } } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OptionUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OptionUtils.java index 19144f42e34f..56d0752e739c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OptionUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OptionUtils.java @@ -35,7 +35,7 @@ import java.util.stream.Stream; import com.oracle.svm.core.SubstrateUtil; -import com.oracle.svm.core.util.UserError; +import com.oracle.svm.core.option.LocatableMultiOptionValue.ValueWithOrigin; import jdk.graal.compiler.options.OptionDescriptor; import jdk.graal.compiler.options.OptionKey; @@ -45,8 +45,12 @@ */ public class OptionUtils { + public static List resolveOptionValuesRedirection(OptionKey option, ValueWithOrigin valueWithOrigin) { + return resolveOptionValuesRedirection(option, valueWithOrigin.value(), valueWithOrigin.origin()); + } + public static List resolveOptionValuesRedirection(OptionKey option, String optionValue, OptionOrigin origin) { - return Arrays.asList(SubstrateUtil.split(optionValue, ",")).stream() + return Arrays.stream(SubstrateUtil.split(optionValue, ",")) .flatMap(entry -> resolveOptionValueRedirection(option, optionValue, origin, entry)) .collect(Collectors.toList()); } @@ -55,14 +59,14 @@ private static Stream resolveOptionValueRedirection(OptionKey< if (entry.trim().startsWith("@")) { Path valuesFile = Path.of(entry.substring(1)); if (valuesFile.isAbsolute()) { - throw UserError.abort("Option '%s' provided by %s contains value redirection file '%s' that is an absolute path.", - SubstrateOptionsParser.commandArgument(option, optionValue), origin, valuesFile); + throw new AssertionError("Option '" + SubstrateOptionsParser.commandArgument(option, optionValue) + "' provided by " + origin + + " contains value redirection file '" + valuesFile + "' that is an absolute path."); } try { return origin.getRedirectionValues(valuesFile).stream(); } catch (IOException e) { - throw UserError.abort(e, "Option '%s' provided by %s contains invalid option value redirection.", - SubstrateOptionsParser.commandArgument(option, optionValue), origin); + throw new AssertionError("Option '" + SubstrateOptionsParser.commandArgument(option, optionValue) + "' provided by " + origin + + " contains invalid option value redirection.", e); } } else { return Stream.of(entry); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/SubstrateOptionsParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/SubstrateOptionsParser.java index 4fe66dba5417..703cfa306256 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/SubstrateOptionsParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/SubstrateOptionsParser.java @@ -41,7 +41,6 @@ import com.oracle.svm.common.option.CommonOptionParser.OptionParseResult; import com.oracle.svm.common.option.UnsupportedOptionClassException; import com.oracle.svm.core.util.InterruptImageBuilding; -import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.LogUtils; import jdk.graal.compiler.options.OptionDescriptor; @@ -63,8 +62,7 @@ static OptionParseResult parseOption(EconomicMap optio try { return CommonOptionParser.parseOption(options, isHosted, option, valuesMap, optionPrefix, booleanOptionFormat); } catch (UnsupportedOptionClassException e) { - VMError.shouldNotReachHere(e.getMessage()); - return null; + throw new AssertionError("Should not reach here", e); } } @@ -160,7 +158,6 @@ public static double parseDouble(String v) { * @return recommendation for setting a option value (e.g., for option 'Name' and value 'file' * it returns "-H:Name=file") */ - @Platforms(Platform.HOSTED_ONLY.class) public static String commandArgument(OptionKey option, String value) { return commandArgument(option, value, null); } @@ -175,7 +172,6 @@ public static String commandArgument(OptionKey option, String value) { * @return recommendation for setting a option value (e.g., for option 'Name' and value 'file' * it returns "-H:Name=file") */ - @Platforms(Platform.HOSTED_ONLY.class) public static String commandArgument(OptionKey option, String value, String apiOptionName) { /* Ensure descriptor is loaded */ OptionDescriptor optionDescriptor = option.loadDescriptor(); @@ -193,7 +189,9 @@ public static String commandArgument(OptionKey option, String value, String a } if (optionDescriptor.getOptionValueType() == Boolean.class) { - VMError.guarantee(value.equals("+") || value.equals("-"), "Boolean option value can be only + or -"); + if (!value.equals("+") && !value.equals("-")) { + throw new AssertionError("Boolean option value can be only + or -"); + } for (APIOption apiOption : apiOptions) { String selected = selectVariant(apiOption, apiOptionName); if (selected != null) { @@ -240,12 +238,10 @@ public static String commandArgument(OptionKey option, String value, String a } } - @Platforms(Platform.HOSTED_ONLY.class) public static String commandArgument(OptionKey option, String value, String apiOptionName, boolean escape, boolean newLine) { return formatCommandArgument(commandArgument(option, value, apiOptionName), escape, newLine); } - @Platforms(Platform.HOSTED_ONLY.class) public static String commandArgument(OptionKey option, String value, boolean escape, boolean newLine) { return formatCommandArgument(commandArgument(option, value), escape, newLine); } @@ -262,7 +258,9 @@ private static String formatCommandArgument(String optionMessage, boolean escape } private static String selectVariant(APIOption apiOption, String apiOptionName) { - VMError.guarantee(apiOption.name().length > 0, "APIOption requires at least one name"); + if (apiOption.name().length <= 0) { + throw new AssertionError("APIOption requires at least one name"); + } if (!apiOption.deprecated().equals("")) { return null; /* Never select deprecated API options. */ } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/UserError.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/UserError.java index 166316a92599..be25436728bb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/UserError.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/UserError.java @@ -43,6 +43,17 @@ @SuppressWarnings("serial") public class UserError { + /** + * Stop compilation immediately and report the message to the user. + * + * @param format format string (must not start with a lowercase letter) + * @param args arguments for the format string that are {@link #formatArguments(Object...) + * preprocessed} before being sent to {@link String#format(String, Object...)} + */ + public static UserException abort(String format, Object... args) { + throw new UserException(String.format(format, formatArguments(args))); + } + /** * UserException type for all errors that should be reported to the SVM users. */ @@ -74,17 +85,6 @@ public Iterable getMessages() { } } - /** - * Stop compilation immediately and report the message to the user. - * - * @param format format string (must not start with a lowercase letter) - * @param args arguments for the format string that are {@link #formatArguments(Object...) - * preprocessed} before being sent to {@link String#format(String, Object...)} - */ - public static UserException abort(String format, Object... args) { - throw new UserException(String.format(format, formatArguments(args))); - } - /** * Stop compilation immediately and report the message to the user. * 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 0110eb07df67..de407337f12f 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 @@ -31,14 +31,10 @@ import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; -import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.VM; import com.oracle.svm.core.option.OptionOrigin; import com.oracle.svm.core.util.ExitStatus; import com.oracle.svm.driver.NativeImage.ArgumentQueue; -import com.oracle.svm.hosted.imagelayer.LayerArchiveSupport; -import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.ExtendedOption; -import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.LayerOption; import com.oracle.svm.util.LogUtils; import jdk.graal.compiler.options.OptionType; @@ -142,25 +138,6 @@ private boolean consume(ArgumentQueue args, String headArg) { return true; } - if (headArg.startsWith(SubstrateOptions.LAYER_CREATE_OPTION)) { - String layerCreateValue = headArg.substring(SubstrateOptions.LAYER_CREATE_OPTION.length()); - if (!layerCreateValue.isEmpty()) { - LayerOption layerOption = LayerOption.parse(layerCreateValue); - for (ExtendedOption option : layerOption.extendedOptions()) { - switch (option.key()) { - case LayerArchiveSupport.PACKAGE_OPTION -> { - String packageName = option.value(); - String moduleName = nativeImage.systemPackagesToModules.get(packageName); - if (moduleName != null) { - nativeImage.addAddedModules(moduleName); - } - } - } - } - } - return false; - } - if (headArg.startsWith(DEBUG_ATTACH_OPTION)) { if (useDebugAttach) { throw NativeImage.showError("The " + DEBUG_ATTACH_OPTION + " option can only be used once."); 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 ac574b77c091..c7de7ab2ff97 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 @@ -28,9 +28,15 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.option.OptionOrigin; +import com.oracle.svm.core.option.OptionUtils; import com.oracle.svm.driver.NativeImage.ArgumentQueue; +import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.ExtendedOption; +import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.LayerOption; +import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.PackageOptionValue; import com.oracle.svm.util.LogUtils; class DefaultOptionHandler extends NativeImage.OptionHandler { @@ -139,6 +145,32 @@ public boolean consume(ArgumentQueue args) { processClasspathArgs(cpArgs); return true; } + if (headArg.startsWith(nativeImage.oHLayerCreate)) { + String rawLayerCreateValue = headArg.substring(nativeImage.oHLayerCreate.length()); + if (!rawLayerCreateValue.isEmpty()) { + List layerCreateValue = OptionUtils.resolveOptionValuesRedirection(SubstrateOptions.LayerCreate, rawLayerCreateValue, OptionOrigin.from(args.argumentOrigin)); + LayerOption layerOption = LayerOption.parse(layerCreateValue); + for (ExtendedOption option : layerOption.extendedOptions()) { + var packageOptionValue = PackageOptionValue.from(option); + if (packageOptionValue == null) { + continue; + } + String packageName = packageOptionValue.name(); + if (packageOptionValue.isWildcard()) { + nativeImage.systemPackagesToModules.forEach((systemPackageName, moduleName) -> { + if (systemPackageName.startsWith(packageName)) { + nativeImage.addAddedModules(moduleName); + } + }); + } else { + String moduleName = nativeImage.systemPackagesToModules.get(packageName); + if (moduleName != null) { + nativeImage.addAddedModules(moduleName); + } + } + } + } + } if (headArg.startsWith(NativeImage.oH)) { args.poll(); nativeImage.addPlainImageBuilderArg(headArg, args.argumentOrigin); 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 0b7eb0fea53c..ccb581ce3c26 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 @@ -285,6 +285,7 @@ private static String oR(OptionKey option) { final String oHInspectServerContentPath = oH(PointstoOptions.InspectServerContentPath); final String oHDeadlockWatchdogInterval = oH(SubstrateOptions.DeadlockWatchdogInterval); + final String oHLayerCreate = oH(SubstrateOptions.LayerCreate); final Map imageBuilderEnvironment = new HashMap<>(); private final ArrayList imageBuilderArgs = new ArrayList<>(); @@ -1338,6 +1339,10 @@ private int completeImageBuild() { } } + if (mainClass != null && !mainClass.isEmpty() && !Character.isJavaIdentifierStart(mainClass.charAt(0))) { + showError("'%s' is not a valid mainclass. Specify a valid classname for the class that contains the main method.".formatted(mainClass)); + } + if (!extraImageArgs.isEmpty()) { showError("Unrecognized option(s): " + StringUtil.joinSingleQuoted(extraImageArgs.stream().map(ArgumentEntry::value).toList())); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java index 1076e9460e9f..d75fbfbd7707 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java @@ -93,7 +93,7 @@ public void loadAllClasses() throws InterruptedException { } } } - classLoaderSupport.reportBuilderClassesInApplication(); + classLoaderSupport.allClassesLoaded(); } private void findSystemElements(Class systemClass) { 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 bb3022f4bbb4..f907d3cefd37 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 @@ -24,13 +24,6 @@ */ package com.oracle.svm.hosted; -import static com.oracle.svm.core.SubstrateOptions.IncludeAllFromClassPath; -import static com.oracle.svm.core.SubstrateOptions.IncludeAllFromModule; -import static com.oracle.svm.core.SubstrateOptions.IncludeAllFromPackage; -import static com.oracle.svm.core.SubstrateOptions.IncludeAllFromPath; -import static com.oracle.svm.core.util.VMError.guarantee; -import static com.oracle.svm.core.util.VMError.shouldNotReachHere; - import java.io.File; import java.io.IOException; import java.lang.module.Configuration; @@ -62,6 +55,7 @@ import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -75,18 +69,16 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import com.oracle.svm.core.SubstrateUtil; -import com.oracle.svm.util.ModuleSupport; -import org.graalvm.nativeimage.libgraal.LibGraalLoader; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.EconomicSet; import org.graalvm.collections.MapCursor; import org.graalvm.nativeimage.impl.AnnotationExtractor; +import org.graalvm.nativeimage.libgraal.LibGraalLoader; import com.oracle.svm.core.NativeImageClassLoaderOptions; import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; -import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.LocatableMultiOptionValue.ValueWithOrigin; import com.oracle.svm.core.option.OptionOrigin; import com.oracle.svm.core.option.SubstrateOptionsParser; @@ -96,9 +88,11 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.annotation.SubstrateAnnotationExtractor; import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport; +import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.PackageOptionValue; import com.oracle.svm.hosted.option.HostedOptionParser; import com.oracle.svm.util.ClassUtil; import com.oracle.svm.util.LogUtils; +import com.oracle.svm.util.ModuleSupport; import com.oracle.svm.util.ReflectionUtil; import jdk.graal.compiler.debug.GraalError; @@ -128,16 +122,23 @@ public final class NativeImageClassLoaderSupport { public final AnnotationExtractor annotationExtractor; - private Set javaModuleNamesToInclude; - private Set javaPackagesToInclude; - private Set javaPathsToInclude; + private Path layerFile; + + private final Set javaModuleNamesToInclude; + private final Set javaPackagesToInclude; + private final Set javaPathsToInclude; + private boolean includeConfigSealed; + private boolean includeAllFromClassPath; + private LoadClassHandler loadClassHandler; + private Optional libGraalLoader; private List classLoaders; private final Set> classesToIncludeUnconditionally = ConcurrentHashMap.newKeySet(); private final Set includedJavaPackages = ConcurrentHashMap.newKeySet(); + private final Map failedJavaPackageInclusionRequests = new ConcurrentHashMap<>(); private final Method implAddReadsAllUnnamed = ReflectionUtil.lookupMethod(Module.class, "implAddReadsAllUnnamed"); private final Method implAddEnableNativeAccess = ReflectionUtil.lookupMethod(Module.class, "implAddEnableNativeAccess"); @@ -209,6 +210,11 @@ protected NativeImageClassLoaderSupport(ClassLoader defaultSystemClassLoader, St modulepathModuleFinder = ModuleFinder.of(modulepath().toArray(Path[]::new)); annotationExtractor = new SubstrateAnnotationExtractor(); + + javaModuleNamesToInclude = new LinkedHashSet<>(); + javaPackagesToInclude = new LinkedHashSet<>(); + javaPathsToInclude = new LinkedHashSet<>(); + includeConfigSealed = false; } private static Stream toRealPath(Path p) { @@ -241,35 +247,72 @@ public List getClassLoaders() { return classLoaders; } - private static Path stringToPath(String path) { - return Path.of(Path.of(path).toAbsolutePath().toUri().normalize()); + public void addJavaModuleToInclude(String moduleName) { + VMError.guarantee(!includeConfigSealed, "Class inclusion configuration is already sealed."); + javaModuleNamesToInclude.add(moduleName); + } + + public void addJavaPackageToInclude(PackageOptionValue packageOptionValue) { + VMError.guarantee(!includeConfigSealed, "Class inclusion configuration is already sealed."); + javaPackagesToInclude.add(packageOptionValue); + } + + public void addClassPathEntryToInclude(String cpEntry) { + VMError.guarantee(!includeConfigSealed, "Class inclusion configuration is already sealed."); + javaPathsToInclude.add(Path.of(cpEntry)); } public void loadAllClasses(ForkJoinPool executor, ImageClassLoader imageClassLoader) { - guarantee(javaModuleNamesToInclude == null && javaPackagesToInclude == null, "This method should be executed only once."); - javaModuleNamesToInclude = Collections.unmodifiableSet(new HashSet<>(IncludeAllFromModule.getValue(parsedHostedOptions).values())); - /* Verify all modules are present */ - final Set allModules = Stream.concat(modulepathModuleFinder.findAll().stream(), upgradeAndSystemModuleFinder.findAll().stream()) - .map(m -> m.descriptor().name()) - .collect(Collectors.toSet()); - javaModuleNamesToInclude.stream() - .filter(m -> !allModules.contains(m)) - .findAny().ifPresent(m -> missingFromSetOfEntriesError(m, allModules, "module-path", IncludeAllFromModule)); + VMError.guarantee(!includeConfigSealed, "This method should be executed only once."); + includeConfigSealed = true; + + /* Verify all requested modules are present */ + List missingModules = javaModuleNamesToInclude.stream().filter(mn -> findModule(mn).isEmpty()).toList(); + if (!missingModules.isEmpty()) { + boolean plural = missingModules.size() > 1; + String pluralS = plural ? "s" : ""; + throw UserError.abort("Module request%s (module=...) %s %s could not find requested module%s. " + + "Provide a module-path that contains the specified module%s or remove %s from option.", + pluralS, String.join(", ", missingModules), layerCreateOptionStr(), pluralS, + pluralS, plural ? "entries" : "entry"); + } - javaPackagesToInclude = Set.copyOf(IncludeAllFromPackage.getValue(parsedHostedOptions).values()); + /* Verify all requested class-path entries are on the application class-path */ + Set resolvedJavaPathsToInclude = new HashSet<>(); + List missingClassPathEntries = new ArrayList<>(); + javaPathsToInclude.forEach(requestedCPEntry -> { + Optional optResolvedEntry = toRealPath(requestedCPEntry).findAny(); + if (optResolvedEntry.isPresent()) { + Path resolvedEntry = optResolvedEntry.get(); + if (applicationClassPath().contains(resolvedEntry)) { + resolvedJavaPathsToInclude.add(resolvedEntry); + return; + } + } + missingClassPathEntries.add(requestedCPEntry.toString()); + }); - javaPathsToInclude = IncludeAllFromPath.getValue(parsedHostedOptions).values().stream() - .map(NativeImageClassLoaderSupport::stringToPath) - .map(Path::toAbsolutePath) - .collect(Collectors.toUnmodifiableSet()); - /* Verify all paths are present */ - javaPathsToInclude.stream() - .filter(p -> !classpath().contains(p)) - .findAny().ifPresent(p -> missingFromSetOfEntriesError(p, classpath(), "classpath", IncludeAllFromPath)); + if (!missingClassPathEntries.isEmpty()) { + boolean plural = missingModules.size() > 1; + String pluralS = plural ? "s" : ""; + String pluralEntries = plural ? "entries" : "entry"; + throw UserError.abort("Class-path entry request%s (path=...) %s do not match %s on application class-path. " + + "Provide a class-path that contains the %s or remove %s from option.", + pluralS, String.join(", ", missingClassPathEntries), layerCreateOptionStr(), pluralEntries, + pluralEntries, pluralEntries); + } else { + /* + * Replace entries with resolved ones so that they are correctly matched in + * LoadClassHandler.loadClassesFromPath. + */ + javaPathsToInclude.clear(); + javaPathsToInclude.addAll(resolvedJavaPathsToInclude); + } - includeAllFromClassPath = IncludeAllFromClassPath.getValue(parsedHostedOptions); + includeAllFromClassPath = SubstrateOptions.IncludeAllFromClassPath.getValue(parsedHostedOptions); - new LoadClassHandler(executor, imageClassLoader).run(); + loadClassHandler = new LoadClassHandler(executor, imageClassLoader); + loadClassHandler.run(); LibGraalLoader loader = getLibGraalLoader(); if (loader != null) { @@ -285,18 +328,10 @@ public void loadAllClasses(ForkJoinPool executor, ImageClassLoader imageClassLoa } } - private static void missingFromSetOfEntriesError(Object entry, Collection allEntries, String typeOfEntry, - HostedOptionKey option) { - String sortedEntries = allEntries.stream() - .map(Object::toString) - .collect(Collectors.joining(System.lineSeparator() + " ", " ", "")); - - throw UserError.abort("The option %s does not match any of the %s entries. To fix, change the option to match one of the %s entries: %s%s", - SubstrateOptionsParser.commandArgument(option, entry.toString()), - typeOfEntry, - typeOfEntry, - System.lineSeparator(), - sortedEntries); + private String layerCreateOptionStr() { + ValueWithOrigin layerCreateValue = SubstrateOptions.LayerCreate.getValue(getParsedHostedOptions()).lastValueWithOrigin().orElseThrow(); + String layerCreateArgument = SubstrateOptionsParser.commandArgument(SubstrateOptions.LayerCreate, layerCreateValue.value()); + return "specified with '%s' from %s".formatted(layerCreateArgument, layerCreateValue.origin()); } private HostedOptionParser hostedOptionParser; @@ -312,7 +347,7 @@ public void setupHostedOptionParser(List arguments) { * hostedOptionParser.getHostedValues(), so we want to affect the options map before it is * copied. */ - HostedImageLayerBuildingSupport.processLayerOptions(hostedOptionParser.getHostedValues()); + HostedImageLayerBuildingSupport.processLayerOptions(hostedOptionParser.getHostedValues(), this); parsedHostedOptions = new OptionValues(hostedOptionParser.getHostedValues()); } @@ -560,7 +595,7 @@ public static List allLayers(ModuleLayer moduleLayer) { private Stream processOption(OptionKey specificOption) { var valuesWithOrigins = specificOption.getValue(parsedHostedOptions).getValuesWithOrigins(); - Stream parsedOptions = valuesWithOrigins.flatMap(valWithOrig -> { + return valuesWithOrigins.flatMap(valWithOrig -> { try { return Stream.of(asAddExportsAndOpensAndReadsFormatValue(specificOption, valWithOrig)); } catch (FindException e) { @@ -568,7 +603,6 @@ private Stream processOption(OptionKey targetModules) { } @@ -654,11 +704,10 @@ private AddExportsAndOpensAndReadsFormatValue asAddExportsAndOpensAndReadsFormat if (targetModuleNamesList.contains("ALL-UNNAMED")) { targetModules = Collections.emptyList(); } else { - targetModules = targetModuleNamesList.stream().map(mn -> { - return findModule(mn).orElseThrow(() -> { - throw userWarningModuleNotFound(option, mn); - }); - }).collect(Collectors.toList()); + targetModules = targetModuleNamesList.stream() + .map(mn -> findModule(mn) + .orElseThrow(() -> userWarningModuleNotFound(option, mn))) + .collect(Collectors.toList()); } return new AddExportsAndOpensAndReadsFormatValue(module, packageName, targetModules); } @@ -702,6 +751,8 @@ private final class LoadClassHandler { LongAdder entriesProcessed; volatile String currentlyProcessedEntry; boolean initialReport; + Set requestedPackages; + List requestedPackageWildcards; private LoadClassHandler(ForkJoinPool executor, ImageClassLoader imageClassLoader) { this.executor = executor; @@ -710,6 +761,18 @@ private LoadClassHandler(ForkJoinPool executor, ImageClassLoader imageClassLoade entriesProcessed = new LongAdder(); currentlyProcessedEntry = "Unknown Entry"; initialReport = true; + + Set tempRequestedPackages = new LinkedHashSet<>(); + List tempRequestedPackageWildcards = new ArrayList<>(); + for (PackageOptionValue value : javaPackagesToInclude) { + if (value.isWildcard()) { + tempRequestedPackageWildcards.add(value); + } else { + tempRequestedPackages.add(value.name()); + } + } + requestedPackages = Collections.unmodifiableSet(tempRequestedPackages); + requestedPackageWildcards = List.copyOf(tempRequestedPackageWildcards); } private void run() { @@ -751,13 +814,33 @@ private void run() { } finally { scheduledExecutor.shutdown(); } + } - /* Verify all package inclusion requests were successful */ - for (String packageName : javaPackagesToInclude) { - if (!includedJavaPackages.contains(packageName)) { - missingFromSetOfEntriesError(packageName, includedJavaPackages, "package", IncludeAllFromPackage); + /* Report package inclusion requests that did not have any effect. */ + void validatePackageInclusionRequests() { + List unusedRequests = new ArrayList<>(); + for (String requestedPackage : requestedPackages) { + if (!includedJavaPackages.contains(requestedPackage)) { + unusedRequests.add(new PackageOptionValue(requestedPackage, false)); } } + var unusedWildcardRequests = new LinkedHashSet<>(requestedPackageWildcards); + if (!unusedWildcardRequests.isEmpty()) { + for (String includedPackage : includedJavaPackages) { + unusedWildcardRequests.removeIf(wildcardRequest -> includedPackage.startsWith(wildcardRequest.name())); + } + } + if (!(unusedRequests.isEmpty() && unusedWildcardRequests.isEmpty())) { + var requestsStrings = Stream.concat(unusedRequests.stream(), unusedWildcardRequests.stream()) + .map(packageOptionValue -> '\'' + packageOptionValue.toString() + '\'') + .toList(); + boolean plural = requestsStrings.size() > 1; + String pluralS = plural ? "s" : ""; + throw UserError.abort("Package request%s (package=...) %s %s could not find requested package%s. " + + "Provide a class/module-path that contains the package%s or remove %s from option.", + pluralS, String.join(", ", requestsStrings), layerCreateOptionStr(), pluralS, + pluralS, plural ? "entries" : "entry"); + } } private void initModule(ModuleReference moduleReference, boolean moduleRequiresInit) { @@ -809,7 +892,7 @@ private void loadClassesFromPath(Path path) { } catch (ClosedByInterruptException ignored) { throw new InterruptImageBuilding(); } catch (IOException | URISyntaxException e) { - throw shouldNotReachHere(e); + throw VMError.shouldNotReachHere(e); } } else { URI container = path.toUri(); @@ -861,7 +944,7 @@ public FileVisitResult visitFileFailed(Path file, IOException exc) { try { Files.walkFileTree(root, visitor); } catch (IOException ex) { - throw shouldNotReachHere(ex); + throw VMError.shouldNotReachHere(ex); } } @@ -939,15 +1022,13 @@ private void handleClassFileName(URI container, Module module, String className, } classNames.add(className); } - int packageSep = className.lastIndexOf('.'); - String packageName = packageSep > 0 ? className.substring(0, packageSep) : ""; synchronized (packages) { EconomicSet packageNames = packages.get(container); if (packageNames == null) { packageNames = EconomicSet.create(); packages.put(container, packageNames); } - packageNames.add(packageName); + packageNames.add(packageName(className)); } } @@ -957,12 +1038,16 @@ private void handleClassFileName(URI container, Module module, String className, } catch (AssertionError error) { VMError.shouldNotReachHere(error); } catch (Throwable t) { + if (includePackage(packageName(className))) { + // Record unresolvable classes that are from requested packages + failedJavaPackageInclusionRequests.put(className, t); + } ImageClassLoader.handleClassLoadingError(t); } if (clazz != null) { String packageName = clazz.getPackageName(); includedJavaPackages.add(packageName); - if (includeUnconditionally || javaPackagesToInclude.contains(packageName)) { + if (includeUnconditionally || includePackage(packageName)) { classesToIncludeUnconditionally.add(clazz); } if (classRequiresInit) { @@ -971,6 +1056,23 @@ private void handleClassFileName(URI container, Module module, String className, } imageClassLoader.watchdog.recordActivity(); } + + private static String packageName(String className) { + int packageSep = className.lastIndexOf('.'); + return packageSep > 0 ? className.substring(0, packageSep) : ""; + } + + private boolean includePackage(String packageName) { + if (requestedPackages.contains(packageName)) { + return true; + } + for (PackageOptionValue requestedPackageWildcard : requestedPackageWildcards) { + if (packageName.startsWith(requestedPackageWildcard.name())) { + return true; + } + } + return false; + } } public void reportBuilderClassesInApplication() { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java index 932e4a447ae1..b48a5a42b2c7 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java @@ -238,7 +238,7 @@ public SVMHost(OptionValues options, ImageClassLoader loader, ClassInitializatio } layerId = ImageLayerBuildingSupport.buildingImageLayer() ? DynamicImageLayerInfo.singleton().layerNumber : 0; useBaseLayer = ImageLayerBuildingSupport.buildingExtensionLayer(); - if (SubstrateOptions.includeAll()) { + if (ImageLayerBuildingSupport.buildingSharedLayer()) { initializeExcludedFields(); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java index cf61550f735a..175c1934c283 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java @@ -44,7 +44,7 @@ import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; import com.oracle.graal.pointsto.util.TimerCollection; -import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; import com.oracle.svm.hosted.HostedConfiguration; import com.oracle.svm.hosted.SVMHost; import com.oracle.svm.hosted.ameta.CustomTypeFieldHandler; @@ -152,7 +152,7 @@ public void onTypeReachable(AnalysisType type) { /* Rescan the component hub. This will be simplified by GR-60254. */ HostedImageLayerBuildingSupport.singleton().getLoader().rescanHub(type.getComponentType(), ((SVMHost) hostVM).dynamicHub(type).getComponentHub()); } - if (SubstrateOptions.includeAll()) { + if (ImageLayerBuildingSupport.buildingSharedLayer()) { /* * Using getInstanceFields and getStaticFields allows to include the fields from the * substitution class. diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java index faabe5269d9d..f8d97fa8ab1c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java @@ -31,6 +31,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import org.capnproto.ReaderOptions; import org.capnproto.Serialize; @@ -45,15 +46,19 @@ import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.HostedOptionValues; +import com.oracle.svm.core.option.LocatableMultiOptionValue.ValueWithOrigin; +import com.oracle.svm.core.option.OptionUtils; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.ArchiveSupport; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.hosted.NativeImageClassLoaderSupport; import com.oracle.svm.hosted.NativeImageGenerator; import com.oracle.svm.hosted.c.NativeLibraries; import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.ExtendedOption; import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.LayerOption; +import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.PackageOptionValue; import jdk.graal.compiler.core.common.SuppressFBWarnings; import jdk.graal.compiler.options.OptionKey; @@ -148,32 +153,43 @@ public Class lookupClass(boolean optional, String className) { * in {@code HostedOptionKey.onValueUpdate()} because processing this options affects other * option's values, and any intermediate state may lead to a wrong configuration. */ - public static void processLayerOptions(EconomicMap, Object> values) { + public static void processLayerOptions(EconomicMap, Object> values, NativeImageClassLoaderSupport classLoaderSupport) { OptionValues hostedOptions = new OptionValues(values); if (SubstrateOptions.LayerCreate.hasBeenSet(hostedOptions)) { /* The last value wins, GR-55565 will warn about the overwritten values. */ - String layerCreateValue = SubstrateOptions.LayerCreate.getValue(hostedOptions).lastValue().orElseThrow(); + ValueWithOrigin valueWithOrigin = SubstrateOptions.LayerCreate.getValue(hostedOptions).lastValueWithOrigin().orElseThrow(); + String layerCreateValue = String.join(",", OptionUtils.resolveOptionValuesRedirection(SubstrateOptions.LayerCreate, valueWithOrigin)); if (layerCreateValue.isEmpty()) { /* Nothing to do, an empty --layer-create= disables the layer creation. */ } else { LayerOption layerOption = LayerOption.parse(layerCreateValue); - String buildLayer = SubstrateOptionsParser.commandArgument(SubstrateOptions.LayerCreate, ""); + classLoaderSupport.setLayerFile(layerOption.fileName()); + + String layerCreateArg = SubstrateOptionsParser.commandArgument(SubstrateOptions.LayerCreate, layerCreateValue); for (ExtendedOption option : layerOption.extendedOptions()) { switch (option.key()) { case LayerArchiveSupport.MODULE_OPTION -> { - UserError.guarantee(option.value() != null, "Option %s of %s requires a module name argument, e.g., %s=module-name.", option.key(), buildLayer, option.key()); - SubstrateOptions.IncludeAllFromModule.update(values, option.value()); + UserError.guarantee(option.value() != null || option.value().isEmpty(), + "Layer option %s specified with '%s' from %s requires a module name argument, e.g., %s=module-name.", + option.key(), layerCreateArg, valueWithOrigin.origin(), option.key()); + classLoaderSupport.addJavaModuleToInclude(option.value()); + } case LayerArchiveSupport.PACKAGE_OPTION -> { - UserError.guarantee(option.value() != null, "Option %s of %s requires a package name argument, e.g., %s=package-name.", option.key(), buildLayer, option.key()); - SubstrateOptions.IncludeAllFromPackage.update(values, option.value()); + UserError.guarantee(option.value() != null || option.value().isEmpty(), + "Layer option %s specified with '%s' from %s requires a package name argument, e.g., %s=package-name.", + option.key(), layerCreateArg, valueWithOrigin.origin(), option.key()); + classLoaderSupport.addJavaPackageToInclude(Objects.requireNonNull(PackageOptionValue.from(option))); } case LayerArchiveSupport.PATH_OPTION -> { - UserError.guarantee(option.value() != null, "Option %s of %s requires a class-path entry, e.g., %s=path/to/cp-entry.", option.key(), buildLayer, option.key()); - SubstrateOptions.IncludeAllFromPath.update(values, option.value()); + UserError.guarantee(option.value() != null || option.value().isEmpty(), + "Layer option %s specified with '%s' from %s requires a class-path entry, e.g., %s=path/to/cp-entry.", + option.key(), layerCreateArg, valueWithOrigin.origin(), option.key()); + classLoaderSupport.addClassPathEntryToInclude(option.value()); } default -> - throw UserError.abort("Unknown option %s of %s. Use --help-extra for usage instructions.", option.key(), buildLayer); + throw UserError.abort("Unknown layer option %s specified with '%s' from %s. Use --help-extra for usage instructions.", + option.key(), layerCreateArg, valueWithOrigin.origin()); } } @@ -226,8 +242,7 @@ public static HostedImageLayerBuildingSupport initialize(HostedOptionValues valu WriteLayerArchiveSupport writeLayerArchiveSupport = null; ArchiveSupport archiveSupport = new ArchiveSupport(false); if (buildingSharedLayer) { - LayerOption layerOption = LayerOption.parse(SubstrateOptions.LayerCreate.getValue(values).lastValue().orElseThrow()); - writeLayerArchiveSupport = new WriteLayerArchiveSupport(archiveSupport, layerOption.fileName()); + writeLayerArchiveSupport = new WriteLayerArchiveSupport(archiveSupport, imageClassLoader.classLoaderSupport.getLayerFile()); } SVMImageLayerSingletonLoader singletonLoader = null; LoadLayerArchiveSupport loadLayerArchiveSupport = null; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayerOptionsSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayerOptionsSupport.java index 36a2ff78c4a3..02b0fc6b9d4b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayerOptionsSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayerOptionsSupport.java @@ -25,7 +25,7 @@ package com.oracle.svm.hosted.imagelayer; import java.nio.file.Path; -import java.util.Arrays; +import java.util.List; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.util.VMError; @@ -38,16 +38,20 @@ public static LayerOption parse(String layerOptionValue) { VMError.guarantee(!layerOptionValue.isEmpty()); // Given an argument of form layer-file.nil,module=m1,package=p1 // First get the list: [layer-file.nil, module=m1, package=p1] - String[] options = SubstrateUtil.split(layerOptionValue, ","); + return parse(List.of(SubstrateUtil.split(layerOptionValue, ","))); + } + + public static LayerOption parse(List options) { + VMError.guarantee(!options.isEmpty()); // Check for the optional file name - String fileName = null; + Path fileName = null; int skip = 0; - if (options[0].endsWith(LayerArchiveSupport.LAYER_FILE_EXTENSION)) { - fileName = options[0]; + if (options.getFirst().endsWith(LayerArchiveSupport.LAYER_FILE_EXTENSION)) { + fileName = Path.of(options.getFirst()); skip = 1; } - ExtendedOption[] extendedOptions = Arrays.stream(options).skip(skip).map(ExtendedOption::parse).toArray(ExtendedOption[]::new); - return new LayerOption(Path.of(fileName), extendedOptions); + ExtendedOption[] extendedOptions = options.stream().skip(skip).map(ExtendedOption::parse).toArray(ExtendedOption[]::new); + return new LayerOption(fileName, extendedOptions); } } @@ -63,4 +67,24 @@ static ExtendedOption parse(String option) { } } + public record PackageOptionValue(String name, boolean isWildcard) { + + static final String PACKAGE_WILDCARD_SUFFIX = ".*"; + + public static PackageOptionValue from(ExtendedOption extendedOption) { + if (!extendedOption.key().equals(LayerArchiveSupport.PACKAGE_OPTION)) { + return null; + } + String extendedOptionValue = extendedOption.value(); + if (extendedOptionValue.endsWith(PACKAGE_WILDCARD_SUFFIX)) { + return new PackageOptionValue(extendedOptionValue.substring(0, extendedOptionValue.length() - PACKAGE_WILDCARD_SUFFIX.length()), true); + } + return new PackageOptionValue(extendedOptionValue, false); + } + + @Override + public String toString() { + return name + (isWildcard ? PACKAGE_WILDCARD_SUFFIX : ""); + } + } }