Javaから実行可能なJARや実行ファイルをGradleで作る

  • 61
    Like
  • 0
    Comment
More than 1 year has passed since last update.

Gradleではapplicationプラグインを使うことで実行用のスクリプトを生成できるが,この方法では複数のファイルに別れてしまい,使い勝手が悪い.そこで,単一ファイルにしたりインストーラーで簡単に導入できるようにしたファイルの作り方を紹介する.

方法その1 依存するクラスをすべて内包したJARを作る

gradleをつかって依存ライブラリを含む単一で実行可能なjarを生成するなどで紹介されているが,このまま使うと,このプロジェクトに依存したプロジェクトを作るとクラスの重複が起こってしまって問題になるので,別のタスクとして定義し,別の名前のファイルになるようにする.

apply plugin: 'java'
apply plugin: 'application'

mainClassName = 'info.informationsea.java.excel2csv.Excel2CSV'
def executableBaseName = "excel2csv"

task executableJar(type: Jar, dependsOn: jar)  {
    archiveName = "${executableBaseName}.jar"
    manifest {
        attributes 'Main-Class' : mainClassName
    }

    from configurations.compile.collect {it.isDirectory() ? it : zipTree(it)}
    from "$buildDir/classes/main"
    from "$buildDir/resources/main"
}

こうしておくと${executableBaseName}.jarbuild/libsにでき上がる.

方法その1-2 java -jarなしで実行可能にする

java -jarで指定するJARには先頭に余計なデータがついていても途中のJARの部分から読んでくれるので,ファイルの先頭に

#!/bin/sh
exec java -jar $0 "$@"

と書いて,後ろにJARをくっつけておくとそのまま実行できる(Linux, OS Xの場合).そのようなファイルを自動生成するタスクは以下の通り.上で説明したexecutableJarタスクが存在していることが前提となっている.

task createExecutable(dependsOn: executableJar) << {
    def executableFile = file("$buildDir/deploy/${executableBaseName}")
    executableFile.parentFile.mkdirs()

    def output = new FileOutputStream(executableFile)
    output.write(("#!/bin/sh\n" +
            "\n" +
            "exec java -jar \$0 \"\$@\"\n").bytes)
    output.write file("$libsDir/${executableBaseName}.jar").bytes
    output.close()
    executableFile.executable = true
}

方法その1-3 javapackagerでJREを入りのインストーラーを作る

javapackagerコマンドを利用するとJREを含んだインストーラーやアプリケーションを作ることができる.これによりGUIのJavaのプログラムを配るときに,JREをインストールさせなくて良いので楽になる.ただし,コマンドラインで使うアプリケーションには使えない.Antタスクでもできるようだが,いまいちやり方が分からないのでコマンドを呼んでいて,そのせいで汎用性に乏しいコードになってしまっている.以下のコードは OS X 専用.

task nativePackage(dependsOn: executableJar) << {
    file("$buildDir/deploy").mkdirs()

    exec {
        executable System.getProperty("java.home")+"/../bin/javapackager"
        args "-deploy", "-title", "${project.name}", "-name", project.name, "-appclass", mainClassName,
                "-native", "installer", "-outdir", "$buildDir/deploy", "-outfile", project.name,
                "-srcdir", "$libsDir", "-srcfiles", "${executableBaseName}.jar"
    }
}

その他のOSでも同様にコマンドを呼んであげれば良い. OS X 専用になってしまっているのはjavapackagerを探すところだけなので,他のOSでもコマンドの呼び出し方は同じ.-srcfilesに指定するJARは必要なファイルがすべて含まれているものにする.

javapackagerはお仕着せ感の強いアプリケーションなので,インストーラーに利用規約を追加したり,App Store提出用バイナリにPermission追加したりするのは面倒くさそう.

方法その2 Capsuleを使う

一部の Class Path をスキャンするようなプログラムでは上で説明したように一つのJARにまとめてしまうと上手く動かない(たとえばSpring).そこで単一JARの中に複数のJARをまとめて入れて,実行時に展開させることができる.そのためには Capsule を使う.

Gradle Capsule Pluginを使うことで,Gradleから簡単に使うことができる.

plugins {
    id "us.kirchmeier.capsule" version "0.10.0"
}

apply plugin: 'java'
apply plugin: 'application'

mainClassName = 'info.informationsea.java.excel2csv.Excel2CSV'

task fatCapsule(type: FatCapsule) {
    applicationClass mainClassName
}

task executableCapsule(type: FatCapsule) {
    applicationClass mainClassName
    reallyExecutable
    archiveName = "excel2csv"
}

ここでは二つのタスクを定義している.最初のfatCapsuleは実行可能なJARを作成している.このプログラムは実行時に内部のJARを~/.capsule/apps以下に展開して実行する.このときJVMを新たに立ち上げるのでメモリを多く使ったり,場合によっては面倒なことになるかもしれない.

次のexecutableCapsuleは上の 方法その1-2 java -jarなしで実行可能にする で説明したスクリプトと同じようなことをするためのもの.archiveNameを設定することで自由にファイル名を変えられる.