HOWTO --- Nix for Haskell Development

Last updated on June 15, 2015 by Jean-Philippe Bernardy
Tags: Haskell

Update: you should probably read the relevant section of the nixpkgs manual instead of this. My tool, Styx, may also be of interest.

“Nix” is often touted as the best solution to solving the so called “cabal hell”. This note is a HOWTO on using nix for Haskell development.

Blank state

Before starting with nix, I recommend removing any existing package database to avoid potential noise pollution.

Nuke your local package database:

rm -r ~/.ghc/

Nuke your cabal sandboxes:

cabal sandbox delete

Before moving on to the next stages, take a moment to enjoy the feeling of Zen.

Nix what?

A word of warning. Nix may refer to any of these (related) projects:

This HOWTO is about leveraging nix and nixpkgs; I will not cover NixOS, and assume that NixOS is not installed.

Installing nix

Assuming you’re on a single user system, you can

curl https://nixos.org/nix/install | sh

This modifies your .profile, so logout/login is advised. After this you’ll get the standard nix commands in your path. Your system will be setup to fetch the packages available in nixpkgs.

(You may want not to run shell scripts as you download them from the internet. In this case lookup https://nixos.org/nix/download.html and do a proper installation as you please.)

Any tedious work will be taken care of by the cabal2nix tool. Install it now using nix:

nix-env -iA nixpkgs.haskellPackages.cabal2nix

The options for the above command are:

-i: to mean “install”

-A: to mean “attribute”. Long story short, this means to refer to the package by the name of the nix expression describing it rather than by the result of that expression. Note in particular that the name of the expression does not specify a version: we get the version that the nix team has currently selected for inclusion in nixpkgs.

Constructing the Haskell environment

We will begin by setting up a default environment for run-of-the-day Haskell hacking. If your projects are already neatly organized in repos, it is not necessary to go through these stages, as we will eventually build consistent environments from a configuration file which is stored in the repo of your project. Yet you should read this section as it will introduce concepts and commands needed later.

You should define your Haskell environment by creating a ~/.nixpkgs/config.nix with the following contents:

{
  packageOverrides = super: let self = super.pkgs; in
  {
     myHaskellEnv =
     self.haskellPackages.ghcWithPackages
        (haskellPackages: with haskellPackages; [
           mtl QuickCheck random text alex cabal-install cpphs happy ghc-paths
           # or anything you like.
        ]);
  };
}

Then you can install this GHC environment by doing

nix-env -iA nixpkgs.myHaskellEnv

Most certainly, this will download a pre-compiled binary of GHC (of a recent version, as I write, version 7.10.1), produced by the Hydra build farm. While binaries are a nice time-saving convenience, working from sources is seamless, as we’ll see later.

All of Hackage should be available in nixpkgs, but you can check the actual list of available packages by doing:

nix-env -qaP -A nixpkgs.haskellPackages

In the above -a means “all” and -P means to print the attribute names as well. The -A attribute is necessary for haskell packages, as they are not visible by default (this choice baffles me too).

Go ahead and install all the stuff you need. You should not suffer any problems with package dependencies, because the nixpkgs maintainers take care to offer a set of packages which are mutually compatible.

As far as I understand, automatic builds of nixpkgs are performed. Build problems are reported to the nixpkgs maintainers, and so should be eventually fixed. So, using nixpkgs gives pretty good guarantees compared to plain cabal, even though issues might temporarily sneak in.

A quirk

In order to support the ghc-paths library (ghc-mod depends on it), you need to add the following variables to your environment:

NIX_GHC_VERSION=$(ghc --numeric-version)
export NIX_GHC="$HOME/.nix-profile/bin/ghc"
export NIX_GHCPKG="$HOME/.nix-profile/bin/ghc-pkg"
export NIX_GHC_DOCDIR="$HOME/.nix-profile/share/doc/ghc/html"
export NIX_GHC_LIBDIR="$HOME/.nix-profile/lib/ghc-${NIX_GHC_VERSION}"

Why the need for this? In the works of Peter Simons: “The problem with that [ghc-paths] is fundamentally based on the assumption that there is one GHC and all libraries are installed into its lib directory – an assumption Nix doesn’t fulfill. We try to remedy that issue by patching ghc-paths so that it returns the contents of those environment variables above instead of hard-coded (incorrect) values.

Upgrading

Eventually you’ll want to upgrade your stuff. If this is the first time you read this HOWTO, skip this section.

Optionally, start by cleaning the old versions that you have installed.

nix-collect-garbage -d

(This will keep the current version; and I’ll let you lookup what -d does)

You can then upgrade the list of packages by doing:

nix-channel --update

And do an actual upgrade like so:

nix-env --upgrade

Supposedly, rolling back to the previous version can be done by

nix-channel --rollback

(but I’ve never tried it).

Living on the bleeding edge

Even though nixpkgs has all of Hackage, you may need the version of a package not yet pushed to Hackage (for example a fork). This can be done on your user account, without having to touch the system-wide configuration, let alone the central nixpkgs repo.

We will store the package descriptions in ~/.nixpkgs/local (but you can choose any location you like):

mkdir -p ~/.nixpkgs/local

First you’ll need some VCS (git) support:

nix-env -i nix-prefetch-scripts

Let us suppose we want to install the latest (HEAD) of the pretty-compact package:

cabal2nix git@github.com:jyp/prettiest.git > pretty-compact.nix

While we’re at it, check the cabal2nix options:

cabal2nix -h

In particular, the following options can come in handy if the package is in a slightly broken state. So read their documentation carefully.

--jailbreak
--no-check
--no-haddock

The pretty-compact.nix file then contains all the info needed to build the package. We should strive not to edit this file, as we may need to regenerate it later. Note that the above command creates a nix file referring to the current commit of the master branch of the repo. When the repo is usefully updated, you’ll need to re-run the command. If you want, save the cabal2nix command to remember the options you used.

echo 'cabal2nix cabal://pretty-compact-1.0 > pretty-compact.nix' >> c2n-regen.sh

Other sources

You may want to use a local tree as the source for the package. To do so, invoke cabal2nix with its root instead of the cabal url:

cabal2nix ~/repo/prettiest > pretty-compact.nix

Linking packages into your set of package descriptions

We can now proceed to add the package to the user-local set of packages. To do so, override the haskellPackages field in nixpkgs, as follows:

{
  packageOverrides = super: let self = super.pkgs; in
  {
     myHaskellEnv =
        # self.haskellPackages.packages.ghc7101
        self.haskellPackages.ghcWithPackages (haskellPackages: with haskellPackages; [
           mtl QuickCheck random text alex cabal-install cpphs happy ghc-paths BNFC
         ]);

     haskellPackages = super.recurseIntoAttrs(super.haskellPackages.override{
        overrides = self: super:
        let callPackage = self.callPackage; in {
            pretty-compact = callPackage ./local/pretty-compact.nix {};
        };
       });
  };
}

Per-project configuration

The configuration in ~/.nixpkgs is, obviously, a per-user one. Yet, it is not difficult to create per-project environments, which can be shared with collaborators, via the project repo.

To create such a per-project environment:

  1. Create a nix file for your project. The following commands will do it by picking the data that you have in the cabal file:

     cd my/repo/project
     cabal2nix . > default.nix
  2. Create the package descriptions of the packages you depend on, using cabal2nix, as described above. In this example I assume that they reside in the nix subdirectory of your repo.

  3. Create a shell.nix file with the following contents:

let pkgs = (import <nixpkgs> {});
    haskellPackages = pkgs.recurseIntoAttrs(pkgs.haskellPackages.override {
        overrides = self: super:
        let callPackage = self.callPackage; in {
              # pretty-compact = callPackage ./local/pretty-compact.nix {};
              thisPackage = callPackage (import ./default.nix) {};
        };
       });
in haskellPackages.thisPackage.env

Assuming a fixed version of nixpkgs, the above describes a reproducible build environment. (One could also fix the version of nixpkgs. I leave this as an exercise to the reader. Yet, tracking the nixpkgs is not necessarily a bad idea, as you may want to eventually push your package to nixpkgs.)

You can then use the above to build your package. Type:

 nix-shell --pure

This opens a shell which provides the build environment described above. It also removes from the environment whatever is not explicitly required, thanks to the --pure option. (By default, nix-shell picks shell.nix from the current directory) Proceed to build the project:

 cabal configure
 cabal build

(When you’re done, exit the shell to restore your default environment)

Convenience

You may also run commands directly, by using:

 nix-shell --pure --command "..."

I personally have a nix-ghc script with the following contents:

nix-shell --pure --command "ghc $*"

The above is useful for running ghc directly in the nix-shell from your favourite environment. Because I use emacs and flycheck, I have configured flycheck to use the local nix-env by adding a .dir-locals.el file with the following contents:

((haskell-mode . ((flycheck-haskell-ghc-executable . "nix-ghc"))))

Nix is not for me after all

If you decide that Nix is not the right solution for you after all, it’s easy to get rid of it. Just type:

 rm -rf /nix

and take a moment to enjoy the feeling of Zen before continuing your search for a less painful cabal.

References

This HOWTO is compiled from various sources, including the NixOS wiki and various blog posts, reddit comments, e-mails, etc. The following is just the initial set of references that I used. (Most recent ones last.)

Note in particular that there are other recipes for the nixconfig file [1,2]. Mine allows both: