Skip to content

Commit

Permalink
Options to include metadata for module or classpath
Browse files Browse the repository at this point in the history
  • Loading branch information
vjovanov committed Dec 3, 2024
1 parent 6146963 commit 8ca7704
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 27 deletions.
1 change: 1 addition & 0 deletions substratevm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ At runtime, premain runtime options are set along with main class' arguments in
* (GR-58914) `ActiveProcessorCount` must be set at isolate or VM creation time.
* (GR-59326) Ensure builder ForkJoin commonPool parallelism always respects NativeImageOptions.NumberOfThreads.
* (GR-60081) Native Image now targets `armv8.1-a` by default on AArch64. Use `-march=compatibility` for best compatibility or `-march=native` for best performance if the native executable is deployed on the same machine or on a machine with the same CPU features. To list all available machine types, use `-march=list`.
* (GR-54953) Add options `-H:IncludeAllMetadataForClassPathEntry`, `-H:IncludeAllForClassPath`, and `-H:IncludeAllMetadataForModule` for bulk inclusion of reachability metadata.

## GraalVM for JDK 23 (Internal Version 24.1.0)
* (GR-51520) The old class initialization strategy, which was deprecated in GraalVM for JDK 22, is removed. The option `StrictImageHeap` no longer has any effect.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,17 @@ public void setBigBang(BigBang bb) {
this.bb = bb;
}

public static boolean isClassIncludedBase(Class<?> cls) {
Class<?> enclosingClass = cls.getEnclosingClass();
return !Feature.class.isAssignableFrom(cls) && !AnnotationAccess.isAnnotationPresent(cls, TargetClass.class) && (enclosingClass == null || isClassIncludedBase(enclosingClass));
}

/**
* Determine if the given class needs to be included in the image according to the policy.
*/
public boolean isClassIncluded(Class<?> cls) {
Class<?> enclosingClass = cls.getEnclosingClass();
return !Feature.class.isAssignableFrom(cls) && !AnnotationAccess.isAnnotationPresent(cls, TargetClass.class) && (enclosingClass == null || isClassIncluded(enclosingClass));
return isClassIncludedBase(cls) && (enclosingClass == null || isClassIncluded(enclosingClass));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1283,12 +1283,21 @@ public enum ReportingMode {
@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 a given module for dynamic access", type = OptionType.Debug) //
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> IncludeAllMetadataForModule = 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, fields, and resources from given paths for dynamic access", type = OptionType.Debug) //
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> IncludeAllMetadataForClassPathEntry = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build());

@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);

@Option(help = "Include all classes, methods, fields, and resources for dynamic access for the whole classpath", type = OptionType.Debug) //
public static final HostedOptionKey<Boolean> IncludeAllMetadataForClassPath = new HostedOptionKey<>(false);

public static boolean includeAll() {
return IncludeAllFromModule.hasBeenSet() || IncludeAllFromPath.hasBeenSet() || IncludeAllFromClassPath.hasBeenSet();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ public void collectResources(ResourceCollector resourceCollector) {

/* Collect remaining resources from classpath */
classLoaderSupport.classpath().stream().parallel().forEach(classpathFile -> {
boolean includeCurrent = classLoaderSupport.getJavaPathsToInclude().contains(classpathFile) || classLoaderSupport.includeAllFromClassPath();
boolean includeCurrent = classLoaderSupport.getJavaPathsToInclude().contains(classpathFile) || classLoaderSupport.includeAllFromClassPath() ||
classLoaderSupport.getPathsToIncludeMetadata().contains(classpathFile) || classLoaderSupport.isIncludeAllMetadataFromClassPath();
try {
if (Files.isDirectory(classpathFile)) {
scanDirectory(classpathFile, resourceCollector, includeCurrent);
Expand All @@ -132,7 +133,8 @@ public void collectResources(ResourceCollector resourceCollector) {
private void collectResourceFromModule(ResourceCollector resourceCollector, ResourceLookupInfo info) {
ModuleReference moduleReference = info.resolvedModule.reference();
try (ModuleReader moduleReader = moduleReference.open()) {
boolean includeCurrent = classLoaderSupport.getJavaModuleNamesToInclude().contains(info.resolvedModule().name());
boolean includeCurrent = classLoaderSupport.getModuleNamesToInclude().contains(info.resolvedModule().name()) ||
classLoaderSupport.getModuleNamesToIncludeMetadata().contains(info.resolvedModule().name());
List<ConditionalResource> resourcesFound = new ArrayList<>();
moduleReader.list().forEach(resourceName -> {
var conditionsWithOrigins = shouldIncludeEntry(info.module, resourceCollector, resourceName, moduleReference.location().orElse(null), includeCurrent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
import static com.oracle.svm.core.SubstrateOptions.IncludeAllFromClassPath;
import static com.oracle.svm.core.SubstrateOptions.IncludeAllFromModule;
import static com.oracle.svm.core.SubstrateOptions.IncludeAllFromPath;
import static com.oracle.svm.core.SubstrateOptions.IncludeAllMetadataForModule;
import static com.oracle.svm.core.SubstrateOptions.IncludeAllMetadataForClassPath;
import static com.oracle.svm.core.SubstrateOptions.IncludeAllMetadataForClassPathEntry;
import static com.oracle.svm.core.util.VMError.guarantee;
import static com.oracle.svm.core.util.VMError.shouldNotReachHere;

Expand Down Expand Up @@ -129,13 +132,20 @@ public final class NativeImageClassLoaderSupport {
public final AnnotationExtractor annotationExtractor;

private Set<String> javaModuleNamesToInclude;
private Set<String> moduleNamesToIncludeMetadata;

private Set<Path> javaPathsToInclude;
private Set<Path> pathsToIncludeMetadata;

private boolean includeAllFromClassPath;
private boolean includeAllMetadataFromClassPath;

private Optional<LibGraalClassLoaderBase> libGraalLoader;
private List<ClassLoader> classLoaders;

private final Set<Class<?>> classesToIncludeUnconditionally = Collections.newSetFromMap(new ConcurrentHashMap<>());
private final Set<Class<?>> classesToIncludeMetadata = Collections.newSetFromMap(new ConcurrentHashMap<>());
private final Set<String> classNamesToIncludeMetadata = Collections.newSetFromMap(new ConcurrentHashMap<>());

private final Method implAddReadsAllUnnamed = ReflectionUtil.lookupMethod(Module.class, "implAddReadsAllUnnamed");
private final Method implAddEnableNativeAccess = ReflectionUtil.lookupMethod(Module.class, "implAddEnableNativeAccess");
Expand Down Expand Up @@ -245,25 +255,15 @@ private static Path stringToPath(String path) {

public void loadAllClasses(ForkJoinPool executor, ImageClassLoader imageClassLoader) {
guarantee(javaModuleNamesToInclude == null, "This method should be executed only once.");
javaModuleNamesToInclude = Collections.unmodifiableSet(new HashSet<>(IncludeAllFromModule.getValue(parsedHostedOptions).values()));
/* Verify all modules are present */
final Set<String> 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));

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));
javaModuleNamesToInclude = collectAndVerifyModulesFromOption(IncludeAllFromModule);
moduleNamesToIncludeMetadata = collectAndVerifyModulesFromOption(IncludeAllMetadataForModule);

javaPathsToInclude = collectAndVerifyPathsFromOption(IncludeAllFromPath);
pathsToIncludeMetadata = collectAndVerifyPathsFromOption(IncludeAllMetadataForClassPathEntry);

includeAllFromClassPath = IncludeAllFromClassPath.getValue(parsedHostedOptions);
includeAllMetadataFromClassPath = IncludeAllMetadataForClassPath.getValue(parsedHostedOptions);

new LoadClassHandler(executor, imageClassLoader).run();

Expand All @@ -281,6 +281,36 @@ public void loadAllClasses(ForkJoinPool executor, ImageClassLoader imageClassLoa
}
}

private Set<String> collectAndVerifyModulesFromOption(HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> option) {
var moduleNamesToInclude = Collections.unmodifiableSet(new HashSet<>(option.getValue(parsedHostedOptions).values()));

/* Verify all modules are present */
final Set<String> allModules = Stream.concat(modulepathModuleFinder.findAll().stream(), upgradeAndSystemModuleFinder.findAll().stream())
.map(m -> m.descriptor().name())
.collect(Collectors.toSet());
moduleNamesToInclude.stream()
.filter(m -> !allModules.contains(m))
.findAny()
.ifPresent(m -> missingFromSetOfEntriesError(m, allModules, "module-path", option));

return moduleNamesToInclude;
}

private Set<Path> collectAndVerifyPathsFromOption(HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> option) {
var pathsToInclude = option.getValue(parsedHostedOptions).values().stream()
.map(NativeImageClassLoaderSupport::stringToPath)
.map(Path::toAbsolutePath)
.collect(Collectors.toUnmodifiableSet());

/* Verify all paths are present */
pathsToInclude.stream()
.filter(p -> !classpath().contains(p))
.findAny()
.ifPresent(p -> missingFromSetOfEntriesError(p, classpath(), "classpath", option));

return pathsToInclude;
}

private static void missingFromSetOfEntriesError(Object entry, Collection<?> allEntries, String typeOfEntry,
HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> option) {
String sortedEntries = allEntries.stream()
Expand Down Expand Up @@ -590,6 +620,10 @@ public void setupLibGraalClassLoader() {
}
}

public Set<String> getClassNamesToIncludeMetadata() {
return new HashSet<>(classNamesToIncludeMetadata);
}

private record AddExportsAndOpensAndReadsFormatValue(Module module, String packageName,
List<Module> targetModules) {
}
Expand Down Expand Up @@ -729,7 +763,7 @@ private void run() {
"org.graalvm.nativebridge"));

Set<String> additionalSystemModules = upgradeAndSystemModuleFinder.findAll().stream().map(v -> v.descriptor().name()).collect(Collectors.toSet());
additionalSystemModules.retainAll(getJavaModuleNamesToInclude());
additionalSystemModules.retainAll(getModuleNamesToIncludeMetadata());
requiresInit.addAll(additionalSystemModules);

for (ModuleReference moduleReference : upgradeAndSystemModuleFinder.findAll()) {
Expand Down Expand Up @@ -757,6 +791,7 @@ private void initModule(ModuleReference moduleReference) {
try (ModuleReader moduleReader = moduleReference.open()) {
Module module = optionalModule.get();
final boolean includeUnconditionally = javaModuleNamesToInclude.contains(module.getName());
final boolean includeForReflection = moduleNamesToIncludeMetadata.contains(module.getName());
var container = moduleReference.location().orElseThrow();
if (ModuleLayer.boot().equals(module.getLayer())) {
builderURILocations.add(container);
Expand All @@ -766,7 +801,7 @@ private void initModule(ModuleReference moduleReference) {
String className = extractClassName(moduleResource, fileSystemSeparatorChar);
if (className != null) {
currentlyProcessedEntry = moduleReferenceLocation + fileSystemSeparatorChar + moduleResource;
executor.execute(() -> handleClassFileName(container, module, className, includeUnconditionally));
executor.execute(() -> handleClassFileName(container, module, className, includeUnconditionally, includeForReflection));
}
entriesProcessed.increment();
});
Expand All @@ -777,6 +812,7 @@ private void initModule(ModuleReference moduleReference) {

private void loadClassesFromPath(Path path) {
final boolean includeUnconditionally = javaPathsToInclude.contains(path) || includeAllFromClassPath;
final boolean includeAllMetadata = pathsToIncludeMetadata.contains(path) || includeAllMetadataFromClassPath;
if (ClasspathUtils.isJar(path)) {
try {
URI container = path.toAbsolutePath().toUri();
Expand All @@ -790,7 +826,7 @@ private void loadClassesFromPath(Path path) {
}
if (probeJarFileSystem != null) {
try (FileSystem jarFileSystem = probeJarFileSystem) {
loadClassesFromPath(container, jarFileSystem.getPath("/"), null, Collections.emptySet(), includeUnconditionally);
loadClassesFromPath(container, jarFileSystem.getPath("/"), null, Collections.emptySet(), includeUnconditionally, includeAllMetadata);
}
}
} catch (ClosedByInterruptException ignored) {
Expand All @@ -800,13 +836,14 @@ private void loadClassesFromPath(Path path) {
}
} else {
URI container = path.toUri();
loadClassesFromPath(container, path, ClassUtil.CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES_ROOT, ClassUtil.CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES, includeUnconditionally);
loadClassesFromPath(container, path, ClassUtil.CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES_ROOT, ClassUtil.CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES, includeUnconditionally,
includeAllMetadata);
}
}

private static final String CLASS_EXTENSION = ".class";

private void loadClassesFromPath(URI container, Path root, Path excludeRoot, Set<Path> excludes, boolean includeUnconditionally) {
private void loadClassesFromPath(URI container, Path root, Path excludeRoot, Set<Path> excludes, boolean includeUnconditionally, boolean includeAllMetadata) {
boolean useFilter = root.equals(excludeRoot);
if (useFilter) {
String excludesStr = excludes.stream().map(Path::toString).collect(Collectors.joining(", "));
Expand All @@ -832,7 +869,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
String className = extractClassName(fileName, fileSystemSeparatorChar);
if (className != null) {
currentlyProcessedEntry = file.toUri().toString();
executor.execute(() -> handleClassFileName(container, null, className, includeUnconditionally));
executor.execute(() -> handleClassFileName(container, null, className, includeUnconditionally, includeAllMetadata));
}
entriesProcessed.increment();
return FileVisitResult.CONTINUE;
Expand Down Expand Up @@ -916,7 +953,7 @@ private String extractClassName(String fileName, char fileSystemSeparatorChar) {
return strippedClassFileName.equals("module-info") ? null : strippedClassFileName.replace(fileSystemSeparatorChar, '.');
}

private void handleClassFileName(URI container, Module module, String className, boolean includeUnconditionally) {
private void handleClassFileName(URI container, Module module, String className, boolean includeUnconditionally, boolean includeAllMetadata) {
synchronized (classes) {
EconomicSet<String> classNames = classes.get(container);
if (classNames == null) {
Expand All @@ -942,12 +979,18 @@ private void handleClassFileName(URI container, Module module, String className,
} catch (AssertionError error) {
VMError.shouldNotReachHere(error);
} catch (Throwable t) {
if (includeAllMetadata) {
classNamesToIncludeMetadata.add(className);
}
ImageClassLoader.handleClassLoadingError(t);
}
if (clazz != null) {
if (includeUnconditionally) {
classesToIncludeUnconditionally.add(clazz);
}
if (includeAllMetadata) {
classesToIncludeMetadata.add(clazz);
}
imageClassLoader.handleClass(clazz);
}
imageClassLoader.watchdog.recordActivity();
Expand Down Expand Up @@ -987,21 +1030,39 @@ public void reportBuilderClassesInApplication() {
}
}

public Set<String> getJavaModuleNamesToInclude() {
public Set<String> getModuleNamesToInclude() {
return javaModuleNamesToInclude;
}

public Set<String> getModuleNamesToIncludeMetadata() {
return moduleNamesToIncludeMetadata;
}

public Set<Path> getJavaPathsToInclude() {
return javaPathsToInclude;
}

public Set<Path> getPathsToIncludeMetadata() {
return pathsToIncludeMetadata;
}

public boolean includeAllFromClassPath() {
return includeAllFromClassPath;
}

public boolean isIncludeAllMetadataFromClassPath() {
return includeAllMetadataFromClassPath;
}

public List<Class<?>> getClassesToIncludeUnconditionally() {
return classesToIncludeUnconditionally.stream()
.sorted(Comparator.comparing(Class::getTypeName))
.collect(Collectors.toList());
}

public List<Class<?>> getClassesToIncludeMetadata() {
return classesToIncludeMetadata.stream()
.sorted(Comparator.comparing(Class::getTypeName))
.collect(Collectors.toList());
}
}
Loading

0 comments on commit 8ca7704

Please sign in to comment.