diff --git a/core/src/test/java/io/substrait/extension/DefaultExtensionCatalogTest.java b/core/src/test/java/io/substrait/extension/DefaultExtensionCatalogTest.java index a64db3506..48151fddf 100644 --- a/core/src/test/java/io/substrait/extension/DefaultExtensionCatalogTest.java +++ b/core/src/test/java/io/substrait/extension/DefaultExtensionCatalogTest.java @@ -80,7 +80,11 @@ private static String parseUrn(String resourceName) throws IOException { private static List getExtensionYamlFiles() { try (ScanResult scan = - new ClassGraph().acceptPathsNonRecursive("substrait/extensions").scan()) { + new ClassGraph() + .enableClasspath() + .enableNonSystemModules() + .acceptPathsNonRecursive("substrait/extensions") + .scan()) { ResourceList resources = scan.getResourcesWithExtension(".yaml"); return resources.stream() .map(r -> r.getPath().substring("substrait/extensions/".length())) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 664182d6e..341733fe9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] antlr = "4.13.2" calcite = "1.42.0" -classgraph = "4.8.197" +classgraph = "5.0.0" graal = "25.0.2" graal-plugin = "1.1.14" gradle-extensions = "4.0.0" diff --git a/isthmus-cli/build.gradle.kts b/isthmus-cli/build.gradle.kts index 93a99276d..7f70518ce 100644 --- a/isthmus-cli/build.gradle.kts +++ b/isthmus-cli/build.gradle.kts @@ -68,13 +68,13 @@ graalvmNative { // src/main/resources/META-INF/native-image/.../reachability-metadata.json. buildArgs.add("-H:+ReportExceptionStackTraces") buildArgs.add("--features=io.substrait.isthmus.cli.RegisterAtRuntime") + // ClassGraph registers this FileSystemProvider via META-INF/services. RegisterAtRuntime + // scans with ClassGraph inside the image builder, which leaves the provider in the JDK's + // cached list of installed providers, and so in the image heap. + buildArgs.add("--initialize-at-build-time=io.github.classgraph.vfs.VfsFileSystemProvider") // Removed due to https://github.com/oracle/graal/issues/13316 // buildArgs.add("--future-defaults=all") jvmArgs.add("--sun-misc-unsafe-memory-access=allow") - - // Capture the classpath since native-compile erases it - val isthmusClasspath = classpath.joinToString(":") - jvmArgs.add("-Disthmus.classpath=$isthmusClasspath") } } } diff --git a/isthmus-cli/src/main/java/io/substrait/isthmus/cli/RegisterAtRuntime.java b/isthmus-cli/src/main/java/io/substrait/isthmus/cli/RegisterAtRuntime.java index 44b58d55b..3ce3264c3 100644 --- a/isthmus-cli/src/main/java/io/substrait/isthmus/cli/RegisterAtRuntime.java +++ b/isthmus-cli/src/main/java/io/substrait/isthmus/cli/RegisterAtRuntime.java @@ -4,10 +4,13 @@ import com.google.protobuf.GeneratedMessage; import com.google.protobuf.ProtocolMessageEnum; import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfo; import io.github.classgraph.ClassInfoList; import io.github.classgraph.ScanResult; import io.substrait.extension.SimpleExtension; import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.List; import org.apache.calcite.rel.metadata.BuiltInMetadata; import org.apache.calcite.rel.metadata.Metadata; import org.apache.calcite.rel.metadata.MetadataHandler; @@ -60,7 +63,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { // Empty class register(Empty.class); - try (PackageScanner substrait = new PackageScanner("io.substrait")) { + try (PackageScanner substrait = new PackageScanner(access, "io.substrait")) { // protobuf items substrait.registerByParent(GeneratedMessage.class); substrait.registerByParent(GeneratedMessage.Builder.class); @@ -83,7 +86,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { SqlStdOperatorTable.class, StandardConvertletTable.class); - try (PackageScanner calcite = new PackageScanner("org.apache.calcite")) { + try (PackageScanner calcite = new PackageScanner(access, "org.apache.calcite")) { calcite.registerByParent(Metadata.class); calcite.registerByParent(MetadataHandler.class); calcite.registerByParent(Resources.Element.class); @@ -146,33 +149,49 @@ private static void register(Class... classes) { } private static final class PackageScanner implements AutoCloseable { + private final Feature.FeatureAccess access; private final ScanResult scan; - PackageScanner(String... packageNames) { + PackageScanner(Feature.FeatureAccess access, String... packageNames) { + this.access = access; scan = new ClassGraph() - .enableAllInfo() // GraalVM native-compile erases the classloader classpath - .overrideClasspath(System.getProperty("isthmus.classpath")) + .enableClasspathEntries(access.getApplicationClassPath()) + .enableClassInfo() + .enableAnnotationInfo() + .ignoreClassVisibility() .acceptPackages(packageNames) .scan(); } void registerByAnnotation(Class annotation) { - scan.getClassesWithAnnotation(annotation).loadClasses().forEach(this::registerByParent); + loadClasses(scan.getClassesWithAnnotation(annotation)).forEach(this::registerByParent); } void registerByParent(Class c) { register(c); - getSubTypes(c).loadClasses().forEach(RegisterAtRuntime::register); + loadClasses(getSubTypes(c)).forEach(RegisterAtRuntime::register); } private ClassInfoList getSubTypes(Class c) { if (c.isInterface()) { - return scan.getClassesImplementing(c); + return scan.getAllClassesImplementing(c); } - return scan.getSubclasses(c); + return scan.getAllSubclasses(c); + } + + private List> loadClasses(ClassInfoList classInfos) { + List> classes = new ArrayList<>(); + for (ClassInfo classInfo : classInfos) { + Class c = access.findClassByName(classInfo.getName()); + if (c == null) { + throw new IllegalStateException("Class not found: " + classInfo.getName()); + } + classes.add(c); + } + return classes; } @Override