Hakyll Pt. 6 – Pure Builds With Nix
Tags: haskell, functional, staticsitegenerator
Published: 2020-04-30 12:57:12

---
title: "Hakyll Pt. 6 – Pure Builds With Nix"
published: true
description: "Learn how to make your hakyll project build pure and how to patch hakyll if you need to."
tags: haskell, fp, static site generator
---

_Special thanks to [Utku Demir](https://github.com/utdemir/) for review and constant inspiration._

_This post was originally published at https://robertwpearce.com/hakyll-pt-6-pure-builds-with-nix.html._

This is part 6 of a multipart series where we will look at getting a website / blog set up with [hakyll](https://jaspervdj.be/hakyll) and customized a fair bit.

* [Pt. 1 – Setup & Initial Customization](https://robertwpearce.com/hakyll-pt-1-setup-and-initial-customization.html)
* [Pt. 2 – Generating a Sitemap XML File](https://robertwpearce.com/hakyll-pt-2-generating-a-sitemap-xml-file.html)
* [Pt. 3 – Generating RSS and Atom XML Feeds](https://robertwpearce.com/hakyll-pt-3-generating-rss-and-atom-xml-feeds.html)
* [Pt. 4 – Copying Static Files For Your Build](https://robertwpearce.com/hakyll-pt-4-copying-static-files-for-your-build.html)
* [Pt. 5 – Generating Custom Post Filenames From a Title Slug](https://robertwpearce.com/hakyll-pt-5-generating-custom-post-filenames-from-a-title-slug.html)
* Pt. 6 – Pure Builds With Nix
* _(wip) Pt. 7 – Customizing Markdown Compiler Options_

## Overview
In this post we're going to create a new hakyll site from scratch with a caveat: we will do just about everything with [nix](https://nixos.org) in order to guarantee reproducibility for anyone (or anything) using our project. There are also two bonuses that we will inherit simply because we are using nix:
* we will not need to rely on global package installs (apart from nix, of course)
* we will be able to easily patch any package problems; for example, if some of hakyll's dependencies are not available in nixpkgs, we can patch hakyll to get it to work.

Here is the example repository with what we're going to make: https://github.com/rpearce/hakyll-nix-example

_Note: this post assumes that you have installed nix on your system._

## Steps to Build Our Hakyll Project With Nix
Make a new project with `release.nix`, `default.nix`, and `shell.nix`, and get into its pure nix shell environment:

```bash
λ mkdir hakyll-nix-example && cd $_
λ echo "{ }: let in { }" > release.nix
λ echo "(import ./release.nix { }).project" > default.nix
λ echo "(import ./release.nix { }).shell" > shell.nix
λ nix-shell --pure -p niv nix cacert
```

We won't have to touch `default.nix` nor `shell.nix` again, for we are delegating their responsibilities to the `release.nix` file that we'll add more to in a moment.

_Note: we require `nix` and `cacert` when running a pure `nix-shell` with `niv` because of this issue: https://github.com/nmattia/niv/issues/222._

Now that we're in the nix shell, initialize [`niv`](https://github.com/nmattia/niv) and specify your nixpkgs owner, repository, and branch to be whatever you want:

```bash
[nix-shell:~/projects/hakyll-nix-example]$ niv init
[nix-shell:~/projects/hakyll-nix-example]$ niv update nixpkgs -o NixOS -r nixpkgs-channels -b nixpkgs-unstable
[nix-shell:~/projects/hakyll-nix-example]$ exit
```

Update your `release.nix` file with the following:

```nix
let
  sources = import ./nix/sources.nix;
in
{ compiler ? "ghc883"
, pkgs ? import sources.nixpkgs { }
}:

let
  inherit (pkgs.lib.trivial) flip pipe;
  inherit (pkgs.haskell.lib) appendPatch appendConfigureFlags;

  haskellPackages = pkgs.haskell.packages.${compiler}.override {
    overrides = hpNew: hpOld: {
      hakyll =
        pipe
           hpOld.hakyll
           [ (flip appendPatch ./hakyll.patch)
             (flip appendConfigureFlags [ "-f" "watchServer" "-f" "previewServer" ])
           ];

      hakyll-nix-example = hpNew.callCabal2nix "hakyll-nix-example" ./. { };

      niv = import sources.niv { };
    };
  };

  project = haskellPackages.hakyll-nix-example;
in
{
  project = project;

  shell = haskellPackages.shellFor {
    packages = p: with p; [
      project
    ];
    buildInputs = with haskellPackages; [
      ghcid
      hlint       # or ormolu
      niv
      pkgs.cacert # needed for niv
      pkgs.nix    # needed for niv
    ];
    withHoogle = true;
  };
}
```

Don't worry, we'll circle back to what we just did.

Create a `hakyll.patch` diff file:

```bash
λ touch hakyll.patch
```

Bootstrap the hakyll project (we won't ever need this again):

```bash
λ nix-shell --pure -p haskellPackages.hakyll --run "hakyll-init ."
```

Build the project and `--show-trace` just in case something goes wrong:

```bash
λ nix-build --show-trace
```

Run the local dev server:

```bash
λ ./result/bin/site watch
```

Navigate to http://localhost:8000 and see your local dev site up and running!

## Understanding the `release.nix` File
Let's break down what we copied and pasted into `release.nix`.

```nix
let
  sources = import ./nix/sources.nix;
in
{ compiler ? "ghc883"
, pkgs ? import sources.nixpkgs { }
}:

# ...
```

The `let` gives us the space to define an attribute (variable), and it is here that we import our `sources.nix` file that was generated by `niv`. The `in` block defines a function parameter with two attributes, `compiler` and `sources`, that each have defaults (when there's a `:`, that means what comes next is a function body or another function argument). For the `compiler`, we will use this version to compile all of the Haskell packages that we interact with. For the `pkgs`, we default to using our pinned version of `nixpkgs`, but this is overridable.

```nix
# ...

let
  inherit (pkgs.lib.trivial) flip pipe;
  inherit (pkgs.haskell.lib) appendPatch appendConfigureFlags;

  # ...
```

Our new `let` falls within the function we created above, and we then state that we would like to inherit some nice functions from [`pkgs.lib.trivial`](https://github.com/NixOS/nixpkgs/blob/master/lib/trivial.nix) and [`pkgs.haskell.lib`](https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/haskell-modules/lib.nix).  The `flip` and `pipe` functions are standards in functional programming, but I'll share a short recap:
* [`flip`](https://github.com/NixOS/nixpkgs/blob/3a1e8bdcd9a9e13ccd3f298bf598acc8b35caa4d/lib/trivial.nix#L130-L138) takes a function `a -> b -> c` and flips the accepted arguments to act like `b -> a -> c`. Its definition is `flip = f: a: b: f b a;` – it takes a function, then `a`, then `b`, and then it applies `a` and `b` in reversed (flipped) order.
* [`pipe`](https://github.com/NixOS/nixpkgs/blob/3a1e8bdcd9a9e13ccd3f298bf598acc8b35caa4d/lib/trivial.nix#L32-L63) establishes a set of functions that you can apply data to, one after the other. Think of bash pipes: `cat blog_post.txt | grep nix`.

```nix
let
  # ...

  haskellPackages = pkgs.haskell.packages.${compiler}.override {
    overrides = hpNew: hpOld: {
      hakyll =
        pipe
           hpOld.hakyll
           [ (flip appendPatch ./hakyll.patch)
             (flip appendConfigureFlags [ "-f" "watchServer" "-f" "previewServer" ])
           ];

      hakyll-nix-example = hpNew.callCabal2nix "hakyll-nix-example" ./. { };

      niv = import sources.niv { };
    };
  };

  # ...
```

This uses our pinned (or overridden) `nixpkgs` to create our own `haskellPackages` for a specific Haskell `compiler` version.

For `hakyll`, we need to make sure it gets compiled with the `watchServer` and `previewServer` flags, or we won't be able to use its local dev server. We also provide an optional patch file (`git diff > hakyll.patch` file) that we can build `hakyll` with if there are any changes to the project that we need to make.  Patch files can be empty when no patches are required, but if you do need to patch something, here is an example `hakyll.patch` file:

```diff
diff --git a/hakyll.cabal b/hakyll.cabal
index fcded8d..9746f20 100644
--- a/hakyll.cabal
+++ b/hakyll.cabal
@@ -199,7 +199,7 @@ Library
   If flag(previewServer)
     Build-depends:
       wai             >= 3.2   && < 3.3,
-      warp            >= 3.2   && < 3.3,
+      warp,
       wai-app-static  >= 3.1   && < 3.2,
       http-types      >= 0.9   && < 0.13,
       fsnotify        >= 0.2   && < 0.4
```

The `hakyll-nix-example` attribute is specifically for our Haskell project in order for us to be sure our project is compiled with our desired compiler version. We leverage the `callCabal2nix` tool to handle automatically converting our `hakyll-nix-example.cabal` file into a `nix` derivation for our build.

Lastly, we ensure that the `niv` we are using in the `nix-shell` is our pinned `niv` that `niv` itself generated.

```nix
let
  # ...

  project = haskellPackages.hakyll-nix-example;
in
{
  project = project;

  # ...
```

The `project` attribute is what our `default.nix` will use when being called with tools like `nix-build`. All we do is access our `hakyll-nix-example` attribute from our customized `haskellPackages`.

```nix
let
  # ...
in {
  # ...

  shell = haskellPackages.shellFor {
    packages = p: with p; [
      project
    ];
    buildInputs = with haskellPackages; [
      ghcid
      hlint       # or ormolu
      niv
      pkgs.cacert # needed for niv
      pkgs.nix    # needed for niv
    ];
    withHoogle = true;
  };
}
```

Exactly like `default.nix` uses the `project` attribute, `shell.nix` is looking for a `shell` attribute to define everything it needs when running `nix-shell --pure`. We use `shellFor`, which comes with the `nixpkgs` Haskell tools, and we provide it a few attributes:
* the `packages` attribute holds our `project` package and any other `nixpkgs` that you would like to have built when entering the shell
* the `buildInputs` attribute holds all the tools that we'll have available to us while we're in the shell; for example, you can run `ghcid` and load your Haskell code to test it out or run `hlint` to lint your Haskell files
* `withHoogle` gives us the ability to query https://hoogle.haskell.org

## Wrapping Up
Using nix to build our project helps make development consistent and predictable; however, learning nix is not necessarily a breeze. The following articles directly contributed to my understanding that led to this post:
* https://nixos.org/nixos/nix-pills/index.html
* https://github.com/Gabriel439/haskell-nix/tree/master/project4
* https://turbomack.github.io/posts/2020-02-17-cabal-flags-and-nix.html (this
  article is what led me to learn how to patch hakyll)
* `overrideAttrs`: https://nixos.org/nixpkgs/manual/#sec-pkg-overrideAttrs
* `overlays`: https://nixos.org/nixpkgs/manual/#chap-overlays
* https://maybevoid.com/posts/2019-01-27-getting-started-haskell-nix.html
* https://kuznero.com/post/linux/haskell-project-structure-in-nixos/
* https://github.com/NixOS/nixpkgs/blob/master/doc/languages-frameworks/haskell.section.md
* https://nixos.org/nixpkgs/manual/#haskell

Next up:
* _(wip) Pt. 7 – Customizing Markdown Compiler Options_

* * *

Thank you for reading!
<br />
Robert