Using the Nix Package Manager for Haskell Development from Arch Linux
I recently installed and configured NixOS on a laptop and had to learn how to develop Haskell on it. The Nix community uses something called cabal2nix
(version 2.0 and up!) and nix-shell
to get the job done. While things work quite smoothly right now in NixOS, I was wondering if I could do the same on my desktop Arch Linux box.
The answer is yes — you can easily use Nix to create a ‘system sandbox’ of sorts (the Nix store) that is completely isolated from Arch’s own Haskell packages/GHC. To be clear, what we are trying to do is install the Nix package manager (which is composed of many satellite programs like nix-env
, nix-shell
, etc.) so that we can develop Haskell programs with all the advantages that come with it.
For myself, I have several different Haskell projects, and I wanted to avoid redownloading and recompiling the same packages for each project’s Cabal sandbox environment. Using Nix, I still have the same Cabal sandboxes (one for each project root), but Nix allows all the different sandboxes to share the same packages if the versions and dependencies are the same. And plus, because the Nix store (where Nix stores everything — /nix/store
) is independent of Arch’s pacman
tool, there is no fear of conflict or things breaking whenever you upgrade Arch Linux’s own Haskell packages.1
Use the Nix Manual to install Nix
The Nix manual has up-to-date documentation on how to get Nix. When we say Nix, we are talking about the collection of console programs (with a nix-
prefix in their names) that make up to form the Nix package management system — much like how Git is made up of smaller programs that work as a team. There is a nix
package on the AUR, but I suggest simply following this guide.
The first step is to run the install script from the NixOS site (which hosts Nix and other related programs) as a normal user:
$ bash <(curl https://nixos.org/nix/install)
. You will now have a directory called /nix
in your system. This is where everything related to Nix will be stored. In addition, the script will create some hidden files under your user’s home directory with the .nix-
prefix. The most important file for now is ~/.nix-profile
, because it links to a shell script that initializes the Nix environment (to bring in nix-*
utilities into the current shell’s scope). You will get a message from the shell script to source this file, like this:
$ . /home/l/.nix-profile/etc/profile.d/nix.sh
. For me, I put the whole thing into an alias for my shell called nix
, like this:
# somewhere in my ~/.zshrc
alias nix='. /home/l/.nix-profile/etc/profile.d/nix.sh'
.2 So, whenever I want access to Nix utilities, I just type in nix
and go on my merry way.
Install cabal2nix
and cabal
Now, use your alias to enable Nix.
$ nix
You now have access to all the nix-*
utilities that make up to provide the Nix package management system. You can list all Nix-packaged packages with nix-env -qaP
. For us, we’re interested in the cabal2nix
package. As of the time of this writing, it is called nixpkgs.haskellPackages.cabal2nix
. However, the haskellPackages
prefix refers to the old system that has been more or less deprecated as of January 2015. We need to use the haskellngPackages
(note the ng
) prefix instead. I know that nixpkgs.haskellngPackages.cabal2nix
isn’t listed with the nix-env -qaP
command, but I believe that’s for legacy reasons. You can still install it! Let’s do that now:
$ nix-env -iA nixpkgs.haskellngPackages.cabal2nix
. This will give you the very useful cabal2nix
binary which you can use to convert any .cabal
file into something that Nix can understand! Let’s also install cabal
for Nix:
$ nix-env -iA nixpkgs.haskellngPackages.cabal-install
. This will install cabal
to ~/.nix-profile/bin/cabal
. This step is not really necessary if you have cabal-install
already installed on the Arch Linux side with pacman
. However, I still recommend it because
- if you’re using Nix for Haskell development, there is no longer a need to use
cabal
outside of the Haskell/Nix development process; - it just makes sense to use the
cabal
package that comes from the same source tree ascabal2nix
(i.e., from the samehaskellngPackages
set3); and - as of the time of this writing the
cabal-install
version from Nix packages set is newer than the Arch version.
At the end of the day, your cabal
binary should be writing to ~/.cabal
so take care to use one version and stick with it.
Nixify your project
Create a .cabal
file
If you haven’t done so already, create a Cabal file your_project.cabal
in your project’s root folder to describe the dependencies in the traditional Haskell way. This step is mandatory!
Create a shell.nix
file
Go to your project’s root folder that contains your_project.cabal
, and do
$ cabal2nix --shell . > shell.nix
. The actual syntax is cabal2nix --shell path/to/cabal/file
, which prints out the contents of the .nix
file to STDOUT. In the case above, we redirect it to a file named shell.nix
. The name of this file is important because it is what nix-shell
expects.
Now just invoke
$ nix-shell
and you’re set. You will be dropped into a bash
instance that has knowledge of the Nix store. The first time you run nix-shell
, Nix will identify any missing dependencies and install them for you. Because your project’s shell.nix
file describes a Haskell project, nix-shell
will install GHC along the way. So when it’s ready, you can start ghci
. Because you installed cabal2nix
earlier, you have access to cabal
(i.e., cabal
is a dependency of cabal2nix
).
To build your binary just do cabal build
! Personally I like to instantiate a Cabal sandbox with cabal sandbox init
first, and then do cabal configure
, cabal repl
, cabal build
, etc.
Local dependencies
If you’re like me, you might have a Haskell library you wrote for yourself (let’s call it “Private Project X” (PPX)) which is not on Hackage. If you just want to build PPX on its own, you can use the same steps outlined above. But what if your other project depends on PPX?
The trick is to use cabal2nix
, and to set up your ~/.nixpkgs
folder. You should already have ~/.nixpkgs
created by now as a result of installing Nix. Make a folder called ~/.nixpkgs/my-local-hs
. Now do
$ cabal2nix path/to/ppx > ~/.nixpkgs/my-local-hs/ppx.nix
. This will create a Nix expression that can be used to build PPX with Nix. It’s like creating a PKGBUILD file. The next step is to create a ~/.nixpkgs/config.nix
file, as follows:
# Taken from
# http://lists.science.uu.nl/pipermail/nix-dev/2015-January/015601.html.
{
packageOverrides = super: let self = super.pkgs; in
{
haskellngPackages = super.haskellngPackages.override {
overrides = self: super: {
# Enable profiling. Taken from
# http://lists.science.uu.nl/pipermail/nix-dev/2015-January/015620.html.
# Comment out this line if you do not want to enable profiling!
mkDerivation = expr: super.mkDerivation (expr // {
enableLibraryProfiling = true; });
# Private package
ztile = self.callPackage ./my-local-hs/ppx.nix {};
};
};
};
}
. Now, invoke cabal2nix --shell
for your other project that depends on PPX. When you invoke nix-shell
for this other project, Nix should be able to resolve the dependency, based on the information you gave it in ~/.nixpkgs/config.nix
. That’s it!
Conclusion
I recommend trying Nix out for Haskell development, or just as a secondary package manager in general. Right now, everything “Just Works” and it’s a pleasure to see different Haskell projects re-use the same packages, even when they are Cabal-sandboxed, as long as you are doing everything within nix-shell
.
Even though the title of this post suggests that this is an Arch Linux guide to Nix, there is nothing Arch-specific about it. You should be able to use the steps in this post for any Linux distribution.
Happy hacking!
That being said, if you’re using Nix then there is little reason to continue to use the Arch packages. I say this with some reluctance, as I am the author of the cabal2pkgbuild utility.↩
There are no Nix utilities with
nix
as its name, so there’s no concern about name clashing.↩To figure out what Nix packages set, a.k.a. channel you are using, do
nix-channel --list
.↩