Minimalistic Nix/Haskell Project Structure

Posted on September 4, 2018 under tag(s) Haskell, Nix

Here’s a minimalistic setup to reproducibly build cabal-based haskell projects. This setup uses a pinned nixpkgs version and accomodates dependencies between cabal folders.

Setup

The default.nix file contains all the necessary nix code for two packages called foo and bar. It pins the nixpkgs version:

{
  hostPkgs ? import <nixpkgs> {},
  pinnedVersion ?  hostPkgs.lib.importJSON ./nixpkgs-version.json
}:
let
  pkgs =
    let
      pinnedPkgs = hostPkgs.fetchFromGitHub {
        owner = "NixOS";
        repo = "nixpkgs-channels";
        inherit (pinnedVersion) rev sha256;
      };
    in import pinnedPkgs {};

  filHerHdevTools = # this is used to filter out leftover hdevtool sockets.
    builtins.filterSource (path: type: (baseNameOf path != ".hdevtools.sock")));

  in pkgs // rec {
    haskellPackages = pkgs.haskellPackages.override {
      overrides = self: super: rec {
      foo  = self.callCabal2nix "foo" (filterHdevTools ./foo) {};
      bar  = self.callCabal2nix "bar" (filterHdevTools ./bar) {inherit foo;};
      };
    };
    env-before = haskellPackages.shellFor {
      packages = p: with p; [foo bar];
      withHoogle = true;
      buildInputs = with haskellPackages; with pkgs; [ cabal-install ghcid
        hlint sysstat hdevtools];
    };
    env-after = pkgs.stdenv.mkDerivation rec {
      name = "env";
      env = pkgs.buildEnv { name = name; paths = buildInputs; };
      buildInputs = [ (haskellPackages.ghcWithPackages
        (pkgs: [pkgs.foo pkgs.bar])) ];
    };
  }

The ./foo/ and ./bar/ folders should contain a single cabal file named foo.cabal and bar.cabal.

Usage

The pinning can be updated via:

nix-prefetch-git https://github.com/nixos/nixpkgs-channels.git \
  refs/heads/nixos-18.03 > nixpkgs-version.json

Packages can be built using:

nix-build -A haskellPackages.foo

The env-before package exposes a massive dev environment that can be used to use cabal-build on foo and bar. It also provides various tools such as Hoogle documentation for their dependencies, Hdevtools, the cabal binaries, ghcid and hlint:

nix-shell -A env-before

The env-after package allows to bring both foo and bar in ghc’s scope.

nix-shell -A env-after

Extras

nix-build won’t build a directory that contains hdevtools socket files. A simple way to circumvent this limitation is filtering them using bulitins.filterSource. The orginal derivation then becomes:

{
  hostPkgs ? import <nixpkgs> {},
  pinnedVersion ?  hostPkgs.lib.importJSON ./nixpkgs-version.json
}:
let
  pkgs =
    let
      pinnedPkgs = hostPkgs.fetchFromGitHub {
        owner = "NixOS";
        repo = "nixpkgs-channels";
        inherit (pinnedVersion) rev sha256;
      };
    in import pinnedPkgs {};

  filterHdevTools = builtins.filterSource
      (path: type: baseNameOf path != ".hdevtools.sock");

  in pkgs // rec {
    haskellPackages = pkgs.haskellPackages.override {
      overrides = self: super: rec {
      foo = self.callCabal2nix "foo" (filterHdevTools ./foo) {};
      bar = self.callCabal2nix "bar" (filterHdevTools ./bar) {inherit foo;};
      };
    };
    env-before = haskellPackages.shellFor {
      packages = p: with p; [foo bar];
      withHoogle = true;
      buildInputs = with haskellPackages; with pkgs; [ cabal-install ghcid
        hlint sysstat hdevtools];
    };
    env-after = pkgs.stdenv.mkDerivation rec {
      name = "env";
      env = pkgs.buildEnv { name = name; paths = buildInputs; };
      buildInputs = [ (haskellPackages.ghcWithPackages
        (pkgs: [pkgs.foo pkgs.bar])) ];
    };
  }

Back