Nix is the best way to develop and build Haskell projects.
Creating a new project
Initialise a new Cabal project by running this command:
$ nix shell nixpkgs\#{cabal-install,ghc} --command cabal init --interactive
Then create the following Nix flake:
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
nix-filter.url = "github:numtide/nix-filter";
};
outputs = inputs:
with builtins;
let
pname = ""; # EDIT THIS
inherit (inputs.nixpkgs) lib;
foreach = xs: f: with lib; foldr recursiveUpdate { } (
if isList xs then map f xs
else if isAttrs xs then mapAttrsToList f xs
else throw "foreach: expected list or attrset but got ${typeOf xs}"
);
hsSrc = root: inputs.nix-filter {
inherit root;
include = with inputs.nix-filter.lib; [
(matchExt "cabal")
(matchExt "hs")
(matchExt "md")
isDirectory
];
};
overlay = final: prev: {
haskell = prev.haskell // {
packageOverrides = lib.composeExtensions prev.haskell.packageOverrides (hfinal: hprev:
with prev.haskell.lib.compose;
{
"${pname}" = hfinal.callCabal2nix pname (hsSrc ./.) { };
}
);
};
};
in
foreach inputs.nixpkgs.legacyPackages
(system: pkgs':
let
pkgs = pkgs'.extend overlay;
ghcs = [ "ghc92" "ghc94" "ghc96" ];
hps = lib.filterAttrs (ghc: _: elem ghc ghcs) pkgs.haskell.packages;
in
{
formatter.${system} = pkgs.nixpkgs-fmt;
legacyPackages.${system} = pkgs;
packages.${system}.default = pkgs.haskellPackages.${pname};
checks.${system}.${pname} = pkgs.buildEnv {
name = pname;
paths = map (hp: hp.${pname}) (attrValues hps);
};
devShells.${system} =
foreach (hps // { default = pkgs.haskellPackages; }) (ghcName: hp: {
${ghcName} = hp.shellFor {
packages = ps: [ ps.${pname} ];
nativeBuildInputs = with hp; [
cabal-install
fourmolu
haskell-language-server
];
};
});
}
) // {
overlays.default = overlay;
};
Now you’re probably thinking:
Whoa, that’s a big flake!
It is, but it has a number of nice features:
-
support for all Nixpkgs-provided systems (as defined in
nixpkgs.legacyPackages
) - support for multiple GHC versions 1
- a devshell that actually works out of the box
-
a flake check for easy CI testing with
nix flake check
- an overlay for easy flake composability
Basic operations
-
add Haskell modules and dependencies to the generated Cabal file
-
if using Direnv, make sure to
direnv reload
after changing the Cabal fille to update the devshell
-
if using Direnv, make sure to
-
build and run the project locally with
cabal build
andcabal run
-
1
To support all major GHC versions automatically:
ghcs = filter (x: match "ghc[0-9]{2}" x != null) (attrNames pkgs.haskell.packages);