最近VirtusLab社が突如として公開したScala用の汎用CLIツールscala-cliを試してみました。
scala-cli.virtuslab.org
基本的にはScalaでスクリプティングするためのツールのようで、便利ツールや教育用途での使用が想定されているようですが、スクリプトを実行可能jarやDockerイメージとしてパッケージングしたり、Scala.jsやScala Nativeでのコンパイルにも対応しているようです。MacであればHomebrewでインストールが可能なのですが、手元の環境ではHomebrewのバージョンが古いせいかインストールできなかったのでGitHubから最新のソースを取得して自分でビルドしたものを使っています。
まずは簡単なScalaプログラム(HelloWorld.scala
)を実行してみます。
@main def hello() = println("Hello, world")
以下のようにして実行します。run
はデフォルトのサブコマンドなので省略することもできるようです。デフォルトでScala 3が使われるようですね。
$ scala-cli run HelloWorld.scala
Compiling project (Scala 3.0.2, JVM)
Compiled project (Scala 3.0.2, JVM)
Hello, world
使用するScalaのバージョン、ライブラリなどはscala-cliコマンドのオプションのほか、ソースコード中にコメントで特殊なディレクティブを記述することでも指定できます。Scala 2.12は大丈夫だったのですが、Scala 2.13を指定してみたところコンパイルが失敗してしまいました(Compilation failedとだけ表示される)。これは最新のソースからビルドしたものを使っているせいかもしれません。
import wvlet.airframe.json.JSON
@main def hello = {
val jsonValue = JSON.parse("""{"id":1, "name":"leo"}""")
println(jsonValue)
}
Ammoniteでサポートされている拡張子が*.sc
のスクリプトにも対応しています。こちらではmainメソッドを記述する必要がないほか、Ammonite形式の記述でライブラリのインポートなどが可能です。AmmoniteスクリプトはMetalsやIntelliJでもサポートされているのでスクリプトの記述時にIDEの支援機能を利用できるのは嬉しいですね。
import $ivy.`org.wvlet.airframe::airframe-json:21.10.0`
import wvlet.airframe.json.JSON
val jsonValue = JSON.parse("""{"id":1, "name":"leo"}""")
println(jsonValue)
mainメソッド付きの*.scala
ファイルと同じように実行できます。
$ scala-cli run hello.sc
パッケージング
スクリプトをパッケージングしてみます。
$ scala-cli package hello.sc
Wrote hello, run it with
./hello
$ ./hello
Hello, world!
hello
というコマンドが生成されます。このコマンドは軽量なランチャーで、必要な依存関係などは初回実行時に自動的にダウンロードするようです。実行にはJavaがインストールされている必要があります。依存関係も含めてパッケージングしたい場合は--assembly
オプションを指定するとUber JARが生成されます。
$ scala-cli package hello.sc --assembly
Wrote hello.jar, run it with
./hello.jar
$ java -jar hello.jar
Hello, world!
--js
オプションでJavaScriptを生成できます。生成されたJavaScriptはNode.jsで実行可能です。
$ scala-cli package hello.sc --js
Wrote hello.js, run it with
node ./hello.js
$ node hello.js
Hello, world!
--native
オプションでScala Nativeを使用したバイナリを生成できます。こちらはScala 2.12もしくは2.13を使用する必要があるようです。*.sc
のサポートはScala 2.13のみのようなのですが、手元で試したところではScala 2.13では依存関係の解決に失敗してビルドが通りませんでした。前述の通り、Scala 2.13では通常の実行でもコンパイルに失敗するので最新のソースではScala 2.13の扱いに問題があるのかもしれません。
$ scala-cli package HelloWorld.scala --native -S 2.12
Linking (1432 ms)
Discovered 971 classes and 8420 methods
Generating intermediate code (2108 ms)
Produced 8 files
Compiling to native code (676 ms)
Linking native code (immix gc, none lto) (148 ms)
Total (4510 ms)
Wrote HelloWorld, run it with
./HelloWorld
$ ./HelloWorld
Hello, world!
--docker
オプションでDockerイメージを生成できます。ただし、デフォルトではJava 8ベースのベースイメージが使用されるようなので、手元の環境もJava 8でコンパイルが行われるようにしておくか、パッケージング時に--docker-from
オプションでより新しいバージョンのJavaを使用可能なベースイメージを指定する必要があります。
$ scala-cli package --docker hello.sc --docker-image-repository hello-docker --docker-from openjdk:11-jre-slim
Started building docker image with your application, it would take some time
Built docker image, run it with
docker run hello-docker:latest
$ docker run hello-docker:latest
Hello, world!
今回は試していないのですが、--docker
オプションを--js
や--native
オプションと組み合わせることでJavaScriptやネイティブバイナリで動作するDockerイメージを生成することもできるようです。
この他にもREPLの起動(オプションでAmmoniteを使用することも可能)や、scalafmtを使用したコードのフォーマット、sbtやmillの形式でのプロジェクトとしてエクスポートなど様々な機能がサポートされているようです。スクリプトを実行するだけであればAmmoniteだけで良さそうな気もしますが、作成したスクリプトを配布したり、スクリプトでプロトタイピングをして必要であればsbtプロジェクトとしてエクスポートして本格的に開発する、なんていう使い方も可能かもしれません。