Static Linking
Static linking can be enabled using the --static
compiler flag. See the usage instructions in the language reference.
When --static
is given, linking static libraries is enabled, but it’s not exclusive. The produced binary won’t be fully static linked if the dynamic version of a library is higher in the compiler’s library lookup chain than the static variant (or if the static library is entirely missing). In order to build a static binary you need to make sure that static versions of the linked libraries are available and the compiler can find them.
The compiler uses the CRYSTAL_LIBRARY_PATH
environment variable as a first lookup destination for static and dynamic libraries that are to be linked. This can be used to provide static versions of libraries that are also available as dynamic libraries.
Not all libraries work well with being statically linked, so there may be some issues. openssl
for example is known for complications, as well as glibc
(see ).
Some package managers provide specific packages for static libraries, where foo
provides the dynamic library and foo-static
for example provides the static library. Sometimes static libraries are also included in development packages.
A fully statically linked program has no dynamic library dependencies at all. This is useful for delivering portable, pre-compiled binaries. Prominent examples of fully statically linked Crystal programs are the crystal
and shards
binaries from the official distribution packages.
In order to link a program fully statically, all dependencies need to be available as static libraries at compiler time. This can be tricky sometimes, especially with common libc
libraries.
glibc
is the most common libc
implementation on Linux systems. Unfortunately, it doesn’t play nicely with static linking and it’s highly discouraged.
Instead, static linking against is the recommended option on Linux. Since it’s statically linked, a binary linked against musl-libc
will also run on a glibc system. That’s the entire point of it.
musl-libc
is a clean, efficient libc
implementation with excellent static linking support.
The recommended way to build a statically linked Crystal program is Alpine Linux, a minimal Linux distribution based on musl-libc
.
Official are available on Docker Hub at crystallang/crystal. The latest release is tagged as crystallang/crystal:latest-alpine
. The Dockerfile source is available at .
With pre-installed crystal
compiler, shards
, and static libraries of all of stdlib’s dependencies these Docker images allow to easily build static Crystal binaries even from glibc
-based systems. The official Crystal compiler builds for Linux are created using these images.
Here’s an example how the Docker image can be used to build a statically linked Hello World program:
Alpine’s package manager APK is also easy to work with to install static libraries. Available packages can be found at pkgs.alpinelinux.org.
macOS
macOS doesn’t officially support fully static linking because the required system libraries are not available as static libraries.
Identifying Static Dependencies
If you want to statically link dependencies, you need to have their static libraries available. Most systems don’t install static libraries by default, so you need to install them explicitly. First you have to know which libraries your program links against.
Note
On most POSIX systems the tool ldd
shows which dynamic libraries an executable links to. The equivalent on macOS is otool -L
.
The following example shows the output of ldd
for a simple Hello World program built with Crystal 0.36.1 and LLVM 10.0 on Ubuntu 18.04 LTS (in the crystallang/crystal:0.36.1
docker image). The result varies on other systems and versions.
These libraries are the minimal dependencies of Crystal’s standard library. Even an empty program requires these libraries for setting up the Crystal runtime.
This looks like a lot, but most of these libraries are actually part of the libc distribution.
On Alpine Linux the list is much smaller because musl includes more symbols directly into a single binary. The following example shows the output of the same program built with Crystal 0.36.1 and LLVM 10.0 on Alpine Linux 3.12 (in the crystallang/crystal:0.36.1-alpine
docker image).
The individual libraries are libpcre
, libgc
and the rest is musl
(libc
). The same libraries are used in the Ubuntu example.
In order to link this program statically, we need static versions of these three libraries.
Note
The *-alpine
docker images ship with static versions of all libraries used by the standard library. If your program links no other libraries then adding the flag to the build command is all you need to link fully statically.