classgraph / classgraph

An uber-fast parallelized Java classpath scanner and module scanner.
MIT License
2.73k stars 285 forks source link

GraalVM static initializer -- unsure if actually working? #867

Open cmeiklejohn opened 3 months ago

cmeiklejohn commented 3 months ago

I'm doing something very similar as to what is being done in this example project. However, it doesn't seem like my classes are being initialized correctly.

Simply, I'm just trying to collect a list of classes, based on some filtering, and build a map of those classes for use later.

Here's the code for com.mycompany.MyClass, that passes a unit test successfully without native compilation.

  public static Map<String, Class> = new HashMap<String, Class>();

  static {
      List<String> prefixes = Arrays.asList(...); // list of class prefixes

      List<ClassInfo> classInfos = new ArrayList<ClassInfo>();

      for (String prefix : prefixes) {
          try (ScanResult scanResult = new ClassGraph()
                  .disableModuleScanning() // added for GraalVM
                  .addClassLoader(ClassLoader.getSystemClassLoader())
                  .enableAnnotationInfo()
                  .enableMethodInfo()
                  .acceptPackages(prefix)
                  .initializeLoadedClasses()
                  .scan()) {
              ClassInfoList classInfoList = scanResult.getSubclasses(baseType);
              // snip some other filtering and restrictions that don't matter
              classInfos.addAll(classInfoList);

              for (ClassInfo classInfo : classInfos) {
                  Class<Message> clazz = (Class<Message>) classInfo.loadClass();
                  try {
                      nameClassMap.put(name, clazz);
                  } catch (InstantiationException e) {
                      throw new RuntimeException(e);
                  } catch (IllegalAccessException e) {
                      throw new RuntimeException(e);
                  }
              }
          }
      }
  }

When running the native test, I've done the following:

First, I've used the build options to ensure this static initializer is actually called:

  "-march=compatibility",  // we want native mode here, but active bug related to LZCNT prevents this.
  "--strict-image-heap",
  "-H:+UnlockExperimentalVMOptions",
  "-H:Log=registerResource:3",
  "-H:+BuildReport",
  "-H:+PrintClassInitialization",
  "--initialize-at-build-time=com.mycompany.MyClass,io.github.classgraph.,nonapi.io.github.classgraph.",
  "-H:ReflectionConfigurationFiles=${nativeImageDir.absolutePath}/reflect-config.json",
  "-H:ResourceConfigurationFiles=${nativeImageDir.absolutePath}/resource-config.json",

Then, using the PrintClassInitialization CSV result, I verified that the class is being included/initialized at build time:

com.mycompany.MyClass, BUILD_TIME, from command line with 'com.mycompany.MyClass'

However, when I run the native test, the static map that I am initializing is empty -- am I missing an obvious step?

Thank you for your help!

lukehutch commented 3 months ago

@cmeiklejohn Thanks for the detailed report. Unfortunately I have no real experience with GraalVM, so I wouldn't even know where to begin helping you. However, my suspicion (given that the result list is empty) is that the classpath is not properly set up at build time to include the jars or directories that you want to scan.

It is important to get this right for GraalVM users though, so I'm glad you're reporting the issues you run into in the bug tracker of the example project! Thanks!