Bundle in JavaFX when you run the executable from

2020-05-07 19:08发布

Over the past few days I've been tearing what's left of my hair out trying to get to the Holy Grail: Gradle + Java 11 + JavaFX + writing all my app and testing code in Groovy, not Java.

One of several problems is that Groovy 2 (even Groovy 3) can't yet cope with Java 9+ modules, which rules one clever solution out.

But I have managed to get a Gradle project together which not only does virtually everything successfully, including the "run" task (from the "application" plugin), and runs tests using TestFX! This is indeed using Java 11, Groovy 2.5.9, JavaFX 13, etc.

It will even execute the task "installdist" (when included in build.gradle). That produces an executable with a "lib" directory full of all the jars you need to run independently (like task "assemble", only not compressed).

The trouble is, when I execute that executable I get the by now classic "Error: JavaFX runtime components are missing, and are required to run this application". Still confused why that happens, as the directory [project dir]/build/install/GradleExp/lib does indeed contain no fewer than 7 JavaFX .jars.

I think I've exhausted what I can do trying to configure Gradle conventionally: given that I'm using Groovy for app and testing code I don't think it's possible to configure the Gradle project to get "installdist" to produce a ready-to-run JavaFX-enabled executable. But... is there something I can do after having produced this, like some command I could run that would somehow bring in the JavaFX files and link them in? Even if this involves linking inelegantly to some JavaFX jar file(s) or locations?

Or maybe I could use the executable sh script file generated by the "build" task under .../build/scripts ? This ropes in the above-mentioned 7 JavaFX .jar files at the start when defining the CLASSPATH:

...
CLASSPATH=$APP_HOME/lib/GradleExp-1.0.jar:
$APP_HOME/lib/commons-math3-3.6.1.jar:
$APP_HOME/lib/guava-27.0.1-jre.jar:
$APP_HOME/lib/javafx-fxml-13-linux.jar:
$APP_HOME/lib/javafx-controls-13-linux.jar:
$APP_HOME/lib/javafx-controls-13.jar:
$APP_HOME/lib/javafx-graphics-13-linux.jar:
$APP_HOME/lib/javafx-graphics-13.jar:
$APP_HOME/lib/javafx-base-13-linux.jar:
$APP_HOME/lib/javafx-base-13.jar:...

... when I try to run that script I get:

mike@M17A /.../GradleExp/build/scripts $  ./GradleExp
Error: Could not find or load main class SceneSwitcher
Caused by: java.lang.ClassNotFoundException: SceneSwitcher

edit
Here's a funny thing: in fact APP_HOME in that script file turns out to be [project directory]/build. But under that there is no directory "lib". There is a directory "libs" which contains precisely one file: GradleExp-1.0.jar, 27 kB, not a "fat" jar by any stretch of the imagination.

I just copied the "lib" directory from the "installdist" task up to /build, and ran that script file: again, despite having all those JavaFX jars now available, I again get "Error: JavaFX runtime components are missing, and are required to run this application". Hmmm... I have much to learn.

1条回答
可以哭但决不认输i
2楼-- · 2020-05-07 19:36

Slaw and cfrick came up with the answer to my question in their comments. I thought this might come in useful for some.

If you want the JavaFX modules on the class-path then create a separate main class which does not extend Application and invoke Application.launch(YourApp.class, args) from the main method.

cfrick's example stripped-down working Gradle project is here. I added the following line:

installDist{}

... this makes a new task available: ./gradlew installdist will then produce an executable under build/install/.

In case cfrick's github page goes away, build.gradle is like this:

plugins {
    id 'groovy'
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.8'
}

repositories {
    jcenter()
}

javafx {
    version = "11.0.2"
    modules = [ 'javafx.controls' ]
}

dependencies {
    implementation 'org.codehaus.groovy:groovy:3.+'
}

application {
    mainClassName = 'ofx.App'
}

installDist{} // my addition

and src/main/groovy/ofx/App.groovy is like this:

package ofx

import javafx.application.Application
import javafx.scene.Scene
import javafx.scene.control.Label
import javafx.scene.layout.StackPane
import javafx.stage.Stage

class App {
    static void main(String[] args) {
        Application.launch(HelloFx, args)
    }
}

class HelloFx extends Application {

    @Override
    void start(Stage stage) {
        def javaVersion = System.getProperty("java.version")
        def javafxVersion = System.getProperty("javafx.version")
        Label l = new Label("Hello, JavaFX $javafxVersion, running on Java $javaVersion.")
        Scene scene = new Scene(new StackPane(l), 640, 480)
        stage.setScene(scene)
        stage.show()
    }

}
查看更多
登录 后发表回答