I do all my development using Nix and cabal these days, and I can happily say that they work in harmony very well. My current workflow is very new, in that it relies on features in nixpkgs
that have only just reached the master branch. As such, the first thing you’ll need to do is clone nixpkgs
from Github:
cd ~
git clone git://github.com/nixos/nixpkgs
(In the future this won’t be necessary, but right now it is).
Single Project Usage
Now that we have a nixpkgs
clone, we can start using the haskellng
package set. haskellng
is a rewrite of how we package things in Nix, and is of interest to us for being more predictable (package names match Hackage package names) and more configurable. First, we’ll install the cabal2nix
tool, which can automate some things for us, and we’ll also install cabal-install
to provide the cabal
executable:
nix-env -f ~/nixpkgs -i -A haskellngPackages.cabal2nix -A haskellngPackages.cabal-install
From this point, it’s all pretty much clear sailing.
If you’re starting a new project, you can just call cabal init
in a new directory, as you would normally. When you’re ready to build, you can turn this .cabal
file into a development environment:
cabal init
# answer the questions
cabal2nix --shell my-project.cabal > shell.nix
This gives you a shell.nix
file, which can be used with nix-shell
. You don’t need to use this very often though – the only time you’ll usually use it is with cabal configure
:
nix-shell -I ~ --command 'cabal configure'
cabal configure
caches absolute paths to everything, so now when you want to build you just use cabal build
as normal:
cabal build
Whenever your .cabal
file changes you’ll need to regenerate shell.nix
– just run the command above, and then cabal configure
afterwards.
Multiple Project Usage
The approach scales nicely to multiple projects, but it requires a little bit more manual work to “glue” everything together. To demonstrate how this works, lets consider my socket-io
library. This library depends on engine-io
, and I usually develop both at the same time.
The first step to Nix-ifying this project is to generate default.nix
expressions along side each individual .cabal
file:
cabal2nix engine-io/engine-io.cabal > engine-io/default.nix
cabal2nix socket-io/socket-io.cabal > socket-io/default.nix
These default.nix
expressions are functions, so we can’t do much right now. To call the functions, we write our own shell.nix
file that explains how to combine everything. For engine-io/shell.nix
, we don’t have to do anything particularly clever:
with (import <nixpkgs> {}).pkgs;
(haskellngPackages.callPackage ./. {}).env
For socket-io
, we need to depend on engine-io
:
with (import <nixpkgs> {}).pkgs;
let modifiedHaskellPackages = haskellngPackages.override {
overrides = self: super: {
engine-io = self.callPackage ../engine-io {};
socket-io = self.callPackage ./. {};
};
};
in modifiedHaskellPackages.socket-io.env
Now we have shell.nix
in each environment, so we can use cabal configure
as before.
The key observation here is that whenever engine-io
changes, we need to reconfigure socket-io
to detect these changes. This is as simple as running
cd socket-io; nix-shell -I ~ --command 'cabal configure'
Nix will notice that ../engine-io
has changed, and rebuild it before running cabal configure
.