Haskell development with Nix
09 Jul 2014Haskell development with cabal was greatly improved by the addition of cabal sandboxes, but cabal hell and issues with non-Haskell dependencies persist. For example, building the gtk bindings is impossible with the latest version of cabal; I was forced to work around this by installing them with the system package manager. When the necessary packages at the right version are unavailable in the package manager though, an alternative solution needs to be found. Enter Nix.
I actually first encountered Nix a few years ago, trying out NixOS for a while before returning to better supported distributions. Even now, the downsides seem to outweigh the advantages, at least for a single-user system, but with recent attention to using Nix for Haskell development, I decided to try using it solely for that, without dedicating my entire system to NixOS.
nix-shell
The major draw of developing with Nix over cabal alone is being able to easily switch out which dependencies are used, without having to delete, rebuild, wait, worry about whether the builds will succeed with the new versions, and so on. The way this is done is by having nix-shell
set up an environment where all the dependencies are available. Fortunately, enumerating the dependencies doesn’t have to be done by hand; the cabal2nix
tool can be used to create Nix expressions from your project’s cabal file:
cabal2nix projectname.cabal --sha256=0 > shell.nix
The dummy sha256 hash is needed to prevent cabal2nix
from trying to download the package from Hackage.
cabal2nix
generated Nix expressions are intended to be added to nixpkgs, so a few edits are needed to have it work automatically with nix-shell
:
- Replace
sha256 = "0";
withsrc = "./.";
-
Replace the list of function parameters (
{ cabal, mtl, ... }:
) with{ haskellPackages ? (import <nixpkgs> {}).haskellPackages }:
If any of the parameters are not Haskell packages, keep them in the list, but add a default value, as above.
-
Prepend
with haskellPackages;
to the expression body, i.e.with haskellPackages; cabal.mkDerivation (self: { ...
-
Optionally, if you don’t have cabal installed globally, add
cabalInstall
tobuildTools
. So, if no other build tools are used:buildTools = [ cabalInstall ];
A potential issue can arise if the name of a non-Haskell package you need conflicts with the name of a Haskell package. In that case, you need to move the
with haskellPackages;
inside the expression body, like so:haskellPackages.cabal.mkDerivation (self: { ... buildTools = with haskellPackages; [ cabalInstall ]; buildDepends = with haskellPackages; [ ... ]; ...
Alternatively, add the package to
extraLibraries
:extraLibraries = with import <nixpkgs> {}; [ pkgname ];
Now, calling nix-shell
in the project directory will drop you into a shell with all the necessary packages in your $PATH
, or you can hook up something like nix-shell --command='cabal build'
to your build system.
Missing dependencies
Not all packages on Hackage are in nixpkgs. If your project depends on one of those, it’s possible to add it yourself. There are two ways to do this; you can clone the nixpkgs repository and modify it1, or you can just add it locally. I will describe the latter.
To add Nix expressions to the local nixpkgs collection, you need to create a ~/.nixpkgs/config.nix
file. Now, the Nix wiki gives an outline of what this file should look like, but with the Nix documentation being as it is2, most of the functions and attributes used are undocumented, so I wasn’t able to tell how it’s supposed to work. Not being comfortable with copy-pasting an opaque block of code, I wrote a clearer version, which probably works differently, but does the job nonetheless:
{
packageOverrides = pkgs: rec {
haskellPackages = with pkgs.haskellPackages; pkgs.haskellPackages // rec {
packageName = callPackage ./haskell/package-name.nix {};
...
};
};
}
With this, packages can be added much the same way they would be to the main nixpkgs collection:
- Add the
callPackage
line toconfig.nix
for the package, as above. -
Use
cabal2nix
to generate the Nix expression:cabal2nix cabal://package-name > ~/.nixpkgs/haskell/package-name.nix
Simple.
Local package dependencies
If you want to have your project depend on a local package not on Hackage, that’s just as easy; all it takes is replacing the sha256
attribute with a src
attribute pointing to the directory containing the package (as with shell.nix
for nix-shell
project support).
Conclusion
Using Nix expressions to set up your development environment can provide a lot of flexibility, allowing you to do things like switch between libraries with or without profiling, or even between compiler versions, which has been described elsewhere3. With this post I’ve shown, I hope, how a basic Nix setup can be created with very little up-front effort, streamlining workflow and providing a base to build on should that flexibility become needed.
-
That is to say, not very good. ↩
-
My use of Nix for Haskell development is heavily inspired by “How I Develop with Nix” and “My experience with NixOS”. ↩