Recently I wanted to create completely static build of audioserve – so it can be easily moved and run on any modern 64bit linux without any other actions. Rust provides some guidelines for static building, but it still took me some time to make it work, mainly due to dependencies on other C/C++ libraries (libssl, libavformat …). So here I my experiences:
How rust compiler links by default
- Pure Rust dependencies are linked statically into final executable
- Rust executable is dependent of few very core linux libraries (libc, libdl, ….), which are linked dynamically
- If a Rust crate is dependent on other C/C++ libraries (called native in Rust lingo), they are linked dynamically (unless crate already has linked it statically to it’s code object as part of it’s build process)
This means that normally you’ll end up with dynamically linked executable, which depends on few dynamic libraries, if you want completely static binary you need to put an extra effort into building.
How to link statically
In order to create completely static executable you must use special target platform as explained here and also here – in short we must build against standard library build with musl c library and not gnu c library. The target platfom x86_64-unknown-linux-musl
provides this. So far it looks fairly easy, but complications come, if you try to link other native (C/C++) libraries – they have to be also build against musl libc – otherwise you’ll see some strange undefined symbols during the linking (and I guess this is the better case, really not sure what will happen if two different libc libraries are arbitrary mixed).
If you will use above mention target on your usual linux (debian, ubuntu, centos etc.), where everything is based on gnu libc, it means that you’ll have to install additions to gcc (to compile against musl libc) and then recompile needed libraries from source ( which can be pretty demanding for complex libraries) – see this approach in this Dockerfile.
Luckily there is an easier approach – alpine linux – where everything is already build against musl libc. Rust on this platform has default target x86_64-alpine-linux-musl
, which is similar to already mentioned x86_64-unknown-linux-musl
with one different – it does not link libc and related core libraries statically, this has to be enabled explicitly with "-C target-feature=+crt-static"
flag to rustc.
Knowing this we can create a Dockerfile, that will do the static build for us:
FROM alpine:edge AS build MAINTAINER Ivan <ivan@zderadicka.eu> RUN apk update &&\ apk add git build-base openssl-dev \ rust cargo &&\ mkdir /src WORKDIR /src ENV RUSTFLAGS="-C target-feature=+crt-static" CMD cargo build --target x86_64-alpine-linux-musl --release
Where openssl-dev
are our native dependencies (you can add whatever is needed). If you run this (first docker build, then docker run with mapping local source directory to /src volume) , you’ll find (compilation result is in target/x86_64-alpine-linux-musl/release
) that resulting binary is still not static. If you remember, what was noted above, other dependent native libraries are linked dynamically unless specified otherwise. Easiest way to change this is to create build.rs in root of your project, which will hint compiler about static linking:
fn main() { #[cfg(feature="static")] { println!("cargo:rustc-link-lib=static=ssl"); println!("cargo:rustc-link-lib=static=crypto"); } }
As you can see here static linking is hidden behind feature static, so I can easily also build dynamically linked version of binary if required. Above mentioned cargo directives map then to -l flags for compiler.
Above described approach assumes that statically linkable libraries (.a) are in the dev package. This is true for many dev packages, but not for all – for ffmpeg libraries (libavformat …) I had to build them from source to have statically linkable objects. If libraries are in non standard place, then compiler has to be also advised, where to find them ( -L compiler flags), which can be easily done by adding
println!("cargo:rustc-link-search=native=some/directory");
to the build.rs file.