# 【TornadoFX】Badass Runtime Pluginを使ってユーザ環境にJREが不要のデスクトップアプリを作ってみる【Kotlin】
# この記事でやること
(忙しい人はこのリポジトリの build.gradle.kts
を見てください。)
Badass Runtime Plugin を使って TornadoFX 製のデスクトップアプリを .exe
/ .app
形式にビルドします。
ユーザ環境にはJREのインストールが不要です。
また、GitHub Actionsで .exe
と .app
にビルドしてリリースするところまでやります。
# 開発環境
以下が私の開発環境です。
(GitHub Actionsの ubuntu-latest
と windows-latest
上でもビルドが通ったことが確認できたので、JDKのバージョンさえ気をつければ大丈夫かと思われます。)
- JDK 14
- バージョンは絶対
14
以降にしてください。jpackageを使うので。 - 私はこれ (https://jdk.java.net/14/) で実行しました。 (環境構築時の2020/5/2時点で
14.0.1
) - JavaFX (OpenJFX) が入っていないディストリビューションで大丈夫です。
- バージョンは絶対
- IntelliJ IDEA 2020.1.1 (Community Edition)
- macOS 10.14.6
# Gradleの設定
Kotlin DSLとbuildSrcで書いています。 Groovyで書いている人は脳内で変換してください。
# プロジェクトの構成
マルチモジュールなプロジェクトにしてありますので、一応軽く構成を紹介しておきます。
プロジェクトルート/
│
├ app/ (:appモジュール)
│ ├ src/main/kotlin/...
│ └ build.gradle.kts
│
├ buildSrc/
│ ├ src/main/kotlin/...
│ └ build.gradle.kts
│
├ modules/
│ └ core/
│ │
│ ├ core-application/ (:core-applicationモジュール)
│ │ ├ src/main/kotlin/...
│ │ └ build.gradle.kts
│ │
│ ├ core-data/ (:core-dataモジュール)
│ │ ├ src/main/kotlin/...
│ │ └ build.gradle.kts
│ │
│ ├ core-domain/ (:core-domainモジュール)
│ │ ├ src/main/kotlin/...
│ │ └ build.gradle.kts
│ │
│ ├ core-ui/ (:core-uiモジュール)
│ │ ├ src/main/kotlin/...
│ │ └ build.gradle.kts
main
関数を置いてある :app
モジュールがあり、modules
ディレクトリ下に core
ディレクトリを作り、そこに :core-*
モジュールを配置してあります。
これらはJavaFXアプリのモジュールで、設定を共通化したかったので、ルートの build.gradle.kts
の subprojects{}
ブロックに設定をまとめてあります。
# settings.gradle.kts
↑のモジュールの配置になるように書いています。
rootProject.name = "MyTornadoFX"
include(":app")
include(":core-ui", ":core-application", ":core-data", ":core-domain")
project(":core-ui").projectDir = File("modules/core/core-ui")
project(":core-application").projectDir = File("modules/core/core-application")
project(":core-data").projectDir = File("modules/core/core-data")
project(":core-domain").projectDir = File("modules/core/core-domain")
# ルートのbuild.gradle.kts
さて、プロジェクトのルートの build.gradle.kts
です。
import org.apache.tools.ant.taskdefs.condition.Os
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
plugins {
id("org.openjfx.javafxplugin") version "0.0.8" // 👈 これちゃんと書いてね💖
id("org.jlleitschuh.gradle.ktlint") version "9.2.1"
id("com.github.ben-manes.versions") version "0.28.0"
}
buildscript {
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath(kotlin("gradle-plugin", version = "1.3.72"))
}
}
allprojects {
repositories {
jcenter()
mavenCentral()
}
apply(plugin = "org.jlleitschuh.gradle.ktlint")
ktlint {
outputColorName.set("RED")
ignoreFailures.set(true)
disabledRules.set(listOf("import-ordering", "no-wildcard-imports"))
reporters { reporter(ReporterType.CHECKSTYLE) }
}
}
subprojects {
apply(plugin = "org.jetbrains.kotlin.jvm")
apply(plugin = "org.openjfx.javafxplugin")
javafx {
version = JavaVersion.VERSION_14.toString()
modules = listOf("javafx.controls", "javafx.fxml", "javafx.graphics") // 👈 これちゃんと書いてね💖
}
tasks.withType<KotlinCompile> {
sourceCompatibility = JavaVersion.VERSION_13.toString()
targetCompatibility = JavaVersion.VERSION_13.toString()
kotlinOptions.jvmTarget = JavaVersion.VERSION_13.toString()
}
}
tasks.register<Zip>("release") {
dependsOn("app:jpackageImage")
val releaseName = "$MyTornadoFx v1.0.0"
val archiveName = when {
Os.isFamily(Os.FAMILY_WINDOWS) -> "$releaseName windows.zip"
Os.isFamily(Os.FAMILY_MAC) -> "$releaseName mac.zip"
Os.isFamily(Os.FAMILY_UNIX) -> "$releaseName linux.zip"
else -> throw UnsupportedOperationException()
}
archiveFileName.set(archiveName)
into(releaseName) {
from("README.md")
from("app/build/jpackage/")
}
}
# JavaFX Gradle Pluginの設定
JavaFXが付いてこないJDKのディストリビューションを使っている場合でも JavaFX Gradle Plugin を使うことでJavaFXアプリを作ることができます。
https://openjfx.io/openjfx-docs/#gradle
ここのGradle版での手順通りです。
(前述した通り、マルチモジュールなので subprojects{}
内に書いてます。)
# :appモジュールのbuild.gradle.kts
import org.apache.tools.ant.taskdefs.condition.Os
plugins {
id("application")
id("org.beryx.runtime") version "1.8.3" // 👈 これちゃんと書いてね💖
}
application {
mainClassName = "net.aridai.mytornadofx.MainKt"
applicationName = "MyTornadoFx"
}
runtime {
options.set(listOf("--strip-debug", "--compress", "2", "--no-header-files", "--no-man-pages"))
modules.set(listOf( // 👈 これちゃんと書いてね💖
"java.desktop",
"java.xml",
"jdk.unsupported",
"java.scripting",
"jdk.jfr",
"java.logging",
"java.prefs"))
jpackage {
when {
Os.isFamily(Os.FAMILY_WINDOWS) -> imageOptions = listOf("--icon", "src/main/resources/icon.ico")
Os.isFamily(Os.FAMILY_MAC) -> imageOptions = listOf("--icon", "src/main/resources/icon.icns")
}
imageName = "MyTornadoFx"
}
}
dependencies {
implementation(project(":core-ui"))
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72")
implementation("no.tornado:tornadofx:1.7.20") // 👈 これちゃんと書いてね💖
}
# Badass Runtime Pluginの設定
plugins { id("org.beryx.runtime") version "1.8.3" }
といった感じで Badass Runtime Plugin をapplyします。
このプラグインは非モジュール化アプリでランタイムイメージを作成してくれます。
ありがたや。
runtime {}
ブロックにランタイムイメージに含めるモジュールの設定 (と後は実行可能ファイルのメタ情報など) を書きます。
今回はJavaFXアプリなので java.desktop
や java.xml
などを指定します。
ここに指定すべきモジュールは ./gradlew suggestModules
を実行することでこんな感じに教えてくれます。
ありがたや。ありがたや。
> Task :app:suggestModules
modules = [
'java.scripting',
'java.xml',
'java.desktop',
'jdk.unsupported',
'jdk.jfr',
'java.logging',
'java.prefs']
# GitHub Actionsでのリリース処理
プロジェクトルートの build.gradle.kts
に以下のようなタスクを生やしてあります。
tasks.register<Zip>("release") {
dependsOn("app:jpackageImage")
val releaseName = "$MyTornadoFx v1.0.0"
val archiveName = when {
Os.isFamily(Os.FAMILY_WINDOWS) -> "$releaseName windows.zip"
Os.isFamily(Os.FAMILY_MAC) -> "$releaseName mac.zip"
Os.isFamily(Os.FAMILY_UNIX) -> "$releaseName linux.zip"
else -> throw UnsupportedOperationException()
}
archiveFileName.set(archiveName)
into(releaseName) {
from("README.md")
from("app/build/jpackage/")
}
}
Badass Runtime Pluginの jpackageImage
タスクによって作られた .exe
または .app
を README.md
と一緒にzipに詰めるだけのタスクです。
GitHub Actionsのリリース用のworkflowで使います。
GitHub Actions用の設定ファイルが以下になります。
(.github/workflows/Release.yml
)
name: Release
on:
push:
tags:
- "*"
jobs:
release:
strategy:
matrix:
os: [macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v1
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Setup Java 14
id: setup-java-14
uses: actions/setup-java@v1
with:
java-version: '14'
java-package: jdk
architecture: x64
- name: Build
id: build
run: ./gradlew release
- name: Release
uses: softprops/action-gh-release@v1
with:
files: './build/distributions/*.zip'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
何かしらのタグがpushされたときをトリガにしていて、WindowsとmacOSでマトリックスビルドを掛けてます。
ビルドの成果物 (./gradlew release
で生成したzipファイル) をReleaseページにアップロードするために softprops/action-gh-release を利用しました。
公式の actions/upload-release-asset が複数のAssetにまだ対応していなかったみたいなので。
(#16)
私のサンプルプロジェクトでも試しに こんな感じ にやってみました。