Devops. I've never asked for this.

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!

First, install Rust nightly. I haven’t tried whether this works on Rust stable, so let me know if you happen to try it.

$ rustup install nightly

Second, install 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 ~/.cargo/config

linker = "x86_64-linux-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