[Haskell-cafe] GHC + musl = easier static linking

Marios Titas redneb8888 at gmail.com
Wed May 27 17:40:53 UTC 2015


TL;DR: I made some GHC binaries for linux that rely on musl instead of
glibc for easier static linking. Scroll down for the link.

Dear *,

As most of you know, musl is a linux only C standard library that is
optimized for static linking. Statically linked binaries can simplify
distribution and deployment. If you have tried to statically link a
program with glibc, you will probably know that this does not always
work as expected: for example, in many cases the resulting binary will
require to be executed in a system that has the exact same version of
glibc as the one that was used to compiled it. In essence, static
binaries linked with glibc are oftentimes *less* portable than dynamic
ones.

I decided to see if I could use GHC with musl. The problem was that
while I could find some references online, I could not find a
precompiled version of GHC that uses musl. So I decided to try and a
build one myself. I was able to successfully bootstrap GHC under musl
and I am posting it today in case someone else finds it usefull:

    https://drive.google.com/folderview?id=0B0zktggRQYRkbGJkbmU0UFFSYUE#list

I posting this on google drive, I have no better place to host this,
hopefully google will not disable my account :-)
This is a fully bootstrapped (i.e. stage 2) GHC and *not* a cross
compiler. So this will *not* work in a typical glibc based linux
distribution. You need a complete musl based environment to use it
(see bellow for that). Also, the binaries produced by this GHC will
all depend on musl and not work on most glibc based distros. On the
other hand, statically compiled binaries should be very portable and
will not depend on any particular C standard library. I have done some
minimal testing and it seems to work rather well; everything I tried
to compile from hackage just worked. Additionally it can compile GHC
itself which is always a good test. The size of the resulting static
binaries is acceptable. In my 64 bit system, a simple hello world has
the following sizes (stripped): 800K glibc dynanic, 1648K glibc
static, and 1012K musl static.

Why not use a cross-compiler? I have found that in practice it is
easier to have a separete environment for producing static binaries. A
cross-compiler requires that you also cross-compile all the C
libraries that you use. Additionally, cabal has still problems (e.g.
[1]) with cross-compiling and will not work out of the box.

Note that musl is not 100% compatible with glibc, so some things might
work slightly differently.

If you do not like using a GHC precompiled by someone else, you can
use my binaries to compile GHC under musl yourself; it would be easier
than cross-compiling GHC from scratch.

So how do you use this? You need a complete musl based environment.
There a few musl based distros [2], albeit most of them are geared
towards embedded applications. I personally use the experimental musl
based gentoo image [3] which feels more like a regular distro. You can
use it with chroot, lxc, or systemd-nspawn. For the sake of
completeness, I have included at the end of this message some quick
and dirty instructions on how to do this.

[1] https://github.com/haskell/cabal/issues/1493
[2] http://wiki.musl-libc.org/wiki/Projects_using_musl
[3] http://distfiles.gentoo.org/experimental/amd64/musl/

8<-------------------------------------

# gentoo stage3 image instructions

CHROOT_PATH=/var/tmp/gentoo-musl
cd /var/tmp
wget http://distfiles.gentoo.org/experimental/amd64/musl/stage3-amd64-musl-vanilla-20150405.tar.bz2
mkdir "$CHROOT_PATH"
tar xjf stage3-amd64-musl-vanilla-20150405.tar.bz2 -C "$CHROOT_PATH"
for x in dev proc sys; do mount --bind /$x "$CHROOT_PATH/$x"; done
cp -L /etc/resolv.conf "$CHROOT_PATH/etc"
chroot "$CHROOT_PATH" /bin/bash -l
PS1="(chroot) $PS1"
# fetch the gentoo package db for the first time (slow):
emerge-webrsync
# enable static libraries (*.a), which are disabled by default:
sed -ie 's/USE="/USE="static-libs /' /etc/portage/make.conf
# reinstall/recompile gmp so that we have its static libs:
emerge --oneshot dev-libs/gmp
# install ghc:
tar xJf /path/to/ghc-7.10.1-x86_64-unknown-linux-musl.tar.xz -C /tmp
cd /tmp/ghc-7.10.1; ./configure --prefix=/opt/ghc-7.10.1; make install
# compile a hello world:
cd /tmp
echo 'main = putStrLn "Hello world"' > test.hs
/opt/ghc-7.10.1/bin/ghc --make -O -optl-static test.hs
./test
file ./test

# Of course, using chroot this way is insecure, don't use to install
packages from hackage.


More information about the Haskell-Cafe mailing list