HOWTO --- Nix for Haskell Development
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:
- the nix package manager
- the nixpkgs set of packages (described using the nix language)
- the NixOS linux distribution
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:
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
Create the package descriptions of the packages you depend on, using
cabal2nix
, as described above. In this example I assume that they reside in thenix
subdirectory of your repo.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.)
- [1] https://ocharles.org.uk/blog/posts/2014-02-04-how-i-develop-with-nixos.html
- [2] http://www.pavelkogan.com/2014/07/09/haskell-development-with-nix/
- [3] http://wiki.ocharles.org.uk/Nix#how-do-i-use-cabal2nix-for-local-projects
- [4] https://www.youtube.com/watch?t=12&v=BsBhi_r-OeE
- [4] Conquer Cabal Hell With Nix https://www.youtube.com/watch?v=mQd3s57n_2Y
Note in particular that there are other recipes for the nixconfig file [1,2]. Mine allows both:
to have dependencies between you local packages
to override existing packages. (so you can bring in another version of, say, “lens” than the one on nixpkgs) Be wary that if you bring another version of a fundamental package (say “random”) much stuff will have to be recompiled locally.