sbt-native-imageでバイナリを生成する方法

最終更新:2020年9月9日

[AD] scalapediaでは記事作成ボランティアを募集しています

この記事では、sbt-native-image プラグインの使い方を解説します。

sbt-native-image はバイナリ生成用プラグイン

sbt-native-imageプラグインを使用すると、Scalaプログラムをネイティブ環境で実行できるようバイナリを出力することができます。

生成に必要なGraalVMを自動でインストールしてくれるので大変便利です。

sbt-native-imageを使用してネイティブイメージを生成する手順

環境構築をする

まず、現在のバージョンでは、プロジェクトで使用するJDKのバージョンと、sbt-native-imageで使用するJDKのバージョンを一致させる必要があります。
公式は「手動で環境構築する手間がない」を謳っていますが、この点には注意です。

一致していない場合、例えばJava 11でコンパイルしたコードのネイティブイメージを生成すると、実行時にGraalVMが実行できない旨のエラーを出力します。

プロジェクトを作成する

まずはsbt-native-imageプラグインを使用するサンプルプロジェクトを用意します。

テンプレートに scala/scala-seed.g8 を使います。

shell
sbt new scala/scala-seed.g8

初回の実行には時間がかかります。関連するいろいろなライブラリ等をダウンロードするためです。

途中でプロジェクト名をつけるよう尋ねられるので、ここではsbt-native-image-sampleと入力します。

sbt shell
[info] resolving Giter8 0.13.1... A minimal Scala project. name [Scala Seed Project]: sbt-native-image-sample Template applied in <current directory>/sbt-native-image-sample

sbt-native-imageを設定する

さて、sbt-native-imageを使ってネイティブイメージを出力してみましょう。

project/plugins.sbt を開いて以下の内容を追記し、sbt-native-imageプラグインを有効にします。

plugins.sbt
addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.2.0")

また、build.sbtに以下の内容を追記します。 プロジェクトでNativeImagePluginを有効にし、起動時のエントリーポイントを明らかにします。

build.sbt
lazy val proj = project ... .enablePlugins(NativeImagePlugin) .settings( ... Compile / mainClass := Some("com.my.MainClass") ... )

(任意)JVMのバージョンを切り替える

必要に応じて以下のようにsbt-native-imageで使用するJavaのバージョンを選びましょう。Java 8またはJava 11を使用することができます。
デフォルトはJava 11です。
nativeImageJvmgraalvm-java11(デフォルト)もしくはgraalvmを設定するとJVMのバージョンを切り替えることができます。

build.sbt
.settings( ... nativeImageJvm := graalvm-java11 ... )

sbt シェルで nativeImageコマンドを実行する

これで準備が整ったので、sbtシェルでnativeImageコマンドを実行します。

sbt shell
> nativeImage ... [info] Native image ready! [info] <project root>/target/native-image/sbt-native-image-sample [success] Total time: 74 s (01:14), completed 2020/09/01 1:11:11

初回の実行には時間がかかります。GraalVMのイメージなどいろいろなものをダウンロードするためです。

生成に成功すると、上のログのようにバイナリのパスが表示されます。

生成したネイティブイメージを実行する

さて、それでは生成したネイティブイメージを実行してみましょう。

実行した結果、"hello"とだけ出力されればOKです。

shell
$ /target/native-image/sbt-native-image-sample hello

爆速で実行完了します。

どれくらいなのか時間を計測してみましょう。

shell
$time /target/native-image/sbt-native-image-sample hello sbt-native-image-sample/target/native-image/native-image-sampl 0.00s user 0.00s system 72% cpu 0.007 total

実行時にJVMを起動していたらこんなものでは済まないので、間違いなくネイティブで実行できていることがわかります。

サンプルコードはこちらのリポジトリで公開しています。

sbt-native-packager との違いは?

既存の類似のプラグインとしてsbt-native-packagerがあります。
今後変わる可能性もありますが、現段階でsbt-native-imageと比較してみましょう。

sbt-native-image はGraalVMのネイティブイメージを自動的にインストールする

sbt-native-image はデフォルトで GraalVMのネイティブイメージを自動的にインストールします。

sbt を起動する前に docker イメージを設定したり、正しい GraalVM JDK を手動でインストールしたりする必要はありません。

ただし、上述のように使用しているGraalVM JDKのバージョンが適切に設定されているかどうかに注意してください。

sbt-native-image はScala 2.12.12+ あるいは 2.13.3+ で正常に動作する

sbt-native-image は、Scala 2.12.12+ および 2.13.3+ で正常に動作することが確認されています。

これに対して、sbt-native-packagerは正常に動作しないことがあります。
不具合を回避するためには scala/bug#11634 のような設定を追加で行う必要があります。

sbt-native-image では生成の進捗が表示される

sbt-native-image は、バイナリを生成している間にネイティブイメージからの進捗を出力します。

sbt-native-packager は、何らかの理由でプロセスが完了しない限りがネイティブイメージからの出力を表示することはありません。

sbt-native-image は Docker をサポートしない

sbt-native-image に Docker サポートを追加する予定はありません。

sbt-native-packager は Docker をサポートしており、リンク環境をより細かく制御したい場合に便利です。

Play Frameworkには未対応

sbt-native-imageプラグインはPlay Frameworkには対応していません。
まだ若いプラグインなので当然ではありますね。

Scalapediaでは実際にPlay Frameworkのネイティブイメージを生成してみました。
起動してトップページを開くことまではできるものの、画像やJavaScriptが読み込まれませんでした。

まだ期待通りに表示できないレベルですので、これ以上のテストはしませんでした。
とはいえ画像やJavaScriptの無関係なAPIサーバとしての用途ではある程度挙動する可能性はあるので、テストしてみる価値はあるかもしれません。

nativeImageコマンドを実行する際には、build.sbtにてアプリケーションのエントリーポイントとしてplay.core.server.DevServerStartまたはplay.core.server.ProdServerStartを指定すると、ネイティブイメージを生成することができます。

サイト内検索