TL;DR: I cannot get OCaml 4.13.1 to compile static executables with native Musl nor with Alpine Linux. The only partial success I had was with
musl-tools in Debian and a
+musl switch, but
musl-tools does not include g++, which package
re2 requires to install.
We’ve had a few threads about this before, and a few blog posts are out there, but I’ve been going around in circles for two days now so it’s time to summarize where I’m at and hope that someone out there shares how they can actually compile static executables with OCaml, because I sure can’t.
One environment I have is the full native gcc/g++ suite from Musl libc locally from musl.cc, without interfering with the system’s compilers and libraries. Just use the
$LD_LIBRARY_PATH exports when you want to use Musl instead of Glibc. This does not require an OCaml
+musl switch because Musl’s gcc is native, just like in Alpine’s.
cd /tmp ; wget https://musl.cc/x86_64-linux-musl-native.tgz cd /usr/local ; tar -zxf /tmp/x86_64-linux-musl-native.tgz ln -s /usr/local/x86_64-linux-musl-native/lib/libc.so /usr/local/x86_64-linux-musl-native/bin/ldd ln -s /usr/local/x86_64-linux-musl-native/lib/libc.so /lib/ld-musl-x86_64.so.1 export PATH="/usr/local/x86_64-linux-musl-native/bin:$PATH" ; hash -r export LD_LIBRARY_PATH="/usr/local/x86_64-linux-musl-native/lib" opam switch create 4.13.1+muslnative+flambda ocaml-variants.4.13.1+options ocaml-option-flambda eval $(opam env --switch=4.13.1+muslnative+flambda)
I also have an official OCaml release under Alpine, to get a 100% bona fide native musl in case my local environment is broken.
Building and usage
# Dockerfile FROM ocaml/opam:alpine-ocaml-4.13-flambda RUN sudo apk add --no-cache m4 linux-headers RUN opam install dune WORKDIR /work/
#!/bin/bash # Get a shell inside the Alpine container. exec docker run --rm -u `id -u`:`id -g` \ -e LINES=`tput lines` -e COLUMNS=`tput cols` -it \ -v `pwd`:/work my-docker-image:latest bash
Both full Musl setups above behave absolutely identically, so they are interchangeable from this point on. In both I can compile things dynamically (no
ocamlopt_flags) and the result just depends on the dynamic loader and Musl’s libc.
file & ldd output
$ file test.exe test.exe: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, with debug_info, not stripped $ ldd test.exe linux-vdso.so.1 (0x00007fff3c38e000) libc.so => /usr/local/x86_64-linux-musl-native/lib/libc.so (0x00007f7e576eb000)
What used to work before:
(env (_ (ocamlopt_flags (:standard -ccopt -static))))
…now under a native Musl and Alpine fails to build (here shown with paths, etc. removed):
(ocamlopt.opt -w @1..3@5..28@30..39@43@46..47@49..57@61..62-40 -strict-sequence -strict-formats -short-paths -keep-locs -g -ccopt -static -shared -linkall -I lib -o lib/gxd.cmxs lib/gxd.cmxa) ld: crtbeginT.o: relocation R_X86_64_32 against hidden symbol `__TMC_END__' can not be used when making a shared object ld: crtend.o: relocation R_X86_64_32 against `.ctors' can not be used when making a shared object; recompile with -fPIC collect2: error: ld returned 1 exit status File "caml_startup", line 1: Error: Error during linking (exit code 1)
If I use
(ocamlopt_flags (:standard -fPIC -ccopt -fPIC -ccopt -pie -ccopt -static)), OCaml can’t even find its own symbols at link time like
caml_call_gc. With one combination of arguments I even got
crt1.o to fail to find
Despite having had some fatal failures in the past with a
+static switch (I think
Bin_prot in particular failed to build without dynamic library support), I tried one just in case:
# NOTE: --assume-depexts prevents opam from installing musl-tools. cd /usr/local/x86_64-linux-musl-native/bin/ ; ln -s gcc musl-gcc opam switch create --assume-depexts 4.13.1+muslnative+static+flambda ocaml-variants.4.13.1+options ocaml-option-static ocaml-option-flambda
file & ldd output
$ file test.exe test.exe: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, with debug_info, not stripped $ ldd test.exe /lib/ld-musl-x86_64.so.1 (0x7f7b85d59000) libc.so => /lib/ld-musl-x86_64.so.1 (0x7f7b85d59000)
Still dynamic by default. If I add
-ccopt -static, just like in attempt 1 I get the
R_X86_64_32 against .ctors error, and if I play with PIC/PIE I get the same subsequent linking failure.
If anyone builds static executables with OCaml under Alpine or with a native Musl (not Debian’s incomplete
musl-tools), I’d love to hear how the heck you worked around these errors. I don’t even know what else I could possibly try.