fvarrui / JavaPackager

:package: Gradle/Maven plugin to package Java applications as native Windows, MacOS, or Linux executables and create installers for them.
GNU General Public License v3.0
1.07k stars 133 forks source link

Packaging doesn't package JavaFX modules #427

Closed SunkenPotato closed 2 months ago

SunkenPotato commented 2 months ago

I'm trying to package my JavaFX application to a JAR. ./gradlew packageMyApp works fine, but when I run the JAR, it throws the following error:

Error: Could not find or load main class com.sunkenpotato.client2p.MainApplication
Caused by: java.lang.NoClassDefFoundError: javafx/application/Application

Here's my build.gradle:

import io.github.fvarrui.javapackager.gradle.PackageTask

buildscript {
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }
    dependencies {
        classpath('io.github.fvarrui:javapackager:1.7.6')
    }
}

plugins {
    id 'java'
    id 'application'
    id 'org.javamodularity.moduleplugin' version '1.8.12'
    id 'org.openjfx.javafxplugin' version '0.0.13'
    id 'org.beryx.jlink' version '3.0.0'
    id 'org.jetbrains.kotlin.jvm'
    id 'io.github.fvarrui.javapackager.plugin' version '1.7.6'
}

group 'com.sunkenpotato'
version '1.0.0'

repositories {
    mavenCentral()
}

java {
    sourceCompatibility = 21
    targetCompatibility = 21
}

ext {
    junitVersion = '5.10.2'
}

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}

mainClassName = 'com.sunkenpotato.client2p.MainApplication'

application {
    mainModule = 'com.sunkenpotato.client2p'
    mainClass = mainClassName
}

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

dependencies {
    implementation 'org.kordamp.bootstrapfx:bootstrapfx-core:0.4.0'
    implementation 'io.github.mkpaz:atlantafx-base:2.0.1'
    implementation 'com.google.code.gson:gson:2.11.0'
    implementation 'commons-io:commons-io:2.11.0'
    implementation 'org.kordamp.ikonli:ikonli-feather-pack:12.3.1'
    implementation 'org.kordamp.ikonli:ikonli-javafx:12.3.1'
    implementation 'com.squareup.okhttp3:okhttp:4.12.0'
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    implementation 'org.apache.logging.log4j:log4j-api:2.23.1'
    implementation 'org.apache.logging.log4j:log4j-core:2.23.1'

    testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}")
}

test {
    useJUnitPlatform()
}

jlink {
    imageZip = project.file("${buildDir}/distributions/app-${javafx.platform.classifier}.zip")
    options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
    launcher {
        name = 'app'
    }
}

jlinkZip {
    group = 'distribution'
}

kotlin {
    jvmToolchain(21)
}

configurations {
    compileAndRuntime
    compileAndRuntime.transitive = true
    implementation.extendsFrom(compileAndRuntime)
}

tasks.register('packageMyApp', PackageTask) {
    dependsOn build
    // mandatory
    mainClass = mainClassName
}

I'm on macOS 14.3. Using Java 21. Any help is greatly appreciated :)

fvarrui commented 2 months ago

Hi @SunkenPotato! I wouldn't download anything from MediaFire 😮

JP doesn't support Java modules yet. You should add JavaFX libraries as dependencias. Don't run runnable jar outside app, it won't work without libs fólder.

fvarrui commented 2 months ago

If your project is on a public repo, please, share it here and I'll try to fix it

SunkenPotato commented 2 months ago

Hey! Don't worry, I haven't downloaded anything.

Thank you so much. https://github.com/SunkenPotato/Homebase would be the repo. Is support for modules coming to JP in the future?

fvarrui commented 2 months ago

Hi @SunkenPotato! Good news ... I managed to build and run your app.

image

Here is your full build.gradle with some changes:

import io.github.fvarrui.javapackager.gradle.PackageTask

buildscript {
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }
    dependencies {
        classpath('io.github.fvarrui:javapackager:1.7.6')
    }
}

plugins {
    id 'java'
    id 'application'
    id 'org.javamodularity.moduleplugin' version '1.8.12'
    id 'org.openjfx.javafxplugin' version '0.0.13'
    id 'org.beryx.jlink' version '3.0.0'
    id 'org.jetbrains.kotlin.jvm'
    id 'io.github.fvarrui.javapackager.plugin' version '1.7.6'
}

group 'com.sunkenpotato'
version '1.0.0'

repositories {
    mavenCentral()
}

java {
    sourceCompatibility = 21
    targetCompatibility = 21
}

ext {
    junitVersion = '5.10.2'
}

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}

mainClassName = 'com.sunkenpotato.client2p.MainApplication'

application {
    mainModule = 'com.sunkenpotato.client2p'
    mainClass = mainClassName
}

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

dependencies {
    implementation 'org.kordamp.bootstrapfx:bootstrapfx-core:0.4.0'
    implementation 'io.github.mkpaz:atlantafx-base:2.0.1'
    implementation 'com.google.code.gson:gson:2.11.0'
    implementation 'commons-io:commons-io:2.11.0'
    implementation 'org.kordamp.ikonli:ikonli-feather-pack:12.3.1'
    implementation 'org.kordamp.ikonli:ikonli-javafx:12.3.1'
    implementation 'com.squareup.okhttp3:okhttp:4.12.0'
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    implementation 'org.apache.logging.log4j:log4j-api:2.23.1'
    implementation 'org.apache.logging.log4j:log4j-core:2.23.1'

    implementation "org.openjfx:javafx-controls:21"
    implementation "org.openjfx:javafx-fxml:21"

    testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}")
}

test {
    useJUnitPlatform()
}

jlink {
    imageZip = project.file("${buildDir}/distributions/app-${javafx.platform.classifier}.zip")
    options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages', '--module-path', '/Users/piro/Downloads/javafx-sdk-21.0.4/lib',]
    launcher {
        name = 'Homebase'
    }
}

jlinkZip {
    group = 'distribution'
}

kotlin {
    jvmToolchain(21)
}

tasks.register('packageMyApp', PackageTask) {
    dependsOn build
    mainClass = mainClassName
    bundleJre = true
    customizedJre = true
//    modules = ['javafx.controls', 'javafx.fxml']
    vmArgs = [ '--add-modules=javafx.controls,javafx.fxml', '--module-path=libs' ]
}

And here you can find some explanations

Add JavaFX libreries as dependencies:

dependencies {
    [...]
    implementation "org.openjfx:javafx-controls:21"
    implementation "org.openjfx:javafx-fxml:21"
    [...]
}

and some JVM arguments needed to avoid JavaFX missing modules error at startup:

tasks.register('packageMyApp', PackageTask) {
    dependsOn build
    mainClass = mainClassName
    bundleJre = true
    customizedJre = true
    // add JavaFX modules to JVM at startup
    vmArgs = [ '--add-modules=javafx.controls,javafx.fxml', '--module-path=libs' ]
}
SunkenPotato commented 2 months ago

Hey @fvarrui, sorry for not answering until now, I was on vacation. This seems to package the app correctly, however, it throws the error when I run build\client-2p\client-2p.exe: JavaFX runtime components are missing, and are required to run this application I modified the tasks a bit to match the OSes and SDKs, but it still won't include the mentioned 'components'.

Here's the task:

tasks.register('packageWindows', PackageTask) {
    dependsOn build
    mainClass = mainClassName
    bundleJre = true
    customizedJre = true
    modules = ['javafx.controls', 'javafx.fxml']
    vmArgs = [ '--add-modules=javafx.controls.jar,javafx.fxml.jar', '--module-path=windows-sdk/lib' ]
    platform = "windows"
}

windows-sdk is the JavaFX SDK for Windows from the gluon website. The contents of windows-sdk/lib are:

javafx-swt.jar*
javafx.base.jar*
javafx.controls.jar*
javafx.fxml.jar*
javafx.graphics.jar*
javafx.media.jar*
javafx.properties*
javafx.swing.jar*
javafx.web.jar*

(* for executable)

I have honestly no idea what I'm doing wrong, and why it's so hard to package JavaFX applications. Any help would be greatly appreciated

fvarrui commented 2 months ago

Hi @SunkenPotato! Just remove .jar from module names. Module names are different from jar names. Don't worry, I hope you enjoyed your vacation 😃

fvarrui commented 2 months ago

And you don't need modules property

fvarrui commented 2 months ago

Sorry, I trying to answer from my mobile phone, and it's being really difficult.

Please, use my changes in your build.gradle file. Those changes do the magic

SunkenPotato commented 2 months ago

Hey @fvarrui Thank you for answering so quickly! I've now completely copied the gradle file without any changes and recompiled, but something seems off to me. The path specified in vmArgs.module-path is libs, however, I don't have a directory called libs, and even if I do use windows-sdk\lib, I still get the JavaFX components error.

It's the exact task from the gradle file you provided:

tasks.register('packageMyApp', PackageTask) {
    dependsOn build
    mainClass = mainClassName
    bundleJre = true
    customizedJre = true
//    modules = ['javafx.controls', 'javafx.fxml']
    vmArgs = [ '--add-modules=javafx.controls,javafx.fxml', '--module-path=windows-sdk/lib' ] // or 'libs', which doesn't exist
}
SunkenPotato commented 2 months ago

I'd also like to note that the program throws other errors, but they're related to MSIs. The task cannot find the commands issc and candle

fvarrui commented 2 months ago

I've now completely copied the gradle file without any changes and recompiled, but something seems off to me. The path specified in vmArgs.module-path is libs, however, I don't have a directory called libs, and even if I do use windows-sdk\lib, I still get the JavaFX components error.

It's the exact task from the gradle file you provided:

tasks.register('packageMyApp', PackageTask) {
    dependsOn build
    mainClass = mainClassName
    bundleJre = true
    customizedJre = true
//    modules = ['javafx.controls', 'javafx.fxml']
    vmArgs = [ '--add-modules=javafx.controls,javafx.fxml', '--module-path=windows-sdk/lib' ] // or 'libs', which doesn't exist
}

vmArgs are used at runtime by your app, when running the JVM. JP copies by default all dependencies into libs folder inside your app, so when your app starts is able to find JavaFX modules.

Sorry for the quick explanation

fvarrui commented 2 months ago

I'd also like to note that the program throws other errors, but they're related to MSIs. The task cannot find the commands issc and candle

You have to install WiX Tools and Inno Setup. It's explained on README

SunkenPotato commented 2 months ago

It works! Thanks so much for the explanation, and excuse my ignorance about the module-path thing. I didn't realize it used it's own.

Thanks again for all the time and help.

fvarrui commented 2 months ago

https://github.com/fvarrui/JavaPackager?tab=readme-ov-file#generated-artifacts, here you can find a table with all generated artifacts. Take a look to Requires column 😃

fvarrui commented 2 months ago

It works! Thanks so much for the explanation, and excuse my ignorance about the module-path thing. I didn't realize it used it's own.

Thanks again for all the time and help.

You're welcome! I'm happy to help

fvarrui commented 2 months ago

And there's also a guide. Have a nice weekend!

SunkenPotato commented 2 months ago

Thank you! Have a nice weekend too!