- Installing VirtualBox (for NixOS)
- Installing NixOS
- Installing Essential Software In NixOS
- Setting Up Vim (for Haskell)
- Adding Nix Expressions for New Packages
- Setting Up a Haskell Project on NixOS
Previously, we looked at how to add new packages to our nixpkgs
expression tree. Often, the reason we are adding new packages is that we want to use them for Haskell development! This installment will explore starting a new Haskell project.
Again, we’re going to be using some custom nix
commands that we set up in previous posts of this series, so if you’re jumping in here, you might want to back up a few installments if you find yourself getting lost.
I’m going to be using the janrain snap tutorial as the basis for this installment, but much of this exploration is getting set up to do the tutorial, so there is not much overlap.
Set Up Project Files
First, let’s set up the basic project files we’re going to be using for our exploration.
Basic Snap Setup
Run the following commands to set up a new projectomatic
directory and initialize it using cabal init
and snap init
. For the cabal init
call, you can accept all the defaults if you want; most of the files generated with that call will be overwritten by snap init
anyway. When asked, you want to choose 2) Executable
.
#!bash
mkdir projectomatic
cd projectomatic
cabal init
snap init
If you encounter a snap: command not found
message, then you need to install snap
into your user environment. If you already have the package, installing it will just make it active for the user environment so you can call snap init
. Otherwise, installing will both build it and make it active.
#!bash
nix-install haskellPackages.snap
The project directory will be initialized with a projectomatic.cabal
file, among others. Feel free to modify the values set by default in the cabal
file. I’m going to leave the defaults as is for this tutorial.
Sanity Check: In your
projectomatic
directory, you should now haveprojectomatic.cabal
,Setup.hs
, and the directorieslog
,snaplets
,src
, andstatic
.
Generate default.nix
Just like we used the cabal2nix
utility to automatically generate nix expression for hackage packages, we can also use cabal2nix
to generate expressions for our new projectomatic
project.
#!bash
cabal2nix projectomatic.cabal --sha256 blah > default.nix
Check out the new default.nix
and you’ll see a nice, clean expression for the new projectomatic package. We want to change the sha256 = "blah"
to src = ./.
, but other than that, the expression is similar to the ones we saw previously.
We can’t run it just yet, though. This expression is generated so that it can be integrated into the haskellPackages
environment of nixpkgs
. Since our project will be distinct from haskellPackages
, we need to do some additional customization.
The first issue is with the expression inputs:
#!nix
{ cabal, heist, lens, MonadCatchIOTransformers, mtl, snap, snapCore
, snapLoaderStatic, snapServer, text, time, xmlhtml
}:
This looks just like the previous nix expressions we encountered, so what’s the problem? When we incorporated new nix expressions into the nixpkgs
expression tree, expressions for dependent Haskell packages were defined in the same haskellPackages
scope. Thus, when Nix needed the input cabal
expression in order to put the cabal
dependency on the path, it could do it because the cabal
expression was in the same scope.
On the other hand, here we are defining a nix expression outside of that scope, so Nix doesn’t know what the cabal
dependency means. In fact, if we try to run nix-build
for projectomatic
, we’ll see this:
#!bash
[dan@nixos:~/Code/projectomatic]$ nix-build
error: cannot auto-call a function that has an argument without a default value (`cabal')
To fix this problem, we need to specify in our default.nix
expression where the dependent expressions are defined. We’ll do this by redefining the expression to take a single haskellPackages
input argument that has a default value defined as (import <nixpkgs> {}).haskellPackages)
, which is the scope we want!
#!nix
{ haskellPackages ? (import <nixpkgs> {}).haskellPackages }:
let
inherit (haskellPackages) cabal heist lens MonadCatchIOTransformers mtl snap snapCore
snapLoaderStatic snapServer text teim xmlhtml
;
in cabal.mkDerivation (self: {
The term <nixpkgs>
instructs Nix to look for expressions beginning with $NIX_PATH/nixpkgs
. Since my NIX_PATH
has only the prefix that I set in my .bashrc
file, the resolved file path is my desired local directory /home/dan/Code/nixpkgs
.
#!bash
[dan@nixos:~/Code/projectomatic]$ echo $NIX_PATH
/home/dan/Code:nixos-config=/etc/nixos/configuration.nix
Using nix-shell
Now we have all the files we need for a complete (but minimal) snap
default project. A lot of our effort was spent tweaking the new default.nix
to work with our new project. Surely there must be some way to check that it’s correct.
A Quick Reminder about Expressions
Remember the purpose of nix expressions.
- Define all system inputs to a package
- Define the build steps for the package
When a nix expression is run, Nix configures something of a private system for building the package. The private system is configured to only have the dependencies of the nix expression. If a required dependency is left out of the expression inputs, the package will fail to build.
nix-shell
Command
The nix-shell
command acts as a way to inject a CLI into this private system. Within the nix-shell
, the user can build expressions and run commands as if they are being run as part of an expression. Let’s try it out
#!bash
[dan@nixos:~/Code/projectomatic]$ nix-shell
error: Package ‘projectomatic-0.1’ in ‘/home/dan/Code/projectomatic/default.nix:18’ has an unfree license, refusing to evaluate. You can set
{ nixpkgs.config.allowUnfree = true; }
in configuration.nix to override this. If you use Nix standalone, you can add
{ allowUnfree = true; }
to ~/.nixpkgs/config.nix.
Whoa, what happened here? By default, Nix won’t evaluate expressions that have an unfree license, which, if you accepted all the defaults like I did, is what we have for our project. By following the instructions given in the error, we can allow unfree packages to be built. However, this will allow unfree packages everywhere, and we just want to build this one unfree package.
An unmentioned way to enable unfree packages is the NIXPKGS_ALLOW_UNFREE
environment variable. We can temporarily enable unfree packages, build the one we want, and then disable it again to return us back to our starting point. I added this to my .bashrc
file (along with nix-install
and nix-search
) to make it easy to drop into an unfree nix-shell
.
#!bash
nix-shell-unfree() {
FLAGSAVE=$NIXPKGS_ALLOW_UNFREE;
echo "Opening shell with NIXPKGS_ALLOW_UNFREE=1. Original setting of $FLAGSAVE will restored on exit.";
export NIXPKGS_ALLOW_UNFREE=1;
nix-shell;
echo "Restoring NIXPKGS_ALLOW_UNFREE=$FLAGSAVE ...";
export NIXPKGS_ALLOW_UNFREE=$FLAGSAVE;
}
Now our nix-shell-unfree
command will put us in a nix shell.
#!bash
[dan@nixos:~/Code/projectomatic]$ nix-shell-unfree
Opening shell with NIXPKGS_ALLOW_UNFREE=1. Original setting of 0 will restored on exit
[nix-shell:~/Code/projectomatic]$ exit
exit
Restoring NIXPKGS_ALLOW_UNFREE=0 ...
Try entering a nix-shell-unfree
and running nix-build
. Did the build work without error? Depending on your dependency versions, maybe it did. For me, I get the following:
#!bash
Setup: At least the following dependencies are missing:
lens >=3.7.6 && <4.2
Looks like I still have some work to do. At first, I started setting up a new local expression for a different lens version, but after getting it set up, I found that snap
requires lens 4.2
, even though the cabal file written by by snap init
requires less than 4.2
! I am chalking this up to a bug and manually edited the projectomatic.cabal
file to use lens <= 4.2
. After that, nix-build
works without issue. Note that I also tried keeping the cabal
file unmodified and using jailbreak=true
, but that had no effect as far as I could tell.
Running the Default Application
We have successfully overcome several obstacles in getting the default, (mostly) unmodified snap
project template to compile. Now let’s run it.
When using nix-build
, Nix puts a sym link into your project directory called result
that points to the location in the store that holds the build results of the expression. We can use result
to get easy access to our new binary.
TADA! We are now ready to actually follow the janrain tutorial. This installment is long enough already, so I’ll save exploring the tutorial itself for next time.
Hi,
Thanks for writing this up; helped me understand customizing nix function arguments. There is a small typo in the example:
import < nixpkgs> {}).haskellPackages
should beimport <nixpkgs> {}).haskellPackages
. (no space before nixpkgs).Great, I will make the correction. Glad it helped.