Welcome to part 3 of my series on setting up NixOS for Haskell development! In this part, we’ll learn how to discover and install essential software for NixOS. We won’t cover the particulars of installing Haskell-related components (I’m saving that for a post on its own), but some of our decisions here will be motivated by the foresight that Haskell development is what we eventually want to do. As always, if you see an error, please let me know. I’m figuring this stuff out as I go.
In the last post, we learned a bit about the Nix package manager. Let’s recap the essential points:
- All software components in NixOS are installed using the Nix package manager.
- Packages in Nix are defined using the nix language to create nix expressions.
- Nix expressions define all inputs to a build process, including dependencies, which can themselves be nix expressions.
- Nix will build all required dependencies if they do not already exist on your system.
- Packages installed for all users are defined in the nix expression
/etc/nixos/configuration.nix
The rest of this post will focus on how to install packages into your user environment. These are the packages that are active on a per-user basis, so other users won’t necessarily have the same active packages as you do.
Finding and Installing Packages
Packages are installed by running Nix expressions, so how do we find Nix expressions? There are actually a few different ways to get them.
- Use the nix expressions that are included with your installation of NixOS, which are part of a nix channel. A nix channel allows you to easily download updated expressions as well as pre-compiled binaries.
- Download a set of nix expressions from the internet.
- Subscribe to a new nix channel to download more up-to-date software packages than are available in the default channel.
Let’s explore each one of these in turn. Spoiler alert: we’ll use 1) early on but most of our time will be spent with 2). I’ll touch on 3), but honestly it’s not something I recommend if you’re doing Haskell development because I don’t think it offers much, if anything, over option 2).
Expressions Available through NixOS Installation
Your NixOS installation comes with a set of packages that are included by default. These are installed in /nix/var/nix/profiles/per-user/root/channels/nixos
, but NixOS is kind enough to give you a shortcut via ~/.nix-defexpr/channels_root/nixos
. This is the default location from which nix expressions are run. It also happens to be a nix channel, but I’ll discuss nix channels later. The key point is that NixOS comes with oodles of expressions built-in and ready to be run. In fact, let’s run one right now in preparation for later steps. We’ll install git
. All of the manipulations of the user environment are invoked using the nix-env
command. For example, if we want to see a list of all available packages from the built-in expressions
#!bash
nix-env -qaP --description *
Here, q
means query, a
means available packages, and P
means show the attribute path, which is essentially a way to specify a particular expression instead of using the software package name. The description
option includes the description of the packages, as well. In the case of git, we’ll see this among the long list of available packages:
#!bash
nixos.pkgs.gitAndTools.gitFull git-1.9.4 Git, a popular distributed version control system
The software is named git-1.9.4
, but the attribute path specifying the nix expression used to build it is nixos.pkgs.gitAndTools.gitFull
. We can use either the package name or the attribute path to install git
, but the attribute path is the recommended strategy since it removes all ambiguity about which package should be installed and how.
So let’s install git
using the following command:
#!bash
nix-env -iA nixos.pkgs.gitAndTools.gitFull
Nix will then download all dependencies that you don’t already have (which, since this is our first installation, is all of them) and install git
. Here the flag i
means install and A
means by attribute path (yes, it bothers me that query commands use -P
and install commands use -A
). We can verify the installation worked successfully by querying the list of installed packages in the user environment.
#!bash
nix-env -q --installed
The command above should list git
as being an installed package.
Installing from Downloaded Expressions
Another approach to getting expressions is to download a collection of them from the internet. The most popular collection is the nixpkgs repository on github. In fact, the nix expressions that are bundled with NixOS (and which we explored above) are simply a stable version of this nixpkgs repository.
While the nixpkgs repository has a lot of available Haskell packages already, I quickly ran into ones that I needed but weren’t included. Properly installing these packages requires adding new nix expressions to the available set. This is why I recommend running downloaded nix expressions as the default method of installation for Haskell developers; most likely, you’ll be adding and running your own expressions anyway, and you can pull in updates with git just as easily as you can using nix channels.
Since Nix is declarative, our expressions will be useful for everyone else, too, so why not contribute them back to nixpkgs? For this, we’ll need to fork the nixpkgs repository and then clone our fork to our local machine. Any updates made locally can be made on a branch, pushed back to github, and then pulled into the main nixpkgs repository. FYI: when I cloned the repository, it was about 125 MB in size.
After you fork and clone the repo locally, cd
to its root. We’re going to run pretty much the same nix-env
command we did before, except now we use the -f
flag to read the nix expressions from the directory. Personally, I’m trying to get good at vim, so I’ll be querying for it with the following:
#!bash
nix-env -f . -qaP --description vim
vim vim-7.4.316 The most popular clone of the VI editor
Pretty easy. In this case, I got lucky and was able to guess the package name “vim” correctly, but in general there are better ways to search, as we’ll find later. Installation is also an analogous command.
#!bash
nix-env -f . -iA vim
We’ll investigate how to update this local nixpkgs repository with our own additions in a future post.
Subscribing to a New Channel
The third option for getting expressions is to subscribe to a new channel. I say new channel because all NixOS installations are automatically subscribed to the channel associated with the released OS version. For instance, I can inspect the default channel’s manifest and see that it’s for NixOS version 14.04.
#!bash
cat ~/.nix-defexpr/channels_root/manifest.nix
[ { meta = { }; name = "nixos-14.04.312.b84584f"; .......
Ok, but what is a channel? A channel is a set of nix expressions combined with a manifest file. The manifest file describes what binaries are available for downloading instead of building from scratch, so they can save you a lot of time if you are doing many installations.
You can pull in new updates from a channel using the nix-channel --update
command. The default channel is not really sufficient for development purposes because we want to add and run our own nix expressions. As such, I’m glossing over channels in favor of pulling updates in from the git repository directly. I admit I’m not the most knowledgeable about channels, so if there some best practices out there to make working with them easier, please let me know.
Enhancing the Nix Experience
We have seen how to query and install packages using Nix, but there are some small customizations we can make to make our development lives a bit easier. Instead of using nix-env
and specifying our standard set of flags again and again, we can write some custom bash commands to do most of the typing for us. One problem: how can we search for packages by keyword? The nix-env
command takes a package name as an input, not a general keyword. Thankfully, we have grep
, which can search through the large list of available packages and find keywords and regular expressions for us. The code below defines a new bash command nix-search
that will search through all the expressions in our git repository looking for matches of a particular keyword or regular expression. Searching for the right expression to install will be much easier.
#!bash
nix-search(){ echo "Searching..."; nix-env -f ~/Code/nixpkgs -qaP --description \* | grep -i "$1"; }
All this hard-coding of -f ~/Code/nixpkgs
is a problem waiting to happen, though. A better way is to use the .nix-defexpr
folder to simply change the default location that Nix searches for expressions. In doing so, we won’t even need the -f
flag anymore.
One thing I’d like is to have .nix-defexpr
be a link itself to my ~/Code/nixpkgs
directory. Otherwise, my attribute paths will need some kind of prefix, and since I only want to use expressions under ~/Code/nixpkgs
, using this unnecessary prefix will get a little annoying. Normally, we could simply make .nix-defexpr
a link to the right directory, but it turns out that if .nix-defexpr
is a link, it is deleted when you log in and replaced with the Nix default. So, as a work around, I’m adding code to my .bashrc
file to set up .nix-defexpr
the way I want when I open the terminal.
We also should change the NIX_PATH
environment variable similarly to what I have done below.
#!bash
# In ~/.bashrc
rm -r ~/.nix-defexpr
ln -s /home/dan/Code/nixpkgs ~/.nix-defexpr
# Then export the right env variables:
export NIX_PATH=/home/dan/Code:nixos-config=/etc/nixos/configuration.nix;
nix-search(){ echo "Searching..."; nix-env -qaP --description \* | grep -i "$1"; }
We can make installing easier too by writing a new command to run expressions from our git repo, specified by attribute path.
#!bash
nix-install(){ nix-env -iA $1; }
The first version of this post used the -f
flag exclusively because I didn’t yet know about NIX_PATH
and .nix-defexpr
. Having a nix-install
helper function makes more sense when the -f
flag was being used, but in its current form it doesn’t save any keystrokes. The user may use her discretion when deciding whether or not to use nix-install
.
Put both functions into the .bashrc file in your home directory, then use source ~/.bashrc
to reload the .bashrc
file for the updates to take effect. We can use the new commands like this (might need to scroll right!):
#!bash
[dan@nixos:~]$ nix-search vim
Searching...
bvi bvi-1.3.2 Hex editor with vim style keybindings
qvim qvim-7.4 The most popular clone of the VI editor (Qt GUI fork)
vim vim-7.4.316 The most popular clone of the VI editor
vimWrapper vim-with-vimrc-7.4.316 The most popular clone of the VI editor
vimb vimb-2.4 A Vim-like browser
vimbWrapper vimb-with-plugins-2.4 A Vim-like browser (with plugins: )
vimprobable2 vimprobable2-1.4.2 Vimprobable is a web browser that behaves like the Vimperator plugin available for Mozilla Firefox
vimprobable2Wrapper vimprobable2-with-plugins-1.4.2 Vimprobable is a web browser that behaves like the Vimperator plugin available for Mozilla Firefox (with plugins: )
vimNox vim_configurable-7.4.316 The most popular clone of the VI editor
vimHugeX vim_configurable-7.4.316 The most popular clone of the VI editor
vimHugeXWrapper vim_configurable-with-vimrc-7.4.316 The most popular clone of the VI editor
[dan@nixos:~]$ nix-install vim
replacing old `vim-7.4.316'
installing `vim-7.4.316'
An Aside About Nix Search Paths
I have had to update this post a few times as I learn more about how Nix search paths work. I have tried to keep everything mentioned here correct and consistent, but having everything clearly spelled out is valuable. Here’s my most up-to-date knowledge:
- By default, the
nix-env
command searches the expressions contained in the ~/.nix-defexpr
directory. This directory can have subfolders or symbolic links to any other directories, and Nix will prepend any attributes with the symbolic link name. For instance, if .nix-defexpr
contained a link dans_nixpkgs -> ~/Code/nixpkgs
, then searches using nix-env
will show attributes as dans_nixpkgs.haskellPackages.snap
, for instance.
- By making
.nix-defexpr
a symbolic link itself, you can eliminate the prefixes, but .nix-defexpr
will be reset back to the default the next time you boot the machine. As a work around, I have placed code in my .bashrc
file to set it up the way I want each time a terminal is opened.
- The
.nix-defexpr
default location is what is overridden when using the -f
flag.
- The
NIX_PATH
environment variable is used when resolving <brackets>
in nix expressions. For example, NIX_PATH=/home/dan/Code
will lead to <nixpkgs>
resolving to /home/dan/Code/nixpkgs
. It has no effect on the nix-env
command itself.
nixos-config
must point to the configuration.nix
file or else nixos-rebuild
calls will fail.
Installing Haskell Platform and GHC
We can use our new bash commands to start installing our required Haskell packages. In the past, I have tried to avoid Haskell Platform because, invariably, I would need a different version of one of the packages it offers or I would need a package that isn’t included. Unfortunately, following either of these scenarios has sometimes required that I re-install all my packages from scratch to resolve odd dependency conflicts. But, this is NixOS, and the claim is that NixOS is designed to prevent that kind of baloney from happening. Only time and experience will tell if that’s true, but in the meantime we’ll barge right ahead and get Haskell Platform since it is often suggested as the best way to get started with Haskell.
#!bash
[dan@nixos:~]$ nix-search "haskell platform"
Searching...
<omitted>
haskellPlatform haskell-platform-2013.2.0.0 Haskell Platform meta package
[dan@nixos:~]$ nix-install haskellPlatform
After installation is complete, we will have Haskell Platform 2013.2.0.0 installed, which used ghc 7.6.3 to build. However, we did not yet install ghc because NixOS only puts packages we install on the path, not their dependencies. Installing ghc is now a fast operation because all NixOS is doing is adding the existing executable to the path. At the same time, let’s install cabal.
#!bash
nix-install haskellPackages.ghc
nix-install haskellPackages.cabalInstall
I have received reports from some users that haskellPackages.ghc
doesn’t work for them, but haskellPlatform.ghc
does, so give that a try if the above doesn’t work for you.
That’s it for this installment. Check back soon for the next *exciting *post in this series, although I’m not yet sure what it will be.