I recently learned of Nix, which bills itself as a “purely functional package manager.” It tries to keep things as self-contained as possible, installing each package into a separate directory in its store. Nix is the basis for a Linux distribution called NixOS, but can also be installed standalone.

Nix has great benefits for Haskell development. Due to its insistence on isolation and purity, different versions of packages can be swapped in and out as needed, neatly avoiding “Cabal hell” and the need for sandboxes. As much as possible, the package manager reuses compiled versions of dependencies, greatly speeding up the process after the first time.

Cabal itself might eventually adopt the kind of hermetic builds Nix offers; however, Nix works quite well already for this purpose.

I found Pavel Kogan’s post on Haskell development with Nix to be quite instructive. In this post, I hope to provide some tips for using Nix for Haskell development, specifically on Mac OS X 10.10 (Yosemite).

Getting Started with Nix on Yosemite

Nix does require you to have Xcode’s Command Line Tools installed, if you haven’t done so already.

For now (as of December 2014), you have to use a Nix testing branch in order for it to install on Yosemite. An article on the Nix wiki details the instructions, which I’ve reproduced with a few tweaks and comments here:

curl https://nixos.org/nix/install | sh
NIXDIR=~/nixtest # set this to wherever you want nixpkgs (package definition files) to reside
mkdir -p $NIXDIR
cd $NIXDIR
git clone https://github.com/joelteon/nixpkgs.git # this is the Yosemite branch
nix-channel --remove nixpkgs
cd ~/.nix-defexpr
rm -rf *
ln -s "$NIXDIR/nixpkgs" .
 
echo "export NIX_PATH=$NIXDIR/nixpkgs:nixpkgs=$NIXDIR/nixpkgs" >> ~/.bashrc 
 
# create nix configuration dir and add the cache for nixpkgs
 
sudo mkdir /etc/nix
echo "binary-caches = http://zalora-public-nix-cache.s3-website-ap-southeast-1.amazonaws.com/ http://cache.nixos.org/" | sudo tee /etc/nix/nix.conf
 
source ~/.bashrc # pick up the new NIX_PATH config
# and finally:

nix-env -i nix

This will take a while to install, since it downloads a lot of binaries and does plenty of compiling.

Haskell Packages

Once Nix is installed, you’ll want to install two additional packages in order to use Haskell:

nix-env -iA nixpkgs.haskellPackages.ghc nixpkgs.haskellPackages.cabalInstall

(Note that while this will put cabal on your path, you’ll want to go through Nix rather than cabal to install new packages; otherwise, the benefits will be lost.)

When it’s done, you can look through some of the Haskell packages that Nix already has available.

nix-env -qaP '*' | grep haskellPackages | less

There are quite a few added from Hackage already, and others can be added as desired.

Adding custom packages

For packages not already in Nix that you want to add, you can set up a personal config.nix file. First do mkdir -p ~/.nixpkgs/haskell, then add something like the following to ~/.nixpkgs/config.nix (again kudos to Pavel Kogan for working this out):

{
  packageOverrides = pkgs: rec {
    haskellPackages = with pkgs.haskellPackages; pkgs.haskellPackages // rec {
      # examplePkg_local = callPackage ./haskell/examplePkg_local.nix {};
      # example2 = callPackage ./haskell/example2.nix { examplePkg = examplePkg_local; };
      # example3 = callPackage ./haskell/example3.nix { inherit example2; };
    };
  };
}

Some brief notes on the Nix syntax used here:

  • # is a comment. // looks like a comment, but is actually a set union that combines the existing haskellPackages with the ones you describe.
  • rec allows entries in the same set to refer to each other.
  • A specific version of a dependency can be passed as an argument: so example2 will use examplePkg_local, rather than whatever is in nixpkgs for examplePkg (if anything).
  • example2 = example2; and inherit example2; are equivalent: “pull in example2 from the local context.”

SSL/TLS support

One tricky OS X-specific problem I encountered was a failure in the http-client-tls library (required by many apps that use secure HTTP connections). Its single test was constantly failing, with an error message of security: createProcess: runInteractiveProcess: exec: does not exist (No such file or directory).

Turns out there’s a security executable that is used to manage certificates in OS X, to which the hermetically sealed Nix package builder does not allow access. A recent attempt was made to add this tool to Nix, but whether it was because of my using 10.10 or something else, I just couldn’t get it to compile.

Ultimately, I cheated by making a dummy Nix package that simply copies the binary into the store. Once this dummy package was marked as a dependency of http-client-tls, it worked fine. (Not a great solution, but seems necessary for now.)

This requires two files, builder.sh and default.nix in the ~/.nixpkgs/security folder.

(Enter mkdir -p ~/.nixpkgs/security and create files like those in the gist below):

For http-client-tls itself, you can generate an initial Nix expression by running:

cabal2nix cabal://http-client-tls > ~/.nixpkgs/haskell/httpClientTls.nix

Then, to get httpClientTls to pull in the security tool, add security_tool to ~/.nixpkgs/haskell/httpClientTls.nix in the arguments at the top and to testDepends, like so:

{ cabal, connection, dataDefaultClass, hspec, httpClient, httpTypes, security_tool
[...]

  testDepends = [ hspec httpClient httpTypes security_tool ];

[...]

Finally, the updated ~/.nixpkgs/config.nix file should look like this:

{
  packageOverrides = pkgs: rec {
    security_tool = pkgs.callPackage ./security/default.nix {};
    haskellPackages = with pkgs.haskellPackages; pkgs.haskellPackages // rec {
      httpClientTls = callPackage ./haskell/http-client-tls.nix { inherit security_tool; };
    };
  };
}

To make sure all is well, run nix-env -iA nixpkgs.haskellPackages.httpClientTls and make sure the test passes. Afterward, applications that rely on http-client-tls should work as normal.

Building a Haskell app locally

In my case, I wanted to install Hakyll to do static blog generation. Hakyll requires you to write a small Haskell program that defines the structure of your site, which has its own cabal file.

To build your local app, you may want to use nix-shell. This is similar to a sandbox, but without requiring everything to be compiled from scratch. This tool reads a shell.nix or default.nix file in the current directory, and sets up an appropriate environment with all dependencies at the ready.

Here’s a little function that you can use to generate shell.nix from a cabal file. (This just packages the substitutions in Kogan’s post into one command.)

mk_nix_shell() { cabal2nix --sha256="0" . \
    | perl -0777 -p -e 's/{.+}:/{ haskellPackages ? (import <nixpkgs> {}).haskellPackages }:/s' \
    | sed -E -e 's/(cabal\.mkDerivation)/with haskellPackages; \1/' -e 'sXsha256 = "0";Xsrc = "./.";X' \
    > shell.nix; }

Use this command by running mk_nix_shell in the project directory (you must have one .cabal file in project root).

Once shell.nix is in place, you can run nix-shell and you’ll be dropped into an environment that has only a core set of tools (cd, mkdir, etc.) plus cabal and any libraries you requested. You can then run cabal build and the library versions you specified in Nix will be used to compile.

Finally, if you have any dependencies that are in Nix but not Cabal (the CSS preprocessor LESS, for instance), you can add a definition extraLibraries to your shell.nix file, like so:

  extraLibraries = with import <nixpkgs> {}; [ nodePackages.less ];

Further Questions

Nix has worked great for me thus far: I’ve already been able to resolve several version conflicts that previously would have required sandboxes and a lot of re-installing. However, there are still some issues with this setup.

Most frustratingly, I still haven’t worked out how to use SublimeHaskell with the Nix-managed set of tools. I’ve thought about trying to use Emacs or another editor that exists within nixpkgs instead, but have yet to set one up. (A version of Sublime is available for NixOS and thus SublimeHaskell ought to work more easily there; perhaps I’ll attempt that in a virtual machine.) If anyone comes up with a solution for this, I’d be glad to hear it.

EDIT: 2014-12-29

I’ve managed finally to set up SublimeHaskell! It required me to use the hsdev branch to avoid problems with the ModuleInspector.

To install, create Nix expressions for hsdev and dependency hdocs, and add them to your ~/.nixpkgs/config.nix:

cabal2nix cabal://hdocs > ~/.nixpkgs/haskell/hdocs.nix
cabal2nix https://github.com/mvoidex/hsdev > ~/.nixpkgs/haskell/hsdev.nix
    [...]
    haskellPackages = [...] {
      hdocs = callPackage ./haskell/hdocs.nix {};
      hsdev = callPackage ./haskell/hsdev.nix { inherit hdocs; };
    [...]
    };

Install hsdev globally with nix-env -iA nixpkgs.haskellPackages.hsdev. Then, in your project, create the shell.nix file as above and add this buildTools line:

  # buildDepends = [...]
  buildTools = [ cabalInstall hsdev ];

Before launching Sublime, create a file "$HOME/Library/Application Support/Sublime Text 3/Packages/User/SublimeHaskell.sublime-settings" with this contents (replacing “USERNAME” with your own):

{
  "add_to_PATH": ["/Users/USERNAME/.nix-profile/bin"],
  "enable_hsdev": true
}

Run nix-shell --pure in the project dir, and open Sublime from that shell: "/Applications/Sublime Text.app/Contents/MacOS/Sublime Text"

Hit Command-Shift-P, type “Add Repository”, and enter: https://github.com/SublimeHaskell/SublimeHaskell/tree/hsdev Finally, do cmd-shift-p “Install Package” -> SublimeHaskell, and restart the app again.

Running in the Nix shell seems to be required so that hsdev doesn’t become overwhelmed when setting up autocomplete. If you open Sublime outside the shell and SublimeHaskell stops working, I suggest to clear the cache by removing the contents of "~/Library/Application Support/Sublime Text 3/Packages/SublimeHaskell/hsdev" and starting it again.

Was all that worth it? I’ve found it so, for instance given the ease with which I can swap in libraries with profiling enabled by running an alternative nix-shell command. Hopefully this post will help others to more readily set up a working environment on Yosemite.