Portable Haskell executables, anyone doing that (possibly using Nix)?

I want to write a few smaller tools in Haskell that are to be run on a very old machine (to which I have no access right now).

I’m already dreading the low glibc version on there which make it virtually impossible to just compile something and transfer to the machines. My question is, then, does anyone build Haskell binaries that don’t depend on glibc? I found

Am I missing a solution here? The nixpkgs manual doesn’t offer any more advice on this, as far as I can see.

1 Like

If you don’t want to depend on glibc, you are bound to find an alternative libc indeed. This could be musl, diet, or any alternative libc (even bionic in principle). You also most likely want a static one, so you can just drop and deploy them.

Using muslc, you are pretty much bound by the syscalls the kernel provides. Thus on a very very old linux, you might run into issues. There are also some issues if you use e.g. the dns package from hackage with muslc on systems that messed up their file system layout, see https://github.com/kazu-yamamoto/dns/pull/166

I am building fully static haskell applications for linux using haskell.nix; and even deploy them to platforms like Synology NASs. This is arguably way more complex than anything you’d likely need though.

3 Likes

PostgREST builds static binaries using static-haskell-nix, but I concur that it seems a bit abandoned – we’re maintaining some patches to keep it working with recent nixpkgs.

Judging by this comment, pkgsStatic in nixpkgs directly might be a better option, but I couldn’t figure out how to make that work yet with PostgREST.

1 Like

This might be a slightly better example; even though it also contains more than just static musl.

1 Like

I’ve had pretty good success with ghc-musl + stack + cabal. The project itself builds using stack (when just building on the host), but with stack2cabal I was able to get Cabal building the project inside the ghc-musl container. (I can’t recall exactly why I couldn’t get Stack working inside the container.) A bit messy overall, but it does work.

If you need further libraries, it’s just an “apk add” away – assumming of course that the libraries are packaged for Alpine Linux. (I can’t recall exactly which I’m using off the top of my head, but I seem to remember there was some LDAP and SASL stuff in there, so decently advanced stuff.)

I’ve had success with ghc-musl and stack without using cabal. This is how I do it: λm.me - Building a bulletin board using twain and friends

I can’t recall exactly, but I believe I’ve tried the same thing. I think the issues mostly stemmed from not being able to use “docker: true” because we were already running in a CI container. I think.

Use an alpine linux docker container and build your binaries with cabal build --enable-executable-static. Don’t use cabal install. That’s all there is to it. GHCup, cabal and HLS all do this to build static executables.

1 Like

pkgsStatic looks interesting indeed! But I guess we’ll have to wait for some more documentation. I cannot figure it out either.

1 Like

That’s what I did now, and it’s actually working fine. I’m a bit baffled by this being possible and pretty easy as well.

2 Likes

pkgsStatic.haskellPackages is mostly usable just like the normal (non-static) Nixpkgs Haskell package set. Here’s a small example of using it (assuming you’re already familiar with Nix):

First, get into a repl with a recent Nixpkgs. For example, here’s the nixpkgs-unstable channel from today. I think 22.05 would probably work as well:

$ nxi repl 'https://github.com/NixOS/nixpkgs/archive/52dd719bbd13d9f0974e5c3c29c74aa249e5b091.tar.gz'

Then, build any Haskell executable from pkgsStatic. For example, here’s hlint compiled statically:

nix-repl> :b pkgsStatic.haskellPackages.hlint

This derivation produced the following outputs:
  data -> /nix/store/2d4gbwkb577wsj46bqqf9738wf2l0r18-hlint-static-x86_64-unknown-linux-musl-3.3.6-data
  out -> /nix/store/8xjirxkl8n2lxz1qikdap6bvpmxrgdq0-hlint-static-x86_64-unknown-linux-musl-3.3.6

Checkout that it is actually statically-linked:

$ file /nix/store/8xjirxkl8n2lxz1qikdap6bvpmxrgdq0-hlint-static-x86_64-unknown-linux-musl-3.3.6/bin/hlint
/nix/store/8xjirxkl8n2lxz1qikdap6bvpmxrgdq0-hlint-static-x86_64-unknown-linux-musl-3.3.6/bin/hlint: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped

Then run it:

$ /nix/store/8xjirxkl8n2lxz1qikdap6bvpmxrgdq0-hlint-static-x86_64-unknown-linux-musl-3.3.6/bin/hlint --version
HLint v3.3.6, (C) Neil Mitchell 2006-2021

If you’re not able to build your library statically (due to system library dependencies that can’t be built statically?), there are a couple other options:

  • for simple executables, manually rewrite library paths with patchelf. For more complicated executables, use some sort of bundler like exodus
  • a user_namespaces-based bundler like nix bundle
1 Like

Thanks for the extensive description! With that, I was able to set up a static build. It fails, though:

Configuring vty-5.33...

Setup: Encountered missing or private dependencies:
terminfo >=0.3 && <0.5

Is this a known problem, or shall I add a comment to the PR that’s open?

1 Like

I took a look at this.

It appears that the underlying problem is that the GHC used when building things from pkgsStatic.haskellPackages is not compiled with the terminfo boot library.

(I’ve used commit 2fd68ed753 from haskell-updates for the following commands.)

Here’s how to confirm that terminfo is actually a boot library:

$ nix-shell -I nixpkgs=./. -p haskellPackages.ghc --command 'ghc-pkg list | grep terminfo'
    terminfo-0.4.1.5
$

But it is not built in pkgsStatic:

$ nix-shell -I nixpkgs=./. -p pkgsStatic.haskellPackages.ghc --command 'x86_64-unknown-linux-musl-ghc-pkg list | grep terminfo'
$

One way to work around is to explicitly pass terminfo when building vty:

$ nix repl ./.
nix-repl> :b pkgsStatic.haskellPackages.vty.override { terminfo = pkgsStatic.haskellPackages.terminfo_0_4_1_5; }
This derivation produced the following outputs:
  out -> /nix/store/kb2rhmaan57ij5n1qykrkljc5hw7r4m1-vty-static-x86_64-unknown-linux-musl-5.33

If you’re interested in fixing this though, I’d suggest first creating an issue about it in Nixpkgs (and pinging @NixOS/haskell). And then trying to either figure out why terminfo isn’t built for the static GHC, or just removing the Nix code that assumes terminfo is a boot library when building statically.

Thanks for the debugging! It works now, and I have created an issue on GitHub as well, like you mentioned:

1 Like