Go を使うと動的ライブラリに依存しない(シングルバイナリな)実行バイナリを作ることができます。 libc の代わりに musl を使うようにセットアップした OCaml コンパイラを使うと、いくつかハマりポイントはありながらも、OCaml でも同じことができることが分かったので、 HTTP サーバをお題としてやってみます。
なお Eio で直接 HTTP サーバを立てるのは若干面倒なので、拙作のライブラリである Yume を使います。Yume は Dream にインスパイアされたライブラリで、 Eio を使った HTTP サーバや HTTP(S) クライアントを簡単に立ち上げられます。以前書いた記事も参考にしてください。
検証環境は Ubuntu 24.04 LTS で、OCaml コンパイラのバージョンは 5.2.0 です。
#OCaml コンパイラを用意する
まず opam を使って OCaml コンパイラを用意します。
opam switch create
に ocaml-option-musl
と ocaml-option-static
を渡すと、シングルバイナリを作ることができる OCaml コンパイラの環境を作ることができます:
opam switch create . --no-install ocaml-option-musl ocaml-option-static
#HTTP サーバのコードを書く
適当に dune init project
してプロジェクトを作ります。その上で、以下のようなコードを書きます:
(* bin/main.ml *)
let handler =
let open Yume.Server in
Router.(use [ get "/" (fun _ _ -> respond "hello") ]) default_handler
let () =
Eio_posix.run @@ fun env ->
Eio.Switch.run @@ fun sw ->
let listen =
Eio.Net.getaddrinfo_stream ~service:"38000" env#net "localhost" |> List.hd
in
Yume.Server.start_server env ~sw ~listen handler @@ fun _socket -> ()
#opam ファイルを書く
opam ファイルは以下のような感じにしておきます:
# *.opam
# ...
depends: [
"dune" {>= "3.16"}
"eio"
"eio_posix"
"ocaml" {>= "5.0"}
"yume"
]
# ...
pin-depends:[
[ "multipart_form-eio.0.6.0" "git+https://github.com/ushitora-anqou/multipart_form.git#1bca726ecea0cb4cf253e65ae02d348015a1ef06" ]
[ "yume.0.1.10" "git+https://github.com/ushitora-anqou/yume.git#0.1.10" ]
]
ポイントは eio_main
に依存しないことです。
eio_main
に依存すると、Eio の Linux バックエンド(eio_linux
)が有効化され io_uring を使おうとしますが、musl 経由では Linux のヘッダファイルが見えないのでビルドに失敗します。代わりに Posix バックエンド(eio_posix
)を使うようにしておきます。同様の理由で、yume の 0.1.9 までは依存先に(誤って)eio_main
を含めていたので使えません。
0.1.10 以降を使ってください。また yume が依存する multipart_form-eio は依存先に eio_main
が含まれているので、適当にフォークして依存を外したものを使います[1]。
#zarith をインストールする
この状況で opam install . --deps-only
すると(主に yume から)必要なライブラリがインストールされますが、途中で zarith のインストールに失敗します。
zarith は gmp を要求するのですが、apt で入る gmp は musl から使えないようです。仕方がないので自前でビルドし、できたディレクトリを指定して zarith のインストールをやり直すとうまく行きます(参考):
$ cd gmp-6.3.0
$ CC=musl-gcc ./configure --prefix /tmp/gmp-prefix
$ make
$ make install
$ cd /path/to/original/code
$ CPPFLAGS=-I/tmp/gmp-prefix/include LDFLAGS=-L/tmp/gmp-prefix/lib opam install zarith
zarith のインストール後に再び opam install . --deps-only
すると、全ての依存ライブラリがインストールされます。
#ビルドする
bin/dune
ファイルで static なバイナリを作るように (flags (:standard -cclib -static))
を指定します(参考):
(executable
...
(name main)
(libraries eio eio_posix yume)
(flags (:standard -cclib -static))
...
)
そのうえで dune build
を打つと、動的ライブラリに依存しない実行バイナリを作ることができます:
$ dune build
$ ldd _build/default/bin/main.exe
not a dynamic executable