Faking Non-NixOS for Stack

Posted on 17 March 2018

I like most things about NixOS, but one thing I do not like is the way it integrates with stack . Nix’s own Haskell infrastructure works well enough that this is not an issue for my own projects, but sometimes I want to test that the Stack workflow is fine for people using less opinionated distros like Ubuntu.

Fortunately, Nixpkgs includes a handy tool called buildFHSUserEnv which will build a chroot wherein everything is laid out according to the Filesystem Hierarchy Standard that most software is accustomed to. This means we can provide an environment with Stack and any dependencies and it will happily run.

Let’s walk through doing this for a package like IHaskell. We begin by cloning the IHaskell repository and creating a fhsenv.nix with only stack :

let pkgs = import < nixpkgs > {}; = importnixpkgs{}; in pkgs.buildFHSUserEnv { name = "fhs" ; targetPkgs = pkgs: [ = pkgs: [ pkgs.haskellPackages.stack ]; }

Entering the chroot and running stack build gives us our first error:

$ $( nix-build fhsenv.nix ) /bin/fhs fhsenv.nix fhs-chrootenv $ stack build $ stack build HttpExceptionRequest Request { Request { host = "raw.githubusercontent.com" port = 443 = 443 secure = True = True requestHeaders = [] = [] path = "/fpco/stackage-content/master/stack/stack-setup-2.yaml" queryString = "" method = "GET" proxy = Nothing = Nothing rawBody = False = False redirectCount = 10 = 10 responseTimeout = ResponseTimeoutDefault = ResponseTimeoutDefault requestVersion = HTTP/1.1 = HTTP/1.1 } ( ConnectionFailure Network.BSD.getProtocolByName: does not exist (no such protocol name: tcp ) ) Network.BSD.getProtocolByName: does not exist (no such protocol name: tcp

Looking through the Nixpkgs issue tracker for similar errors reveals that we need the iana-etc package. Let’s add it:

let pkgs = import < nixpkgs > {}; = importnixpkgs{}; in pkgs.buildFHSUserEnv { name = "fhs" ; targetPkgs = pkgs: [ = pkgs: [ pkgs.haskellPackages.stack pkgs.iana-etc ]; }

Now it’ll start to download GHC, which takes forever for me. This is the wrong download though, so cancel it and let’s move on. More on this in a bit.

If the download had successfully completed, stack would then have complained that make was unavailable, so we add gnumake . Then it would have complained about the lack of Perl, a missing C compiler, missing libgmp , and no pkg-config , so we add those too. Then it progresses a lot further before it halts, complaining about libtinfo being missing. The closest thing we have is ncurses , so we add that too. Now our expression looks like this:

let pkgs = import < nixpkgs > {}; = importnixpkgs{}; in pkgs.buildFHSUserEnv { name = "fhs" ; targetPkgs = pkgs: [ = pkgs: [ pkgs.iana-etc pkgs.haskellPackages.stack pkgs.gcc pkgs.gmp pkgs.gnumake pkgs.perl pkgs.pkgconfig pkgs.ncurses ]; }

This prompts stack to download a different GHC, but the whole process should complete successfully now.

At this point, we’re in luck, because IHaskell has been configured to work with stack --nix , which means the dependencies stack needs are already specified under the nix.packages key in stack.yaml , and we can copy them into fhsenv.nix to speed up the process of building everything. At this point I found that header files in /usr/include weren’t being found, but this was easy to fix by specifying C_INCLUDE_PATH in the profile attribute. I’d recommend commenting out ihaskell-widgets at this point, because it takes an absurdly long time to compile and doesn’t seem to have any interesting dependencies. The complete fhsenv.nix for stack build and stack test looks like this:

let pkgs = import < nixpkgs > {}; = importnixpkgs{}; in pkgs.buildFHSUserEnv { name = "fhs" ; targetPkgs = pkgs: [ = pkgs: [ pkgs.blas pkgs.cairo.dev pkgs.file pkgs.gcc pkgs.glib.dev pkgs.gmp pkgs.gnumake pkgs.haskellPackages.stack pkgs.iana-etc pkgs.liblapack pkgs.pango.dev pkgs.perl pkgs.pkgconfig pkgs.ncurses pkgs.zeromq pkgs.zlib.dev ]; profile = '' export C_INCLUDE_PATH= /usr/include: $C_INCLUDE_PATH /usr/include: '' ; }

Of course, building IHaskell is no fun if we can’t install it and see it in action. Providing the Jupyter notebook environment is an additional line:

let pkgs = import < nixpkgs > {}; = importnixpkgs{}; in pkgs.buildFHSUserEnv { name = "fhs" ; targetPkgs = pkgs: [ = pkgs: [ pkgs.blas pkgs.cairo.dev pkgs.file pkgs.gcc pkgs.glib.dev pkgs.gmp pkgs.gnumake pkgs.haskellPackages.stack pkgs.iana-etc pkgs.liblapack pkgs.pango.dev pkgs.perl pkgs.pkgconfig ( pkgs.python3.withPackages (ps: [ ps.jupyter ps.notebook ] ) ) (ps: [ ps.jupyter ps.notebook ] pkgs.ncurses pkgs.zeromq pkgs.zlib.dev ]; profile = '' export C_INCLUDE_PATH= /usr/include: $C_INCLUDE_PATH /usr/include: '' ; }

and we can install and run IHaskell as usual:

fhs-chrootenv $ stack build $ stack build fhs-chrootenv $ stack exec -- ihaskell install --stack $ stack exec -- ihaskell install --stack fhs-chrootenv $ stack exec -- jupyter notebook $ stack exec -- jupyter notebook

Cool!

This expression is available on GitHub.