Kotlin
gradle

ことりんと一緒 - 3. 実行可能 JAR

説明

2. Gradle プロジェクト では、Kotlin を Gradle でビルドするプロジェクトを作成し、実際にビルド&実行してみました。

ところで、実行の仕方は gradle run でした。つまり、Kotlinの標準ライブラリへの参照をもっている Gradle プロジェクトから実行を行っていました。

では、生成された JAR ファイルを直接実行するとどうなっていたでしょうか。試してみます。

$ java -jar build/libs/sample-1.0-SNAPSHOT.jar
build/libs/sample-1.0-SNAPSHOT.jarにメイン・マニフェスト属性がありません

このようにマニフェスト属性がないというエラーが出力されます。
つまり、JAR ファイルのマニフェストにどのクラスが MainClass かという情報が欠落しているのです。

このエラーを解消して 実行可能JAR を Gradle で作成してみます。

前提

次の環境で作業しています。

項目 内容
OS MacOS
Homebrew が使えること
JDK 1.8.0_152
Gradle 4.3.1

説明

1. マニフェストに Main-Class 属性の追加

Gradle の JAR タスクで マニフェストファイルの編集を行います。MainClass がどれか指定したいので Main-Class 属性を マニフェストファイルに追加されるようにします。

以下のタスク定義を build.gradle に追加します。

jar {
manifest {
attributes 'Main-Class': "HelloKotlinKt"
}
}

上記の定義でマニフェストに MainClass が HelloKotlinKt として登録されます。

2. マニフェストの確認

ビルド実施して生成された JAR に含まれているマニフェストを確認してみます。

$ cat META-INF/MANIFEST.MF
Manifest-Version: 1.0
Main-Class: HelloKotlinKt

3. Main-Class 属性追加後の実行

実行してみます。

$ java -jar build/libs/sample-1.0-SNAPSHOT.jar
Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics
        at HelloKotlinKt.main(HelloKotlin.kt)
Caused by: java.lang.ClassNotFoundException: kotlin.jvm.internal.Intrinsics
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        ... 1 more

Caused by: java.lang.ClassNotFoundException: kotlin.jvm.internal.Intrinsics と、Kotlin のライブラリが見つからなくてエラーが発生しています。

JAR ファイルを中身を確認してみます。

$ jar tvf build/libs/sample-1.0-SNAPSHOT.jar
     0 Tue Dec 12 14:02:26 JST 2017 META-INF/
    52 Tue Dec 12 14:02:26 JST 2017 META-INF/MANIFEST.MF
    35 Tue Dec 12 14:02:26 JST 2017 META-INF/sample.kotlin_module
  1005 Tue Dec 12 14:02:26 JST 2017 HelloKotlinKt.class

JAR の中に Kotlin のライブラリが含まれていない事が分かります。

4. 依存ライブラリの追加

build.gradle の中で Kotlin のライブラリは次のように定義しています。

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
}

つまり、依存関係でコンパイル・スコープとして定義しています。
このコンパイル時に利用していたライブラリを同一JARに含め、FarJAR として作成を行うように定義を行います。

JAR タスクに以下を追加します。

from {
    configurations.compile.collect {
        it.isDirectory() ? it : zipTree(it)
    }
}

5. 依存ライブラリ追加後のJARのビルドと中身の確認

JAR ファイルの中身を閲覧してみると、Kotlin のライブラリが含められている事が確認できます。

$ jar -tvf build/libs/sample-1.0-SNAPSHOT.jar
     0 Tue Dec 12 14:21:12 JST 2017 META-INF/
    52 Tue Dec 12 14:21:12 JST 2017 META-INF/MANIFEST.MF
    35 Tue Dec 12 14:21:10 JST 2017 META-INF/sample.kotlin_module
  1005 Tue Dec 12 14:21:10 JST 2017 HelloKotlinKt.class
   197 Mon Nov 27 19:00:42 JST 2017 META-INF/kotlin-stdlib-jdk8.kotlin_module
     0 Mon Nov 27 19:00:42 JST 2017 kotlin/
     0 Mon Nov 27 19:00:42 JST 2017 kotlin/collections/
     0 Mon Nov 27 19:00:42 JST 2017 kotlin/collections/jdk8/
  2132 Mon Nov 27 19:00:42 JST 2017 kotlin/collections/jdk8/CollectionsJDK8Kt.class
     0 Mon Nov 27 19:00:42 JST 2017 kotlin/internal/
     0 Mon Nov 27 19:00:42 JST 2017 kotlin/internal/jdk8/
  2218 Mon Nov 27 19:00:42 JST 2017 kotlin/internal/jdk8/JDK8PlatformImplementations.class
     0 Mon Nov 27 19:00:42 JST 2017 kotlin/streams/
     0 Mon Nov 27 19:00:42 JST 2017 kotlin/streams/jdk8/
  1745 Mon Nov 27 19:00:42 JST 2017 kotlin/streams/jdk8/StreamsKt$asSequence$$inlined$Sequence$1.class
  1856 Mon Nov 27 19:00:42 JST 2017 kotlin/streams/jdk8/StreamsKt$asSequence$$inlined$Sequence$2.class
  1855 Mon Nov 27 19:00:42 JST 2017 kotlin/streams/jdk8/StreamsKt$asSequence$$inlined$Sequence$3.class
  1869 Mon Nov 27 19:00:42 JST 2017 kotlin/streams/jdk8/StreamsKt$asSequence$$inlined$Sequence$4.class
  1417 Mon Nov 27 19:00:42 JST 2017 kotlin/streams/jdk8/StreamsKt$asStream$1.class
  5018 Mon Nov 27 19:00:42 JST 2017 kotlin/streams/jdk8/StreamsKt.class
     0 Mon Nov 27 19:00:42 JST 2017 kotlin/text/
     0 Mon Nov 27 19:00:42 JST 2017 kotlin/text/jdk8/
  1580 Mon Nov 27 19:00:42 JST 2017 kotlin/text/jdk8/RegexExtensionsJDK8Kt.class
   246 Mon Nov 27 18:49:30 JST 2017 META-INF/kotlin-runtime.kotlin_module
  2190 Mon Nov 27 18:50:20 JST 2017 META-INF/kotlin-stdlib.kotlin_module
   743 Mon Nov 27 18:49:30 JST 2017 kotlin/ArrayIntrinsicsKt.class
  1322 Mon Nov 27 18:49:30 JST 2017 kotlin/Deprecated.class
  1258 Mon Nov 27 18:49:30 JST 2017 kotlin/DeprecationLevel.class
   965 Mon Nov 27 18:49:30 JST 2017 kotlin/DslMarker.class
   343 Mon Nov 27 18:50:08 JST 2017 kotlin/ExceptionsKt.class
  2595 Mon Nov 27 18:50:08 JST 2017 kotlin/ExceptionsKt__ExceptionsKt.class
   739 Mon Nov 27 18:49:30 JST 2017 kotlin/ExtensionFunctionType.class
   414 Mon Nov 27 18:49:30 JST 2017 kotlin/Function.class
  1437 Mon Nov 27 18:50:20 JST 2017 kotlin/InitializedLazyImpl.class
   476 Mon Nov 27 18:49:32 JST 2017 kotlin/KotlinNullPointerException.class
   912 Mon Nov 27 18:50:20 JST 2017 kotlin/KotlinVersion$Companion.class
  4039 Mon Nov 27 18:50:20 JST 2017 kotlin/KotlinVersion.class
 :
 :

6. 依存ライブラリ追加後の実行

最後に、生成された JAR ファイルを実行してみます。

$ java -jar build/libs/sample-1.0-SNAPSHOT.jar
Hello, Kotlin!

エラーなく出力することができました。

まとめ

Gradle で生成する JAR ファイルをどこでも実行できるように実行可能形式のFatJAR (UberJAR) にしておくと便利ですね。