静态链接 Haskell 程序

Hacks May 09, 2020

ghc 默认会静态链接 Haskell 依赖,动态链接其他语言的依赖(比如 C 库)。这样在部署的时候就会非常非常麻烦。编译出来的二进制程序可能在一个发型版上能工作,但是在其他发行版就挂了(因为缺动态链接库)。库的版本也是个头疼的问题。

一个很显然的 use case 就是 AWS Lambda,你对机器没有控制权,自然也没有权限安装这些库。这时候静态链接就很有必要了。

网络上有甚多关于 crtopal-static/fPIC 的 hack。他们都是过时的,不需要。

我们使用 Stack build system。它太好用了,不熟悉的可以想象成 Haskell 的 pipenv。直接用 cabal 也是一样的,不过我更喜欢 Stack。

举个例子。我有一个项目使用了 text-icu 这个依赖。它是一个 icu (C library) 到 Haskell 的 binding。如果在 Debian 上直接编译然后到 Ubuntu 上使用,就会出现找不到动态链接库的错误:

$ ./MainParser: error while loading shared libraries: libicuuc.so.57: cannot open shared object file: No such file or directory

但是我其实是有这个库安装的,不过版本太新了

$ ls /usr/lib/x86_64-linux-gnu/ | grep libicuuc
libicuuc.a
libicuuc.so
libicuuc.so.60
libicuuc.so.60.2

ldd 一下会发现更多的依赖:

$ ldd MainParser
	linux-vdso.so.1 (0x00007fff1d19f000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f92fac94000)
	libicuuc.so.57 => not found
	libicui18n.so.57 => not found
	libicudata.so.57 => not found
	libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007f92faa6a000)
	librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f92fa862000)
	libutil.so.1 => /lib/x86_64-linux-gnu/libutil.so.1 (0x00007f92fa65f000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f92fa45b000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f92fa23c000)
	libgmp.so.10 => /usr/lib/x86_64-linux-gnu/libgmp.so.10 (0x00007f92f9fbb000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f92f9bca000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f92fb032000)

部署实在是很痛苦。我们来静态编译来解决这个问题。

package.yamlexecutables 里加入如下几行:

ghc-options: -Wall -O2 -static -threaded
cc-options: -static
ld-options: -static -pthread

在最外层声明用到的库:

extra-libraries: icuuc icudata icui18n stdc++

stack build 编译,会发现如下警告:

     warning: Using 'dlopen' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking

不用管他,我们几乎不可能会遇到缺这种情况。我用 alpine 试了一下,musl 是可以正常运行的,看起来不一定需要 glibc

file 一下,编译的二进制果然是静态的:

$ file ~/.local/bin/out-exe 
/root/.local/bin/out-exe: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=60b77954d518087e2421560cbfcef152d71ab337, stripped

当然了,这样一来程序的体积会非常大。不过这也是没有办法的事情。

aLPHAtOAD

太年轻,太简单,有时候幼稚。