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.
1. Full Musl, local
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 $PATH
and $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.
Building
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)
2. Full Musl, Alpine
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
Dynamic linking with Musl
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)
Static linking with Musl
Attempt 1
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 _start_c
from crt1.o
to fail to find main
!
Attempt 2
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.
I’m Stumped
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.