--- title: Static linking on Nix with GHC 9.6 description: Static linking moves effort from your users to you, and it is time to pay the cost again. --- I like to use [Nix](https://nixos.org/) to do static builds of [Haskell](https://haskell.org) programs for distribution. I find it very needs suiting. Haskell is the best general-purpose programming language, and Nix is the best build tool, and I like things that are good. The largest project on which I work regularly is [Futhark](https://futhark-lang.org), which has just this setup, as [previously described here](https://sigkill.dk/blog/2021-05-06-another-haskell-and-nix-setup.html). To ensure reproducibility, the version of [Nixpkgs](https://github.com/NixOS/nixpkgs) is pinned (using [`niv`](https://github.com/nmattia/niv), but that detail doesn't matter). Every once in a while I manually update the version of Nixpkgs we use (also with `niv`, and again that doesn't matter, but `niv` is really good so I should shill it a bit). Sometimes that has no real effect, and sometimes it bumps the version of [GHC](https://www.haskell.org/ghc/). Sometimes that causes trouble. In particular, today when I updated Nixpkgs, it bumped the version of GHC 9.4 to 9.6, which resulted in a lot of linker errors. The reason seems to be that the GHC 9.6 in Nixpkgs expects `libdw`: ``` ghc --info|grep libdw ,("RTS expects libdw","YES") ``` This was not the case for GHC 9.4. Now, GHC is smart enough to link statically against `libdw`, but *not* to also link against all the *dependencies* of `libdw` - and there are many, and Nixpkgs does not make them available as static libraries by default. After a lot of trial and error, I came up with the following `default.nix` that will let you statically link a Haskell program with GHC 9.6 (and presumably later): ``` let pkgs = import (builtins.fetchTarball { url = "https://github.com/NixOS/nixpkgs/archive/a39290dfdf0a769a3fda56b61fdf40f7d9db7ea1.tar.gz"; }) {}; in pkgs.haskell.lib.overrideCabal (pkgs.haskell.packages.ghc910.callCabal2nix "bug" ./. {}) ( _drv: { isLibrary = false; isExecutable = true; enableSharedExecutables = false; enableSharedLibraries = false; configureFlags = [ "--ghc-option=-split-sections" "--ghc-option=-optl=-static" "--ghc-option=-optl=-lbz2" "--ghc-option=-optl=-lz" "--ghc-option=-optl=-lelf" "--ghc-option=-optl=-llzma" "--ghc-option=-optl=-lzstd" "--extra-lib-dirs=${pkgs.glibc.static}/lib" "--extra-lib-dirs=${pkgs.gmp6.override { withStatic = true; }}/lib" "--extra-lib-dirs=${pkgs.zlib.static}/lib" "--extra-lib-dirs=${(pkgs.xz.override { enableStatic = true; }).out}/lib" "--extra-lib-dirs=${(pkgs.zstd.override { enableStatic = true; }).out}/lib" "--extra-lib-dirs=${(pkgs.bzip2.override { enableStatic = true; }).out}/lib" "--extra-lib-dirs=${(pkgs.elfutils.overrideAttrs (old: { dontDisableStatic = true; })).out}/lib" "--extra-lib-dirs=${pkgs.libffi.overrideAttrs (old: { dontDisableStatic = true; })}/lib" ]; } ) ``` (This assumes you have a `bug.cabal` file actually defining some Haskell project.) The important part is the various `configureFlags` I pass to GHC, in particular the extra linker flags (the `-optl` stuff) and the `--extra-lib-dirs`. The four different compression libraries and `elfutils` are for the benefit of `libdw`. Note also that I have to override a bunch of Nixpkgs derivations to get them to actually build the static libraries (and that I had to use four different techniques to do so). It's a bit chaotic, but because of Nix, I'm actually not worried about pushing this into production, as it'll keep working at least until the next time I bump Nixpkgs. [See also here](https://github.com/diku-dk/futhark/blob/de60d2fb65dd5be08761ac9f7782531535bb73a8/default.nix) for an example of this in practice.