Search code examples
javajavafxgraalvmgraalvm-native-imagegluonfx

Package#getImplementationVersion() returns null with native image (GraalVM/GluonFX)


I have a JavaFX-based desktop app, for which I create native images via GraalVM/GluonFX. To retrieve the version of the app during runtime, I previously – when there was only a fat JAR of the app – used Package#getImplementationVersion(). However, this returns null.

I assume this is because I don't set the manifest entries properly? My configuration of the gluonfx-maven-plugin:

<plugin>
    <groupId>com.gluonhq</groupId>
    <artifactId>gluonfx-maven-plugin</artifactId>
    <version>1.0.10</version>
    <configuration>
        <mainClass>${mainClass}</mainClass>
        <nativeImageArgs>
            <arg>--allow-incomplete-classpath</arg>
            <arg>--initialize-at-build-time=org.pdfclown.Version</arg>
            <arg>--no-fallback</arg>
        </nativeImageArgs>
    </configuration>
</plugin>

Is there a way to configure the plugin such that Package#getImplementationVersion() returns the app's version? I wasn't able to find something in the corresponding documentation. Also, a related issue in GraalVM has been resolved in 2020.


Solution

  • Let say you have a Maven project and you add to your main Application class:

    @Override
    public void start(Stage primaryStage) throws Exception {
        ...
        System.out.println("Main class version: " + getClass().getPackage().getImplementationVersion());
    }
    

    If you do a fat jar with the shade plugin and take care of adding the required manifest entries like Implementation-Version (let's say 1.0), when you run:

    java --module-path=PATH_TO_FX --add-modules javafx.controls,javafx.fxml -jar my-fat-jar.jar
    

    it should print:

    Main class version: 1.0
    

    However if you run

    mvn javafx:run
    

    it prints:

    Main class version: null
    

    as you have reported.

    The reason for this: there is no manifest in the classpath, and the classes (target/classes) are not packed in a jar.

    If your project has a third party dependency that includes a manifest with an Implementation-Version entry (let's say 2.0), doing:

    @Override
    public void start(Stage primaryStage) throws Exception {
        ...
        System.out.println("3rd party Main class version: " + com.my.third.party.MainClass.class.getPackage().getImplementationVersion());
    }
    

    will work:

    mvn javafx:run
    [INFO] --- javafx-maven-plugin:0.0.8:run (default-cli) @ MyProject ---
    3rd party Main class version: 2.0.
    

    because the jar is a dependency of packed classes with a manifest.

    And doing the native image mvn gluonfx:build gluonfx:nativerun also works:

    [INFO] --- gluonfx-maven-plugin:1.0.10:nativerun (default-cli) @ MyProject ---
    [Mon Dec 20 21:29:16 CET 2021][INFO] ==================== RUN TASK ====================
    [Mon Dec 20 21:29:16 CET 2021][INFO] [SUB] 2021-12-20 21:29:16.810 MyProject[23068:414510] Starting Gluon VM...
    [Mon Dec 20 21:29:16 CET 2021][INFO] [SUB] Dec 20, 2021 9:29:16 PM com.sun.javafx.application.PlatformImpl startup
    [Mon Dec 20 21:29:16 CET 2021][INFO] [SUB] WARNING: Unsupported JavaFX configuration: classes were loaded from 'unnamed module @21c815e4'
    [Mon Dec 20 21:29:16 CET 2021][INFO] [SUB] 3rd party Main class version: 2.0
    

    So at this point I see three options:

    • Move your core code to a module, publish a jar, and add it to your main project as a dependency, that should work.
    • Move your manifest key-values to a properties file, and use the Maven resources plugin. That should work out of the box with the GluonFX plugin (make sure you add the properties extension to the resources List). This should be the easiest one.
    • File an issue on Substrate to ask for a manifest. So far, Substrate creates a jar out of the project classes, so in theory it could also have a manifest, and it could be filed with some key-values coming from the plugin's configuration. Adding the manifest is easy, the problem is adding the changes to the configuration to pass down the key-values.