はじめに
ここで言う配布とは会社等の内製ツールとしてチームへ配布する想定です。商売する等の目的で配布する場合はライセンスにご注意ください。
前回はFXLauncherで色々と試みましたが、exeファイルの生成には失敗したので、別の方法を模索してみました。こちらのページを参考に手を加えています。
結論
Gradleプラグインのus.kirchmeier.capsuleとedu.sc.seis.launch4jを使って実現出来ました。exeファイルを生成する工程は前回も出てきたlaunch4jを使っています。
手順
順を追って説明していきます。IntelliJでTornadoFXプロジェクトを作成するところまでは前回と同じなので省きます。
[1] fat jarを作る
us.kirchmeier.capsuleを使ってfat jarを作成します。build.gradleに以下の変更を加えます。
version = '0.0.1'// 追加
buildscript {
ext.kotlin_version = "1.2.60"
ext.tornadofx_version = "1.7.17"
ext.junit_version = "5.1.0"
repositories {
mavenLocal()
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"// 追加
}
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.junit.platform:junit-platform-gradle-plugin:1.1.0"
classpath "us.kirchmeier:gradle-capsule-plugin:1.0.2" // 追加
}
}
apply plugin: "kotlin"
apply plugin: "application"
apply plugin: "org.junit.platform.gradle.plugin"
apply plugin: "us.kirchmeier.capsule"// 追加
// ~中略~
// fatJarを生成するタスク
task fatJar(type: FatCapsule) {
applicationClass mainClassName
}
この段階でコンソールから
gradle fatJar
をすると build/libs 以下にjarファイルが2つ生成されます。ファイル名のサフィックスに"-capsule"がついている方が目的のjarファイルです。
[2] exeファイルを作る
edu.sc.seis.launch4jを使って作成します。まずはbuild.gradleを以下のように変更します。
buildscript {
ext.kotlin_version = "1.2.60"
ext.tornadofx_version = "1.7.17"
ext.junit_version = "5.1.0"
repositories {
mavenLocal()
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.junit.platform:junit-platform-gradle-plugin:1.1.0"
classpath "us.kirchmeier:gradle-capsule-plugin:1.0.2"
classpath "edu.sc.seis.launch4j:launch4j:2.4.6" //追加
}
}
apply plugin: "kotlin"
apply plugin: "application"
apply plugin: "org.junit.platform.gradle.plugin"
apply plugin: "us.kirchmeier.capsule"
apply plugin: "edu.sc.seis.launch4j"// 追加
// ~中略~
// exe化するタスク
createExe {
headerType = 'gui' // cuiアプリならconsole
copyConfigurable = project.tasks.fatJar.outputs.files
jar = "lib/${project.tasks.fatJar.archiveName}" //対象のjarファイル(今回はfatJarタスクが生成したファイル)
mainClassName = mainClassName
bundledJrePath = 'jre-win' //実行時に参照するJREのパス(exeファイルからみた相対パス)
bundledJre64Bit = true
outputDir = "output"//exeファイルの出力先
}
createExeというタスクでexeファイルを生成します。
gradle createExe
クリーンビルドする場合は
gradle clean createExe
ビルドが成功すると、build/outputにexeファイルが出力されます。
オプションが色々とありますが、詳しいことはgradle-launch4jのgithubにある程度まとまっています。空欄のものもあるので全ては把握出来ませんが…。注意点はbundledJrePathで指定するパスはexeファイル実行時に参照するJREのパスです。ですので、このあとJREを同梱するタスクを追加しますが、そこで同梱したJREへのexeファイルからみた相対パスです。
[3] 配布用にまとめてzipでアーカイブする
build.gradleにタスクを追加します。
// アーカイブ時のテンポラリディレクトリ
ext {
archiveTmpDir = "${buildDir}/${project.name}_tmp"
}
// アーカイブ前に対象のファイルをテンポラリディレクトリにコピーするタスク
task prePack(dependsOn: createExe) doLast {
copy {
from "${buildDir}/${createExe.outputDir}/${project.name}.exe"
into archiveTmpDir
}
copy {
from 'D:/Amazon Corretto/jre8'// 同梱するJREのパス。例えば左記のような感じ。
into "${archiveTmpDir}/${createExe.bundledJrePath}"
}
}
// zipでアーカイブ
task pack(type: Zip, dependsOn: prePack) {
from archiveTmpDir
archiveName "MyApp.zip"
}
この段階でコンソールで
gradle pack
クリーンビルドする場合は
gradle clean pack
とすると、build/distributions以下にMyApp.zipというzipファイルが出来上がります。このzipファイルを配布すればJREがインストールされていない環境でも動作するはずです。
まとめ
ひとまずTornadoFXで作成したアプリをJAR、更にはexeファイルにまとめるという目的は達しましたが、JREがまるごと配布ファイルに含まれるということでHello World程度のアプリケーションでもzipファイルが巨大です。もっといいパッケージングの方法があるかもしれません。
また、今回は非常にシンプルなアプリであるため上手く動いている可能性があります。複雑なGUIアプリケーションをTornadoFXで作成した場合に上手くいかないかもしれません。
最後にbuild.gradle全体を載せます。
version = '0.0.1'
buildscript {
ext.kotlin_version = "1.2.60"
ext.tornadofx_version = "1.7.17"
ext.junit_version = "5.1.0"
repositories {
mavenLocal()
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.junit.platform:junit-platform-gradle-plugin:1.1.0"
classpath "us.kirchmeier:gradle-capsule-plugin:1.0.2"
classpath "edu.sc.seis.launch4j:launch4j:2.4.6"
}
}
apply plugin: "kotlin"
apply plugin: "application"
apply plugin: "org.junit.platform.gradle.plugin"
apply plugin: "us.kirchmeier.capsule"
apply plugin: "edu.sc.seis.launch4j"
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
repositories {
mavenLocal()
mavenCentral()
maven {
url "https://oss.sonatype.org/content/repositories/snapshots/"
}
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compile "no.tornado:tornadofx:$tornadofx_version"
testCompile "org.junit.jupiter:junit-jupiter-api:$junit_version"
testRuntime "org.junit.jupiter:junit-jupiter-engine:$junit_version"
}
mainClassName = "com.example.demo.app.MyApp"
jar {
manifest {
attributes(
"Class-Path": configurations.compile.collect { it.getName() }.join(" "),
"Main-Class": mainClassName
)
}
from(configurations.compile.collect { entry -> zipTree(entry) }) {
exclude "META-INF/MANIFEST.MF"
exclude "META-INF/*.SF"
exclude "META-INF/*.DSA"
exclude "META-INF/*.RSA"
}
}
ext {
archiveTmpDir = "${buildDir}/${project.name}_tmp"
}
task fatJar(type: FatCapsule) {
applicationClass mainClassName
}
task prePack(dependsOn: createExe) doLast {
copy {
from "${buildDir}/${createExe.outputDir}/${project.name}.exe"
into archiveTmpDir
}
copy {
from '同梱したいJREへのパス' // 例えば D:/Amazon Corretto/jre8
into "${archiveTmpDir}/${createExe.bundledJrePath}"
}
}
task pack(type: Zip, dependsOn: prePack) {
from archiveTmpDir
archiveName "MyApp.zip"
}
createExe {
headerType = 'gui'
copyConfigurable = project.tasks.fatJar.outputs.files
jar = "lib/${project.tasks.fatJar.archiveName}"
mainClassName = mainClassName
bundledJrePath = 'jre-win'
bundledJre64Bit = true
outputDir = "output"
}