Scala のソースコードをコンパイルして実行可能でコンパクトな Jar ファイルを作る Ant ビルドファイル

タイトル長すぎですね^^;

 

端的に言うと、Scala のソースからコマンド一発で Executable-Jar を作りたい、と、そういうことです。

 

最近、Scala を始めた(再開した)のですが、Jar ファイル作ろうとして結構つまづいたのでメモ。

いきさつ

僕は普段、Scala IDE (Scala プラグイン) を入れた Eclipse でコード書いてます。

 

Java であれば、Eclipse から直接 Executable-Jar を吐き出せるのですが、残念ながら、現在のところ Scala からは無理なようでした。

 

ググってみると、一度 Java の main メソッドを噛ましてやれば出来るよ、とか書いてる人もいたのですが、試してみても一筋縄ではいかないし、そもそも Java のコード書かないといけないなんてなんだかなあ、という感じ。

 

というわけで自分で Ant ビルドファイル書いた方が早いという結論に至りました。

クラスファイル(群)から Scala 仕様の実行可能 Jar ファイルを作る

ひとまず、クラスファイルはすでにあるという前提で、実行可能 Jar ファイルを作ってみます。

 

ここで注意しないといけないのは、Scala のコードから作られたクラスファイルの実行には、Scala のライブラリが必要になる、ということです。でも、普通のマシンには Scala のライブラリなんて入ってませんよね(もっと Scala が普及して、当たり前に入っているようになるといいですね ;-)

 

なので、今回は Scala のライブラリも Jar ファイルに同梱してあげます。

 

それを実現する Ant ビルドファイルがこちら:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project default="create-jar" basedir=".">

<!-- Target Program Name -->
<property name="target" value="Spiral" />

<!-- Binaries Directory -->
<property name="project-home" value="." />
<property name="bin.dir" value="${project-home}/bin" />

<!-- Libraries -->
<property name="scala-home" value="/home/ubuntu/opt/scala" />

<!-- Aliases -->
<property name="target.jar" value="${target}.jar" />
<property name="scala.lib" value="${scala-home}/lib/scala-library.jar" />

<target name="create-jar">
<jar destfile="${target.jar}" filesetmanifest="mergewithoutmain">
<manifest>
<attribute name="Main-Class" value="${target}" />
<attribute name="Class-Path" value="." />
</manifest>
<fileset dir="${bin.dir}" />
<zipfileset src="${scala.lib}"
excludes="META-INF/*.SF,library.properties" />
</jar>
</target>
</project>

これを build.xml というファイル名でプロジェクトのトップディレクトリに置いておけば、Ant コマンド一発で実行可能 Jar ファイルを作ることができるというわけです。

 

# 使用する場合には、target(プログラム名)、bin.dir(クラスファイルのあるディレクトリ)、scala-home(Scala のインストール先) あたりをご自身の環境に合わせて書き換えて下さい。

Jar のファイルサイズの問題

これで実行可能な Jar ファイルが出来たわけなんですが、問題はそのファイルサイズです。

 

(僕の環境では)高々 2KB くらいのソースコードから出来た Jar ファイルがなんと 6MB オーバー。まあ、Scala ライブラリを丸ごと突っこんだので当然の結果なのですが。

 

なんとかならないかな〜と思って調べてみたら、やっぱりなんとかなりました。

 

ProGuard という、クラスファイル群の中から本当に必要なものだけをぶっこ抜いてくれるツールがあったのでした(ちなみに、ProGurad 自体は他にも色々な機能があります)

ProGuard で Jar をスリムに

ということで、Scala から作った Jar ファイルを ProGuard でスリム化する方法を調べてみたのですが、出てくるのはAndroid の事例ばかりで(この記事とか)、普通の Scala アプリの例がなかなか出てきませんでした。

 

で、いろいろ試行錯誤しながら辿り着いた答えがこちら:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project default="create-stripped-jar" basedir=".">

<!-- Target Program Name -->
<property name="target" value="Spiral" />

<!-- Binaries Directory -->
<property name="project-home" value="." />
<property name="bin.dir" value="${project-home}/bin" />

<!-- Libraries -->
<property name="scala-home" value="/home/ubuntu/opt/scala" />
<property name="proguard-home" value="/home/ubuntu/opt/proguard" />
<property name="java-home" value="/usr/lib/jvm/java-6-openjdk/jre" />

<!-- Aliases -->
<property name="target.jar" value="${target}.jar" />
<property name="target.stripped" value="${target}.strippped.jar" />
<property name="scala.lib" value="${scala-home}/lib/scala-library.jar" />
<property name="proguard" value="${proguard-home}/lib/proguard.jar" />
<property name="java.runtime" value="${java-home}/lib/rt.jar" />

<target name="create-jar">
<jar destfile="${target.jar}" filesetmanifest="mergewithoutmain">
<manifest>
<attribute name="Main-Class" value="${target}" />
<attribute name="Class-Path" value="." />
</manifest>
<fileset dir="${bin.dir}" />
<zipfileset src="${scala.lib}"
excludes="META-INF/*.SF,library.properties" />
</jar>
</target>

<target name="create-stripped-jar" depends="create-jar">
<taskdef resource="proguard/ant/task.properties" classpath="${proguard}" />
<proguard>
<injar path="${target.jar}" />
<outjar path="${target.stripped}" />
-libraryjars "${java.runtime}"
-dontwarn
-dontoptimize
-dontobfuscate
-keepclasseswithmembers public class * {
public static void main(java.lang.String[]);
}
</proguard>
</target>
</project>

これで Ant を実行してできたスリム化後の Jar のファイルサイズは 500KB ほど(スリム化前の 1/10 以下)。まあ、こんなもんでしょうか。

 

# 同じく、proguard-home(ProGuard のインストール先)、java-home(JRE のインストール先)などを環境に合わせて使って下さい。

# ProGuard がいっぱい Warning を出しますが、あまり気にしない。。。

# まだ単純なプログラムでしか試してないので、やってみてダメでも怒らないで下さい^^;

Scala ソースコードのコンパイルから Ant でビルドする

ここまで来たら、ソースのコンパイルから Ant 一発でできるようにしたい、と思うのがプログラマの常というものです(?)

 

Scala のコンパイルは Scala のサイトのこのあたりを参考にすればすんなりできました。

 

という訳で、揺りかごから墓場まで Scala の面倒を見る Ant ビルドファイルを最後に載せておきます。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project default="create-stripped-jar" basedir=".">

<!-- Target Program Name -->
<property name="target" value="Spiral" />

<!-- Sources and Binaries Directory -->
<property name="project-home" value="." />
<property name="src.dir" value="${project-home}/src" />
<property name="bin.dir" value="${project-home}/bin" />

<!-- Libraries -->
<property name="scala-home" value="/home/ubuntu/opt/scala" />
<property name="proguard-home" value="/home/ubuntu/opt/proguard" />
<property name="java-home" value="/usr/lib/jvm/java-6-openjdk/jre" />

<!-- Aliases -->
<property name="target.jar" value="${target}.jar" />
<property name="target.stripped" value="${target}.strippped.jar" />
<property name="scala.c" value="${scala-home}/lib/scala-compiler.jar" />
<property name="scala.lib" value="${scala-home}/lib/scala-library.jar" />
<property name="proguard" value="${proguard-home}/lib/proguard.jar" />
<property name="java.runtime" value="${java-home}/lib/rt.jar" />

<target name="prepare">
<mkdir dir="${bin.dir}" />
</target>

<target name="compile" depends="prepare">
<taskdef resource="scala/tools/ant/antlib.xml"
classpath="${scala.c}:${scala.lib}" />
<scalac srcdir="${src.dir}" destdir="${bin.dir}"
classpath="${bin.dir}:${scala.lib}">
<include name="**/*.scala" />
</scalac>
</target>

<target name="create-jar" depends="compile">
<jar destfile="${target.jar}" filesetmanifest="mergewithoutmain">
<manifest>
<attribute name="Main-Class" value="${target}" />
<attribute name="Class-Path" value="." />
</manifest>
<fileset dir="${bin.dir}" />
<zipfileset src="${scala.lib}"
excludes="META-INF/*.SF,library.properties" />
</jar>
</target>

<target name="create-stripped-jar" depends="create-jar">
<taskdef resource="proguard/ant/task.properties" classpath="${proguard}" />
<proguard>
<injar path="${target.jar}" />
<outjar path="${target.stripped}" />
-libraryjars "${java.runtime}"
-dontwarn
-dontoptimize
-dontobfuscate
-keepclasseswithmembers public class * {
public static void main(java.lang.String[]);
}
</proguard>
</target>

<target name="clean">
<delete includeemptydirs="true" quiet="true">
<fileset file="${target.jar}" />
<fileset file="${target.stripped}" />
<fileset dir="${bin.dir}" />
</delete>
</target>
</project>

 

コメントをお書きください

コメント: 1
  • #1

    funatti (火曜日, 28 8月 2012 21:17)

    非常に参考になりました^^

    実際使ってみたのですが、このままだとJavaの混合ソースがコンパイルできなかったので、
    http://www.codecommit.com/blog/scala/joint-compilation-of-scala-and-java-sources
    を参考にjavacの部分を追加して使わせていただきました。ありがとうございます。