Scala CLIのおもしろい使い方を見付けたのでメモ。これまでと比べてはるかにScalaのちょっとしたスクリプトのシングルバイナリを生成できるようになるので、本当にGolangみたいに簡単にちょっとしたツールを作れるようになった。
scala-cli package
Scala CLIにはpackage
というサブコマンドがあり、これを利用するとScala ScriptをそのままUber JAR(直接実行できるJARファイル)にビルドできるのでなかなか便利だ。
しかもなんと--native
オプションを使うことでバックエンドがScala Nativeに切り替わり、ネイティブバイナリを生成できるようになる。
//> using scala 3.3.1 //> using dep "com.lihaoyi::os-lib::0.9.3" // ↑こんな感じでライブラリも使える(コロン2つにする点に注意) println("Hello, World!")
% scala-cli --power package --native -o helloworld helloworld.scala.sc ... % ./helloworld Hello, World!
Scala CLIなんぞやという話はこちら。
ここからいくつかの要素を組み合わせる。
Scala Nativeはシングルバイナリを生成できる
シングルバイナリとはここでは「他の動的ライブラリに依存していない実行可能バイナリ」くらいの意味。
Scala NativeはLLVMを利用していて、Scala CLIからいくらかコンパイラオプションやリンカオプションをいじれるようになっている。
Scala CLIのpackageのオプションで--native-linking='-static'
というオプションを追加すると、出力されるバイナリは静的リンクされてシングルバイナリにできる。これはユーティリティを配布したいときに非常に便利だ。
% scala-cli --power package --native --native-linking='-static' -o helloworld helloworld.scala.sc % ldd helloworld 動的実行ファイルではありません
とはいえ、Scala Nativeは実行環境にLLVMまわりのツールキットを必要とするので、ちょっと面倒だ。
Dockerイメージを利用すると便利
VirtusLab(Scala CLIの開発元)が出している公式のDockerイメージを使うとより面白いことができる。コンテナにはLLVMまわりのツールが最初から搭載されているので、本当に簡単にシングルバイナリのビルドができる。
% docker run --rm -it -v .:/app --workdir /app virtuslab/scala-cli --power package --native --native-linking='-static' -o helloworld helloworld.scala.sc % ldd helloworld 動的実行ファイルではありません
用途
ここでは手許でバイナリをビルドしたが、Dockerfileでマルチステージビルドの前段としてvirtuslab/scala-cli
を使えば、ペライチのScala Scriptからバイナリを生成したイメージをdistroless
ベースで作成したりできる。ツールを配布したいときに便利だ。
クロスコンパイルはまだできなそう
clangにオプションを渡せるのでクロスコンパイルもイケるかと思ったけれど、うまくいかなかった。
% docker run --rm -it -v .:/app --workdir /app virtuslab/scala-cli --power package --native --native-compile='--target=aarch64-linux-gnu' --native-linking='-static' helloworld.scala.sc Starting compilation server Compiling project (Scala 3.3.1, Scala Native 0.4.17) Compiled project (Scala 3.3.1, Scala Native 0.4.17) [info] Linking (1156 ms) [info] Checking intermediate code (quick) (69 ms) [info] Discovered 692 classes and 3825 methods [info] Optimizing (debug mode) (955 ms) [info] Generating intermediate code (623 ms) [info] Produced 12 files [error] In file included from /root/.cache/scalacli/virtual-projects/e1/project-51f8174f/.scala-build/app_1db1351409/native/native-code-javalib_native0.4_3-0.4.17-0/scala-native/netinet/in6.c:2: [error] In file included from /usr/include/netinet/in.h:21: [error] In file included from /usr/include/features.h:392: [error] /usr/include/features-time64.h:20:10: fatal error: 'bits/wordsize.h' file not found [error] #include <bits/wordsize.h> [error] ^~~~~~~~~~~~~~~~~ [error] 1 error generated.
まとめ
Scala CLIのDockerイメージを使ってシングルバイナリを作るテクについて紹介しました。ちょっとしたツール界といえば今はGoかRustの独擅場みたいになっていますが、Scalaで便利ツールを作るハードルはかなり低くなっています。
過去の記事
Scala CLIまわりだと過去にこういう記事も書いておりました。