Class NativeImageMetadataGenerator

java.lang.Object
org.codehaus.groovy.tools.NativeImageMetadataGenerator

public class NativeImageMetadataGenerator extends Object
Writes the GraalVM reachability metadata for Groovy's own runtime into the groovy jar, as META-INF/native-image/org.apache.groovy/groovy/reachability-metadata.json (GROOVY-12365), so that a dynamic Groovy application builds with plain native-image and no agent step for anything the runtime does on its own behalf.

Every entry is derived from what the runtime is known to do, rather than recorded by observing an application, and is conditional on the class that performs the access being reached (the metaclass registry, or the invokedynamic bootstrap), so a statically compiled program that links no dynamic call site carries none of it:

  • The metaclass registry bootstrap (conditional on MetaClassRegistryImpl). Creating the registry scans the static methods of the DGM method holders, instantiates the additional meta-method classes, loads every type named in the DGM records by name, and caches the receiver types; a parameter or return type is cached when a call is first selected. Caching an abstract type reads its methods, and those of its abstract superclasses and superinterfaces, to decide whether it is a single-abstract-method type. None of these classes is initialised yet when that happens, hence the registry condition. This is where JDK types appear: they are reflected over on every application's behalf.
  • The invokedynamic machinery (conditional on IndyInterface). Its classes obtain method handles to their own methods and to a few JDK and MOP methods through a Lookup held in a run-time-initialised class, which the image builder cannot fold, so the targets are registered.
  • Default-import class discovery (conditional on Java17). getDefaultImportClasses loads GroovySystem and groovy.beans.ListenerList by name to locate the groovy jar; those names are not constant Class.forName arguments the image builder can fold.
  • Groovy-owned types that get a metaclass (conditional on the registry too, since a metaclass can be created for a class before the class initialises). CachedClass reads their declared constructors, methods and fields, up the superclass chain, and the JDK's Introspector their public methods; every interface in the hierarchy is cached and SAM-checked. The types are the Groovy-owned DGM receivers and the runtime classes an ordinary dynamic program instantiates, such as its closure and range implementations.
  • Negative lookups by naming convention, for every type above that gets a metaclass: <Type>BeanInfo and <Type>Customizer (the Introspector) and groovy.runtime.metaclass.<Type>MetaClass (the registry's custom metaclass lookup). Registering the absent names makes the lookups fail with the ClassNotFoundException the callers expect rather than a missing-registration error.
The JDK side of the hierarchies is the one of the JDK the jar was built with; the supertypes JDK 21 adds are listed by hand, and any a later JDK adds are absent until listed, which only matters under --exact-reachability-metadata.

The tests-native subproject checks all of this against a probe program: its checkNativeMetadata task fails on any Groovy-owned need the native-image agent records that is not shipped, and its nativeRun tasks build and run the probe as an image with nothing but this metadata for Groovy's part.

Not emitted, deliberately: Class.forName calls with a constant name (the image builder folds them, absent classes included); reflection over the application's classes and the JDK types it uses dynamically; dynamic proxies for closures coerced to interfaces; serialization. Those depend on what the application does and remain the application's, or the native-image agent's, responsibility.

Since:
6.0.0
  • Field Details

    • METADATA_DIRECTORY

      public static final String METADATA_DIRECTORY
      The metadata directory inside the jar, per the GraalVM convention META-INF/native-image/<groupId>/<artifactId>/.
      See Also:
    • METADATA_FILE

      public static final String METADATA_FILE
      See Also:
  • Constructor Details

    • NativeImageMetadataGenerator

      public NativeImageMetadataGenerator()
  • Method Details

    • write

      public static String write(List<GeneratedMetaMethod.DgmMethodRecord> records, String targetDirectory) throws IOException
      Writes the metadata file under targetDirectory.
      Parameters:
      records - the DGM records DgmConverter produced
      targetDirectory - the classes directory the jar is assembled from
      Returns:
      the path of the file written
      Throws:
      IOException
    • writeForModule

      public static String writeForModule(String artifactId, List<Class<?>> instanceExtensions, List<Class<?>> staticExtensions, String targetDirectory) throws IOException
      Writes the metadata of an extension module (a jar with a META-INF/groovy/org.codehaus.groovy.runtime.ExtensionModule descriptor) under targetDirectory, as META-INF/native-image/org.apache.groovy/<artifactId>/reachability-metadata.json. The registry loads the descriptor's extension classes by name, scans their static methods and caches every type in those signatures, all while it bootstraps; the entries are conditional on the registry.
      Parameters:
      artifactId - the module's artifact id, e.g. groovy-nio
      instanceExtensions - the descriptor's extensionClasses
      staticExtensions - the descriptor's staticExtensionClasses
      targetDirectory - the directory the jar is assembled from
      Returns:
      the path of the file written
      Throws:
      IOException
    • main

      public static void main(String[] args) throws IOException, ClassNotFoundException
      The build's entry point for a module: <artifactId> <targetDirectory> <extensionClasses> <staticExtensionClasses>, the class lists comma-separated and possibly empty. The classes are loaded without initialisation, so no Groovy runtime starts here.
      Throws:
      IOException
      ClassNotFoundException