JavaFX applikation auf anderen Systemen zum laufen bringen

nieselfriem

Bekanntes Mitglied
Hallo,

ich habe nach einiger Abstinenz mich wieder mal Java zugewendet. Nun habe ich mich etwas auf JavaFX gestürzt und mir mein Intellij da eingerichtet um mich mal der Destoptoolentwicklung hinzugeben.

Nun habe ich aber das Problem, dass ich gerne meine kleine "Anwendung" auf einem anderen System laufen lassen will. Nun scheitere ich daran, dass ich unter Windows entwickelt habe, dort JavaFX installiert habe und die Anwendung mal unter einem Linux laufen lassen wollte. Nun scheitere ich an einer
Code:
java.lang.ClassNotFoundException: com.sun.glass.ui.gtk.GtkPlatformFactory

Gibt es eine Möglichkeit JavaFX Anwendungen so im Artefakt zusammenzubauen, dass ich JavaFX nicht explizit unter dem Zielsystem installieren muss?

VG Georg
 
Nur kurz am Rande:
Ich habe die Lösung dort etwas ausprobiert und es scheint schon halbwegs zu funktionieren.

Mein Gradle.build file:
Java:
plugins {
    id 'java'
}

group 'de.kneitzel'
version '1.0-SNAPSHOT'
ext.moduleName = 'HelloWorld.main'
sourceCompatibility = 1.11

repositories {
    mavenCentral()
}

def currentOS = org.gradle.internal.os.OperatingSystem.current()
def platform
if (currentOS.isWindows()) {
    platform = 'win'
} else if (currentOS.isLinux()) {
    platform = 'linux'
} else if (currentOS.isMacOsX()) {
    platform = 'mac'
}

dependencies {
    compile "org.openjfx:javafx-base:11:${platform}"
    compile "org.openjfx:javafx-graphics:11:${platform}"
    compile "org.openjfx:javafx-controls:11:${platform}"
    compile "org.openjfx:javafx-fxml:11:${platform}"
}

task run(type: JavaExec) {
    classpath sourceSets.main.runtimeClasspath
    main = "helloworld.HelloWorld"
}

jar {
    inputs.property("moduleName", moduleName)
    manifest {
        attributes('Automatic-Module-Name': moduleName)
    }
}

compileJava {
    inputs.property("moduleName", moduleName)
    doFirst {
        options.compilerArgs = [
                '--module-path', classpath.asPath,
                '--add-modules', 'javafx.controls,javafx.fxml'
        ]
        classpath = files()
    }
}

task createJar(type: Copy) {
    dependsOn 'jar'
    into "$buildDir/libs"
    from configurations.runtime
}

Was er noch nicht ganz macht ist das gradle.run - da muss ich den module-path richtig angeben ... und das jar ist noch kein fat jar, so dass da noch ein Aufruf mit der Angabe des module-path erfolgen muss:
java --module-path "." --module "HelloWorld.main/helloworld.HelloWorld"

Aber das schaue ich mir morgen noch etwas an - werde ich dann auch noch in meinen Blog mit aufnehmen.

Das ganze Test-Projekt findet sich auf https://github.com/kneitzel/blog-javafx-series unter "xx helloworld - gradle with fatjar". Aber ist halt noch work in progress. Vor allem will man ja auch etwas haben, das unter allen Plattformen funktioniert und so wie ich es sehe, funktioniert es derzeit nur auf der Plattform, auf der es auch gebaut wurde .. Aber das war jetzt auch nur ein paar Minuten Try ehe es für mich ins Bett geht 🙂
 
Also ich habe jetzt eben noch einmal geschaut und der erste Ansatz war Müll (Ja, mir gehen im Bett manchmal so Dinge durch den Kopf...)

Java geht hier ja einen speziellen Weg und den sollten wir nicht unbedingt verlassen:

a) gradlew assemble -> Hier bekommen wir ja unter build/distribution ein tar und ein ZIP, welches die Anwendung enthält. Dieses enthält die notwendigen Jars zusammen mit Startscripts, die dann für Windows, Linux und Mac verwendet werden können. Die Dependencies werden aber nur für die eigene Plattform hinzu gefügt (also bei mir: win). Damit man aber alle Plattformen hat, kann man dies in den Dependencies explizit angeben:
Java:
    compile group: 'org.openjfx', name: 'javafx-controls', version: '11.0.2', classifier: 'win'
    compile group: 'org.openjfx', name: 'javafx-fxml', version: '11.0.2', classifier: 'win'

    compile group: 'org.openjfx', name: 'javafx-controls', version: '11.0.2', classifier: 'mac'
    compile group: 'org.openjfx', name: 'javafx-fxml', version: '11.0.2', classifier: 'mac'

    compile group: 'org.openjfx', name: 'javafx-controls', version: '11.0.2', classifier: 'linux'
    compile group: 'org.openjfx', name: 'javafx-fxml', version: '11.0.2', classifier: 'linux'
Das Ergebnis ist dann etwas aus mehreren Dateien, die man auf allen drei Systemen entpacken und starten kann. Für den Start benötigt das Zielsystem lediglich eine passende Java Version.

[CODE lang="java" title="gradle.build für javafx"]plugins {
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.8'
}

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

group 'de.kneitzel'
version '1.0-SNAPSHOT'

mainClassName = 'helloworld.HelloWorld'

repositories {
mavenCentral()
}

dependencies {
// Nur die eigene Plattform:
// compile group: 'org.openjfx', name: 'javafx-controls', version: '11.0.2'
// compile group: 'org.openjfx', name: 'javafx-fxml', version: '11.0.2'

// JavaFX Plattform: Windows
compile group: 'org.openjfx', name: 'javafx-controls', version: '11.0.2', classifier: 'win'
compile group: 'org.openjfx', name: 'javafx-fxml', version: '11.0.2', classifier: 'win'

// JavaFX Plattform: Mac
compile group: 'org.openjfx', name: 'javafx-controls', version: '11.0.2', classifier: 'mac'
compile group: 'org.openjfx', name: 'javafx-fxml', version: '11.0.2', classifier: 'mac'

// JavaFX Plattform: Linux
compile group: 'org.openjfx', name: 'javafx-controls', version: '11.0.2', classifier: 'linux'
compile group: 'org.openjfx', name: 'javafx-fxml', version: '11.0.2', classifier: 'linux'
}
[/CODE]

Details finden sich auch auf https://openjfx.io!

b) jlink ist das Tool, welches dazu dient ein komplettes Image incl. JRE und allen Java-Modulen, die gebraucht werden, zu erzeugen. Und dieses Tool lässt sich auch sehr schön nutzen mit gradle, indem man das plugin einfügt.
Dadurch wird dann in build/image alles abgelegt, was benötigt wird. Und das wird jetzt natürlich wieder Plattform spezifisch, denn das JRE wird ja kopiert. Und das ist auch mit der Grund, wieso das mit den Dependencies für alle Plattformen weg muss - daran stört sich jlink, denn erkennen tut es die einzelnen Plattformen alle und meckert dann entsprechend.
Ein Cross-Plattform bauen ist auch möglich. Das habe ich aber mit gradle zusammen noch nicht gemacht. Da benötigt man halt ein JDK der Plattform und muss dann --module-path $TARGET/jmods beim jlink Aufruf angeben mit $TARGET dem JDK Verzeichnis. Und Es müssen natürlich die korrekten jar Files der Plattform vorliegen. Also wenn man das für Windows bauen möchte, dann mit classifier: win die dependencies angeben ...
Das gradle.build mit jlink sieht auch nicht groß komplexer aus. jlink plugin sowie der jlink part, der aber nur den Namen setzt ist dazu gekommen.

[CODE lang="java" title="build.gradle mit jlink" highlight="4,22-26"]plugins {
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.9'
id 'org.beryx.jlink' version '2.22.0'
}

javafx {
version = "11"
modules = [ 'javafx.controls', 'javafx.fxml' ]
}

group 'de.kneitzel'
version '1.0-SNAPSHOT'
tasks.withType(JavaCompile) { options.encoding = 'UTF-8' }
modularity.disableEffectiveArgumentsAdjustment()
sourceCompatibility = 1.11

repositories {
mavenCentral()
}

jlink {
launcher {
name = 'helloworld'
}
}

mainClassName = 'HelloWorld.main/helloworld.HelloWorld'

dependencies {
compile group: 'org.openjfx', name: 'javafx-controls', version: '11.0.2'
compile group: 'org.openjfx', name: 'javafx-fxml', version: '11.0.2'
}[/CODE]

Die beiden Wege werden auch mittels Maven gehen. Generell ist meine Empfehlung aber, dass man mit Maven oder Gradle arbeitet, denn damit wird generell vermieden, dass JavaFX irgendwie (manuell) installiert werden muss. Und es müssen keine module-path angegeben werden auf Kommandozeile oder in der IDE.

Das wären erst einmal die Wege, die mir so vorschweben. Ab JDK 14 gibt es noch ein JPackage, aber damit habe ich mich noch nicht im Detail beschäftigt, da ich auf LTS Versionen setze und mein Neugier kontrollieren kann 🙂 Aber damit liesse sich dann ein Installer für das image erstellen... Dazu sollte ein gradlew jpackage Aufruf schon ausreichen wenn ich die Dokumentation des Plugins richtig verstanden habe.

Ich hoffe, diese Lösungsideen kommen Deinen Anforderungen auch relativ nahe.
 
Alles klar, ich werde es mir mal anschauen. Vielen Dank für deine Mühe. Ich war zwar Nachts auch wach aber bei mir war es eine Rufbereitschaft und keine javaprobem 😉

VG Georg
 
Die für das Zielsystem einfachste (aber nicht unbedingt für dich) Variante ist, über GraalVM ein native executable zu bauen.
Du brauchst neben dem openjfx-Plugins, die @kneitzel schon erwähnt hat, noch das client-maven-plugin (siehe hier: https://github.com/gluonhq/client-maven-plugin) - und du musst von Gradle auf Maven wechseln.

Dann brauchst du lokal, neben deinem regulären JDK, noch ein aktuelles GraalVM (siehe hier: https://www.graalvm.org/downloads/).

Für Windows musst du noch Visual Studio installieren (was genau du an Paketen brauchst, weiss ich jetzt nicht, aber nimm an, dass es viele sind...). Alle Requirements findest du auf GitHub in meinem ersten Link. Etwas spezieller sind builds für RasperryPi, da hab ich keine Ahnung, wie das geht.

Aber nochmal etwas zurück: Der Weg über GraalVM ist im Ergebnis cool, aber der Weg ist sehr steinig. Ich habe auf Arbeit mir einen Seed gemacht, damit ich leicht ein Project zum Starten habe.
Du kannst, auch ohne JLink und der ganzen aktuellen Modul-Magic von Java, aber auch einfach ein Fat-Jar machen, dass JavaFX enthält. Das ist kein Problem und mit Maven z.B. mit dem Shade-Plugin einfach zu erreichen. Dann muss auf dem Zielsystem nur noch das richtige Java (ohne JavaFX!) installiert sein und gut ist.
 

Neue Themen


Zurück
Oben