【问题标题】:How to deploy a javafx application as an executable jar or exe with gradle?如何将 javafx 应用程序部署为带有 gradle 的可执行 jar 或 exe?
【发布时间】:2021-01-02 13:17:32
【问题描述】:

我想在 javafx 中创建一个应用程序。我是 java fx 和 gradle 的新手,所以目前我只是在搞事情。我想尝试的第一件事是将应用程序构建到可执行的 jar 或 exe 中。我已经尝试了几件事,但我从来没有得到一个简单的 jar 来执行。当我双击它时它不会执行。当我尝试从命令行执行它时,我收到此错误:错误:JavaFX 运行时组件丢失,需要运行此应用程序 我尝试了很多在网上找到的解决方案,但都没有成功。

这是应用程序的代码:

package org.example;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;

public class Main extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        Label label = new Label("hello world");
        Scene scene = new Scene(label);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

这是我的 build.gradle 文件:

plugins {
    id 'java'
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.9'
}

group 'org.example'
version ''

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
}

javafx {
    version = "15.0.1"
    modules = [ 'javafx.controls','javafx.fxml' ]
}
mainClassName = 'org.example.Main'
jar {
    manifest {
        attributes 'Main-Class': 'org.example.Main'
    }
    from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

有人知道如何解决这个问题吗?

【问题讨论】:

    标签: java gradle javafx exe executable-jar


    【解决方案1】:

    使用jlink 构建包含JavaFX 模块的Java 运行时。

    使用 jpackage(来自 JDK 14 及更高版本)使用该运行时捆绑您的应用程序。这将为您提供 .exe 启动器和特定于平台的安装程序。

    Gradle 'Exec' 任务可以轻松地为您运行这两个工具作为构建的一部分。

    为使事情变得更简单,您可以考虑使用包含 JavaFX 模块的 JDK,这样您就不必费心配置 JavaFX SDK 和模块路径。 Azul 和 Bellsoft 的 OpenJDK 版本包含 JavaFX。

    这是我在 jpackage 中使用的那种任务的示例。我的“appImage”任务复制文件以准备捆绑,我的 jlink 任务创建运行时映像。此任务创建一个可运行的应用程序映像,我将其用于第二次调用 jpackage 以生成安装程序。如果您不需要安装程序,您也可以只压缩此图像,但每个图像都是特定于平台的,因为它包含本机启动器和要使用的 JRE:

    task jpackageImage(type: Exec, dependsOn: [jlink, appImage]) {
        workingDir = project.projectDir
        inputs.property('consoleApp', project.consoleApp)
        inputs.property('vendorName', project.vendorName)
        if (project.hasProperty('copyright')) {
            inputs.property('copyright', project.copyright)
        }
        // TODO set input directory
        inputs.dir "${buildDir}${File.separator}image${File.separator}app"
        inputs.dir "${buildDir}${File.separator}image${File.separator}runtime"
        // define outputs
        outputs.dir "${buildDir}${File.separator}application"
        
        // in a doFirst in case values change (e.g. archive name gets version bump)
        // after configuration phase
        doFirst {
            // Error: Application output directory XXXXXXXXXXXXXX already exists.
            def tmpRoot = "$buildDir/tmp/image"
            project.delete tmpRoot
            project.delete "${buildDir}${File.separator}application"
            
            // resource directories need to exist
            project.mkdir resourceDir
    
            //file("${buildDir}/image/app/README.txt").text = "This file should be installed."
    
            def appName = project.applicationName // project.applicationName.replaceAll(" ","")
            def copyrightStr = project.hasProperty('copyright') ? project.copyright.toString() : "Copyright (c) ${year} ${vendorName}".toString().trim()
            def tmp = [
                "${jpackageTool}",
                '--type', 'app-image', // Valid values on Windows are: {"app-image", "exe", "msi"}
                '--verbose',
                '--temp', tmpRoot,
                '--app-version', project.version,
                '--input', "${buildDir}${File.separator}image${File.separator}app",
                '--runtime-image', "${buildDir}${File.separator}image${File.separator}runtime",
                '--name', appName,
                '--main-jar', "libs${File.separator}${configurations.runtime.artifacts.files.singleFile.name}",
                '--main-class', mainClass,
                '--resource-dir', resourceDir,
                '--icon', iconFileStr,
                '--description', project.description,
                '--vendor', vendorName,
                //'--category', 'Utility',
                '--copyright', copyrightStr,
                '--dest', "${buildDir}${File.separator}application",
                ]
            // Use a console app for easier debugging (log messages/debug prints are visible)
            if (osName.startsWith('windows')) {
                // Windows-specific options
                //'--win-menu',
                //'--win-menu-group', vendorName,
                //'--win-upgrade-uuid', project.upgradeUUID,
                //'--win-shortcut',
                // for a console application
                if (project.consoleApp) {
                    tmp.addAll(['--win-console'])
                }
            }
            if (osName.startsWith('mac')) {
                tmp.addAll([
                    // macOS-specific options
                    //This name must be less than 16 characters long and be suitable for displaying in the menu bar and the application Info window.
                    '--mac-package-name', project.macPkgName,
                    '--mac-package-identifier', project.macPkgIndentifier,
                    //'--mac-package-signing-prefix', <prefix string>,
                    //'--mac-sign', // Request that the bundle be signed
                    //'--mac-signing-keychain', <file path>,
                    //'--mac-signing-key-user-name', '<team name>'
                ])
            }
            commandLine = tmp
            println commandLine
        }
    
        // workaround https://bugs.openjdk.java.net/browse/JDK-8254920
        doLast {
            if (osName.startsWith('windows') && jlinkCompression == 2) {
                project.copy {
                    from "${buildDir}\\application\\${project.applicationName}\\runtime\\bin\\zip.dll"
                    into "${buildDir}\\application\\${project.applicationName}"
                }
            }
        }
    }
    

    我真的应该在某个时候为这些东西制作一个合适的 Gradle 插件。

    您可以在https://docs.oracle.com/en/java/javase/14/docs/specs/man/jpackage.html了解更多关于 jpackage 的信息

    【讨论】:

    • 你有资源可以让我了解 jpackage 的工作原理吗?
    • @yentepaternotte 我已经用我的 Gradle 任务之一扩展了答案以运行 jpackage,并提供了文档链接。
    • @yentepaternotte 您不必自己手动配置所有内容。查看 Gradle 的 badass-jlink-plugin
    【解决方案2】:

    如果您想对像您这样的示例进行短期的、有点不稳定的修复,您可以在与当前类路径相同的类路径中拥有另一个类,并带有一个 main 方法。您在该方法的主体中要做的是执行 Main.main(args)。这将诱使程序不认为它是 Java 应用程序,从而使其正常工作。此外,您必须将主类名称更改为新类的名称。

    来源:https://edencoding.com/runtime-components-error/

    【讨论】:

    • 没有稳定的长期解决方案吗?
    • @yentepaternotte 有,但很难。不过,如果你确实想要它,它在我提供的源代码中。
    猜你喜欢
    • 2021-08-05
    • 1970-01-01
    • 2012-07-11
    • 2015-05-03
    • 2015-12-20
    • 1970-01-01
    • 2014-09-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多