我的组织正在开发一个大型 Java 应用程序,其旧代码库主要是用 Swing 编写的,而我的任务是开发一个将独立管理的分支项目,使用 JavaFX 编写(在 Temurin JDK 21 中,因此 JavaFX 不是捆绑)并使用 Kotlin Gradle DSL 进行管理。该项目包含一个面板和一些业务逻辑,并且还有两种主要方法来独立测试它(一种在
Main.java
中,一种在 UI 面板中,Planner.java
)。
我想将其发布到我们的 artefactory,但我没有运气构建我的
build.gradle.kts
文件以便能够:
这就是我现在的位置:
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
}
}
}
}
我不是此类领域的 Gradle 专家:我们将不胜感激任何帮助。
根据您既定的目标和当前的构建脚本,我认为以下构建脚本示例至少应该帮助您指明正确的方向。一些注意事项:
我(非详尽)使用 Gradle 8.11 测试了这些示例。
两个示例都使用
0.1.0
插件的 org.openjfx.javafxplugin
版本。这是截至此答案的最新版本,并且是使示例正常工作所必需的。依赖于您的项目的其他 Gradle 项目可能也需要应用该插件才能找到正确的 JavaFX 工件。
您正在使用的版本 (
0.0.14
) 和旧版本的工作方式不同,并且会导致分类器包含在生成的 POM 中(例如,<classifier>win</classifier>
)。
我应用了 Java Library Plugin 而不是 Application Plugin。看起来您的项目更像是一个库而不是应用程序。您可以应用这两个插件,但目前您似乎并未真正使用 Application Plugin 的任何功能。
对于第一个示例,由于您似乎正在创建一个库,因此我将 JavaFX 依赖项放在
api
配置中。假设您的公共 API 公开 JavaFX。它还导致生成的 POM 中的依赖项被放入 compile
范围内。如果您的公共 API 不公开 JavaFX,您可以使用
implementation
配置。请注意,这会将生成的 POM 中的依赖项放在 runtime
范围内。
withSourcesJar()
块中的java
会自动添加一个sourcesJar
任务,并且该任务创建的JAR将包含在Maven发布中。如果您想包含项目 Javadoc 的 JAR,您可以添加 withJavadocJar()
调用。
您当前的构建脚本表明您的项目有一个
module-info.java
文件。因此,我设置了两个 mainModule
任务的 JavaExec
属性。与 modularity.inferModulePath
为 true
(默认情况下)相结合,任务会将文件放入 classpath
上的 --module-path
属性中,并通过 --module
启动程序。至少在最新版本的 Gradle 上是这样的。
两个注册的
JavaExec
任务依赖于 jar
任务,并且 JAR 放在模块路径上而不是 main.output
上。两者都是因为您当前的构建脚本表明您的项目是模块化的。这是确保任何资源(即 src/main/resources
下的那些文件)最终位于模块内的一种方法。这也是如何将 Application Plugin中的
run
任务配置为适用于模块化项目。
plugins {
id("org.openjfx.javafxplugin") version "0.1.0"
`java-library`
`maven-publish`
}
val isRelease = hasProperty("release")
group = "my.organization.subproject"
version = "0.1" + if (isRelease) "" else "-SNAPSHOT"
java {
withSourcesJar()
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
dependencies {
val javafxVersion = "21.0.5"
api("org.openjfx:javafx-controls:$javafxVersion")
api("org.openjfx:javafx-swing:$javafxVersion")
}
publishing {
publications {
create<MavenPublication>("mavenJava") {
from(components["java"])
}
}
repositories {
maven {
val baseUrl = "https://jfrog.organization.edu/artifactory/apt-maven-"
url = uri(baseUrl + if (isRelease) "release" else "snapshot")
credentials {
username = findProperty("repoUsername")?.toString() ?: ""
password = findProperty("repoPassword")?.toString() ?: ""
}
}
}
}
tasks {
val main by sourceSets
val modPath = (main.runtimeClasspath - main.output) + files(jar.flatMap { it.archiveFile })
register<JavaExec>("runMain") {
dependsOn(jar)
group = "application"
mainModule = "AutoMosaic"
mainClass = "my.organization.subproject.fx.Main"
classpath = modPath
}
register<JavaExec>("runPlanner") {
dependsOn(jar)
group = "application"
mainModule = "AutoMosaic"
mainClass = "my.organization.subproject.fx.Planner"
classpath = modPath
}
}
发布到 Maven 存储库时,这将生成以下 POM(在本例中项目名称为
example
):
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!-- This module was also published with a richer model, Gradle metadata, -->
<!-- which should be used instead. Do not delete the following line which -->
<!-- is to indicate to Gradle or any Gradle module metadata file consumer -->
<!-- that they should prefer consuming it instead. -->
<!-- do_not_remove: published-with-gradle-metadata -->
<modelVersion>4.0.0</modelVersion>
<groupId>my.organization.subproject</groupId>
<artifactId>example</artifactId>
<version>0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>21.0.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-swing</artifactId>
<version>21.0.5</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
如果您希望依赖项最终位于
provided
范围内,那么不幸的是,似乎没有一种简单的方法可以做到这一点。请参阅 How Map gradle 'compileOnly' to 'provided' in generated pom (generatePom...) 寻找潜在的解决方案,但我不知道如何将 Groovy DSL 转换为 Kotlin DSL。请注意,流行的 JavaFX 库ControlsFX,只是从生成的 POM 中删除了 JavaFX 依赖项。因此,您可能只想使用第二个示例的方法。
从 POM 中排除 JavaFX 依赖项javafx
)排除了 JavaFX 依赖项。Maven Publish Plugin 不会包含该配置的依赖项,至少默认情况下不会。然后,将
compileClasspath
和
runtimeClasspath
配置配置为从
javafx
配置扩展,以确保
JavaCompile
和
JavaExec
任务继续工作(或者至少使配置它们变得更容易)。
plugins {
id("org.openjfx.javafxplugin") version "0.1.0"
`java-library`
`maven-publish`
}
val isRelease = hasProperty("release")
group = "my.organization.subproject"
version = "0.1" + if (isRelease) "" else "-SNAPSHOT"
java {
withSourcesJar()
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
configurations {
val javafx = create("javafx")
compileClasspath.configure {
extendsFrom(javafx)
}
runtimeClasspath.configure {
extendsFrom(javafx)
}
}
dependencies {
val javafxVersion = "21.0.5"
"javafx"("org.openjfx:javafx-controls:$javafxVersion")
"javafx"("org.openjfx:javafx-swing:$javafxVersion")
}
publishing {
publications {
create<MavenPublication>("mavenJava") {
from(components["java"])
}
}
repositories {
maven {
val baseUrl = "https://jfrog.organization.edu/artifactory/apt-maven-"
url = uri(baseUrl + if (isRelease) "release" else "snapshot")
credentials {
username = findProperty("repoUsername")?.toString() ?: ""
password = findProperty("repoPassword")?.toString() ?: ""
}
}
}
}
tasks {
val main by sourceSets
val modPath = (main.runtimeClasspath - main.output) + files(jar.flatMap { it.archiveFile })
register<JavaExec>("runMain") {
dependsOn(jar)
group = "application"
mainModule = "AutoMosaic"
mainClass = "my.organization.subproject.fx.Main"
classpath = modPath
}
register<JavaExec>("runPlanner") {
dependsOn(jar)
group = "application"
mainModule = "AutoMosaic"
mainClass = "my.organization.subproject.fx.Planner"
classpath = modPath
}
}
发布到 Maven 存储库时,这将生成以下 POM(在本例中项目名称为 example
):
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!-- This module was also published with a richer model, Gradle metadata, -->
<!-- which should be used instead. Do not delete the following line which -->
<!-- is to indicate to Gradle or any Gradle module metadata file consumer -->
<!-- that they should prefer consuming it instead. -->
<!-- do_not_remove: published-with-gradle-metadata -->
<modelVersion>4.0.0</modelVersion>
<groupId>my.organization.subproject</groupId>
<artifactId>example</artifactId>
<version>0.1-SNAPSHOT</version>
</project>