You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于Kotlin Gradle DSL创建平台无关的JavaFX可执行Jar

JavaFX项目Gradle配置问题:无法运行Main类及生成平台无关Jar包

我们团队维护着一个以Swing为核心的大型遗留Java应用,我负责开发一个独立分支项目:基于Temurin JDK 21(未捆绑JavaFX)、采用Kotlin Gradle DSL管理的JavaFX项目,包含UI面板、业务逻辑,以及两个用于独立测试的main方法(分别位于Main.java和Planner.java中)。

我需要将该项目发布到公司的Artefactory,但当前的build.gradle.kts配置无法满足以下两个需求:

  • 无法运行带有main方法的类;
  • 无法生成不依赖平台特定JavaFX实现的Jar包。

当前配置代码如下:

import org.gradle.internal.os.OperatingSystem

plugins {
    java
    application
    `maven-publish`

    // I can't imagine this is necessary but my module isn't able to import JavaFX without it.
    id("org.openjfx.javafxplugin") version "0.0.14"
}

java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(21))
    }
}

val isRelease = project.hasProperty("release")
val appVersion = "0.1" + if (isRelease) "" else "-SNAPSHOT"
val modulePath by lazy { "${configurations.runtimeClasspath.get().asPath}:build/classes/java/main" }
val moduleString by lazy { javafx.modules.joinToString(separator = ",") }

application {
    version = appVersion
    group = "my.organization.subproject"
    mainClass.set("my.organization.subproject.fx.Main")
    mainModule.set("AutoMosaic")
}

repositories {
    mavenCentral()
}

javafx {
    version = "21"
    modules("javafx.controls", "javafx.swing")

    // When this is enabled, I cannot run the classes with Main methods.
    configuration = "compileOnly"
}

val javafxPlatform: String = when {
    OperatingSystem.current().isMacOsX && System.getProperty("os.arch") == "aarch64" -> "mac-aarch64" // ARM Mac
    OperatingSystem.current().isMacOsX && System.getProperty("os.arch") == "x86_64" -> "mac" // Intel Mac
    OperatingSystem.current().isWindows -> "win"
    OperatingSystem.current().isLinux -> "linux"
    else -> throw GradleException("Unsupported OS for JavaFX")
}

dependencies {
    // In an attempt to only rely on platform-neutral dependencies.
    compileOnly("org.openjfx:javafx-base:21")
    compileOnly("org.openjfx:javafx-controls:21")
    compileOnly("org.openjfx:javafx-graphics:21")
    compileOnly("org.openjfx:javafx-swing:21")

    // To execute Main and Planner locally.
    runtimeOnly("org.openjfx:javafx-base:21:$javafxPlatform")
    runtimeOnly("org.openjfx:javafx-controls:21:$javafxPlatform")
    runtimeOnly("org.openjfx:javafx-graphics:21:$javafxPlatform")
    runtimeOnly("org.openjfx:javafx-swing:21:$javafxPlatform")
}

// To create the Main and Planner Gradle tasks that can be run.
fun createJavaExecTask(taskName: String, mainClassName: String) {
    tasks.register<JavaExec>(taskName) {
        group = "application"
        mainClass.set(mainClassName)
        jvmArgs = listOf(
            "--module-path", modulePath,
            "--add-modules", moduleString
        )
        classpath = sourceSets["main"].runtimeClasspath
    }
}

// To run these through IDEA, configure Gradle tasks with Tasks and Arguments:
// 1. Main: main
// 2. Planner: planner
// Then they should be runnable via IDEA. The issue is that the module path for the JavaFX jars
// managed by the plugin are incredibly obtuse and can change, so even grabbing them and
// hard-coding them in an Application task yields issues.
createJavaExecTask("main", "my.organization.subproject.fx.Main")
createJavaExecTask("planner", "my.organization.subproject.fx.Planner")

// Print the arguments necessary to configure an IDEA task.
tasks.register("jvmOpts") {
    doLast {
        println("JVM opts required to make an IDEA task:")
        println("--module-path $modulePath --add-modules $moduleString")
    }
}

val repositoryUrl = "https://jfrog.organization.edu/artifactory/apt-maven"

val sourcesJar by tasks.registering(Jar::class) {
    from(sourceSets.main.get().allSource)
    archiveClassifier.set("sources")
}

publishing {
    publications {
        create<MavenPublication>("mavenJava") {
            groupId = "my.organization.mainproject"
            from(components["java"])
            artifact(sourcesJar.get())
        }
    }
    repositories {
        maven {
            val repoPassword: String? by project
            val repoUsername: String? by project
            url = uri(repositoryUrl + (if (project.hasProperty("release")) { "-releases" } else { "-snapshots" }))
            credentials {
                username = repoUsername
                password = repoPassword
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者Sebastian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 09:42:06