Create your own GraalVM native-image custom @Option in your Features
GraalVM comes with the awesome native-image tool to compile to native a java classpath. This tool has a SDK called SVM (S for Subtrate) which basically enables to define how to replace/delete/.... part of the compiled code (subtitution) and to write extensions called Feature.
A basic feature is as simple as:
@AutomaticFeature
public class MyFeature implements Feature {
}
Indeed it does nothing but it will be discovered - no need to create a META-INF/services/org.graalvm.nativeimage.hosted.Feature file.
Most common extensions will register reflection model and resources before the native-image analyzis to let it be aware of these elements. Here is an example registering a resource:
@Override
public void beforeAnalysis(final BeforeAnalysisAccess access) {
Resources.registerResource("MY-INF/resource.txt", findResourceDuringBuild());
}
If you already played with GraalVM native-image you probably already saw -H:... options. One common is the -H:ReflectionConfigurationFiles=/path/to/reflection.json which enables to define the reflection metadata to store in the final binary. These options are backed by @Option and are static fields of an options class:
public final class CustomOptions {
@Option(help = "Some description for this option.", type = OptionType.User)
static final HostedOptionKey<String> MyOption1 = new HostedOptionKey<>(null);
@Option(help = "Some description for this option", type = OptionType.User)
static final HostedOptionKey<String> MyOption2 = new HostedOptionKey<>(null);
// ...
}
This class can be defined anywhere - even as a nested class. However it is not like @AutomaticFeature read automatically at run time (actually native-image build time which is the run time of native-image ;)). These options are actually converted with an annotation processor of graalvm project in a SPI on org.graalvm.compiler.options.OptionDescriptors. Sadly for us, this annotation processor is not on central so not easy to grab. This does not mean you can't use that kind of API, it just means you have to implement the SPI yourself.
The SPI has two main methods:
- get(String) which returns a descriptor for one of the option fields,
- iterator() which returns an iterator on all options.
For previous option class the SPI implementation can look like:
public class CustomOptionDescriptors implements OptionDescriptors {
@Override
public OptionDescriptor get(final String value) {
switch (value) {
case "MyOption1":
return OptionDescriptor.create(
value, OptionType.User, String.class,
"Some description for this option.",
CustomOptions.class, value,
CustomOptions.MyOption1);
case "MyOption2":
return OptionDescriptor.create(
value, OptionType.User, String.class,
"Some description for this option.",
CustomOptions.class, value,
CustomOptions.MyOption2);
default:
return null;
}
}
@Override
public Iterator<OptionDescriptor> iterator() {
return Stream.of("MyOption1", "MyOption2").map(this::get).iterator();
}
}
The previous @Option annotation is replaced by OptionDescriptor.create() call. This means previous class can get rid of the annotation - which avoids to duplicate the type of option and help message.
The only trick is that the OptionDescriptor must hold the same instance - HostedOptionKey - than the class having the static fields to ensure the initialization from the command line of the descriptor instance impacts the read access through the class hosting the fields.
Once you have registered your class in META-INF/services/org.graalvm.compiler.options.OptionDescriptors, you can use the class defining the options in any feature:
?@Override
public void beforeAnalysis(final BeforeAnalysisAccess access) {
if (CustomOptions.ResourceName.hasBeenSet()) {
final String resourceName = Options.ResourceName.getValue();
Resources.registerResource(resourceName, findResourceDuringBuild(resourceName));
}
}
To activate your extension and configure the resource name you then just have to add to native-image call this ResourceName option:
native-image \
..... \
-H:ResourceName=myresource.txt \
.....
This simple trick, even if more technical than it should, enables to make the GraalVM custom features configurable and consistent with GraalVM built-in features which is key when doing the integration with some frameworks and not just at application level - where all can be hardcoded.
From the same author:
In the same category: