Cross-compile and link a static binary on macOS for Linux with cargo and rust
One Go feature which I’m using regularly is cross-compiling Go code to other platforms (usually from macOS to linux-amd64).
In Go, this is a built-in feature that “just works”. The following command produces a statically linked ELF binary which can simply be copied and run on a Linux machine:
$ GOARCH=amd64 GOOS=linux go build $ file api ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
Rust doesn’t have built-in support for such a feature.
Edit: Rust core-commiter Steve Klabnik pointed out, that actually what I’m showing off here is the built-in Rust support :)
I’ve been looking for a while how to achieve this using Cargo and Rust. And (spoiler-alert!) I’ve found one, which is relatively simple to set up.
Assuming you have rustup and Homebrew installed, it’s actually quite simple to setup cross-compiling on macOS!
Start with installing the required target. In our case (Linux x86_64) this is
x86_64-unknown-linux-musl. The lightweight standard library musl is able to produce statically linked libraries (contrary to the
x86_64-unknown-linux-gnu target, which uses GNU libc.
$ rustup target add x86_64-unknown-linux-musl
What’s still missing is the linker. This was always the part where I go into trouble when trying this earlier. Finally, this blog post from Graham Enos guided me to a Homebrew tap by Filippo Valsorda, which provides a complete macOS to Linux cross-compile toolchain.
$ brew install filosottile/musl-cross/musl-cross
That was the heavy-lifting. Now just tell Cargo where to find the linker. You can place the following into your projects
.cargo/config or configure it globally in your
[target.x86_64-unknown-linux-musl] linker = "x86_64-linux-musl-gcc"
Edit: As of rust-1.32.0, it might be required to add a symlink to make it work, which I came accross here
ln -s /usr/local/bin/x86_64-linux-musl-gcc /usr/local/bin/musl-gcc
That’s it! You can now cross-compile Linux binaries with Cargo!
$ cargo build --release --target x86_64-unknown-linux-musl
You can find the resulting statically linked ELF binary in the
target/x86_64-unknown-linux-musl/release directory. This file can be copied and run on any Linux x86_64 machine, just like the cross-compiled Go binary!
$ file target/x86_64-unknown-linux-musl/release/api ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, not stripped