Skip to content

[GR-60421] Implement package-wildcards for LayerCreate package suboption. #10756

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand Down Expand Up @@ -343,6 +344,7 @@ public static boolean collectFuzzyMatches(Iterable<OptionDescriptor> 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 <T> boolean collectFuzzyMatches(Iterable<T> toSearch, String name, Collection<T> matches, Function<T, String> extractor) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Boolean> UseOldMethodHandleIntrinsics = new HostedOptionKey<>(false);

@Option(help = "Include all classes, methods, and fields from given modules", type = OptionType.Debug) //
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> 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<AccumulatingLocatableMultiOptionValue.Strings> 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<AccumulatingLocatableMultiOptionValue.Strings> 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<Boolean> 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<Boolean> UseBaseLayerInclusionPolicy = new HostedOptionKey<>(false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -153,7 +156,20 @@ protected static URI originURI(String origin) {

static List<String> getRedirectionValuesFromPath(Path normalizedRedirPath) throws IOException {
if (Files.isReadable(normalizedRedirPath)) {
return Files.readAllLines(normalizedRedirPath);
try (BufferedReader reader = Files.newBufferedReader(normalizedRedirPath)) {
List<String> 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());
}
Expand Down Expand Up @@ -338,17 +354,24 @@ protected JarOptionOrigin(boolean isStable, URI rawOrigin) {
public List<String> 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<String> values = getRedirectionValuesFromPath(probeJarFS.getPath("/", normalizedRedirPath.toString()));
return values;
} finally {
if (cleanup) {
probeJarFS.close();
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -45,8 +45,12 @@
*/
public class OptionUtils {

public static List<String> resolveOptionValuesRedirection(OptionKey<?> option, ValueWithOrigin<String> valueWithOrigin) {
return resolveOptionValuesRedirection(option, valueWithOrigin.value(), valueWithOrigin.origin());
}

public static List<String> 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());
}
Expand All @@ -55,14 +59,14 @@ private static Stream<? extends String> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -63,8 +62,7 @@ static OptionParseResult parseOption(EconomicMap<String, OptionDescriptor> 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);
}
}

Expand Down Expand Up @@ -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);
}
Expand All @@ -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();
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
}
Expand All @@ -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. */
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -74,17 +85,6 @@ public Iterable<String> 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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<NativeImage> {
Expand Down Expand Up @@ -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<String> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ private static <T> String oR(OptionKey<T> option) {

final String oHInspectServerContentPath = oH(PointstoOptions.InspectServerContentPath);
final String oHDeadlockWatchdogInterval = oH(SubstrateOptions.DeadlockWatchdogInterval);
final String oHLayerCreate = oH(SubstrateOptions.LayerCreate);

final Map<String, String> imageBuilderEnvironment = new HashMap<>();
private final ArrayList<String> imageBuilderArgs = new ArrayList<>();
Expand Down Expand Up @@ -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()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public void loadAllClasses() throws InterruptedException {
}
}
}
classLoaderSupport.reportBuilderClassesInApplication();
classLoaderSupport.allClassesLoaded();
}

private void findSystemElements(Class<?> systemClass) {
Expand Down
Loading
Loading