Cabal Flags With and Without Nix Hakyll build environment with watcher and preview server using nix
Yak shaving for every day. I wanted to write post about something completely different but I needed to shave a beast and figured I can as well share “a quick how to” in hope I might safe someone else’s time.
I’ve been using NixOS exclusively on basically all of my machines.
I also like to abuse nix for all sort of stuff - for creating project environments using
nix-shell, deployment etc. Anyway as it is pretty common at least in my case, the software
gods were pretty angry with me again. Even though I have this site “nixified” for over a year
it turned out Hakyll in nixpkgs is not built with previewServer
and watchServer
which I would
pretty much like to use.
Discussion under issue 442 in cabal2nix shed a bit more light onto the issues described within this post. The issue with
- There are good reasons why
doJailbreak
ignores conditional dependencies. See this comment for more informations. - The reason why Hakyll in nixpkgs is not built with default
previewServer
andwatchServer
is due to Cabal Install. Nixpkgs set doesn’t contain compatible version ofwarp
package soHakyll
can’t be built with those flags. In situations like this Cabal simply silently ignores the flag. This behaviour seems at best odd to me. There is currently some dicussion about making flags explicit. Change like this might make maintenance of nixpkgs a bit more difficult so we will see how this goes.
Cabal Flags
Lets start by familiarizing ourselves with Cabal Flags first.
Cabal allows user to define custom build configuration flags within cabal file.
Declaration of custom flags is usually in top section of Cabal file. Flag has to be Bool
value
and can be both True
or False
by default.
Looking into hakyll.cabal we can see two flags watchServer
and previewServer
.
Interestingly enough, both are set to True
by default. This is can configure
to overwrite default flags:
$ cabal configure -f watchServer -f previewServer
Frankly it was more difficult for me to found this information than I think it should have been. It’s hard to point a finger to why so. Maybe it’s hard to navigate cabal documentation, maybe the error message Hakyll logs when it’s missing a flag is not helpful enough or maybe it was purely my own stupidity. Anyway Hackage lists available flags on package description page including a link to relevant part of documentation of cabal so it’s not like folks didn’t try to be helpful.
Haskell Infrastructure for Nix
Like Stack, nixpkgs provide curated Haskell package sets but with both flexibility and user experience (meaning complexity) of Nix. Cabal2Nix is a standard tool for generating Nix expressions for Cabal projects.
$ cabal2nix . > site.nix
If you don’t want to pre-generate nix expressions you can as well use
haskellPackages.cabal2nix "site" ./.
as a part of build rather than generating it upfront.
There are two gotchas though. First Hakyll within nix packages is build with watchServer
and previewServer
disabled so these features are not available, second the standard jailbreak
1 seems not to be compatible with cabal
flags.
Overriding Hakyll
To fix these two problems we’re going to override the definition of Hakyll.
For general structure of default.nix
file I’m roughly following Gabriel’s haskell-nix
way which looks like this:
let
config = {
packageOverrides = pkgs: {
haskellPackages = pkgs.haskellPackages.override {
overrides = haskellPackagesNew: haskellPackagesOld: {
site = haskellPackagesNew.callPackage ./site.nix {};
};
};
};
};
pkgs = import <nixpkgs> { inherit config; };
in pkgs.haskellPackages.site;
This will extend default package set with my custom site
package.
Next thing we need to do is to add overrides for Hakyll. We’re going to use
version already available to us in haskellPackages and just apply desired changes.
For start we need to configure projects with watchServer
and previewServer
flags:
#...
{
haskellPackages = pkgs.haskellPackages.override overrides = haskellPackagesNew: haskellPackagesOld: {
hakyll = haskellPackagesOld.hakyll.overrideAttrs(old: {
configureFlags = "-f watchServer -f previewServer"; # pass configure flags
jailbreak = true; # jailbreak dependecies
});
site = haskellPackagesNew.callPackage ./site.nix {};
};
};
#...
This is as far as I know not documented but it’s easy enough to reverse engineer from the generir-builder.nix.
At this stage build fails for me though because of unavailable version of warp.
I assume this is because jailbreak
doesn’t work correctly with flag directive.
To solve this problem I’m going to define custom patch for Hakyll. I usually use git to produce patches.
- git clone the repository
- do desired changes to source locally
- run
git diff > file.patch
to produce patch file
Patch file I’m using.
The simplest way to apply custom patches in nix expression is to use patches
attribute as follows:
#...
{
haskellPackages = pkgs.haskellPackages.override overrides = haskellPackagesNew: haskellPackagesOld: {
hakyll = haskellPackagesOld.hakyll.overrideAttrs(old: {
configureFlags = "-f watchServer -f previewServer";
patches = [./hakyll.patch]; # applying our custom patch
});
site = haskellPackagesNew.callPackage ./site.nix {};
};
};
#...
Now we have build for our site
binary using the customized version of Hakyll
with support for watch
command.
Conclusion
More than anything this demonstrates how frustrating it sometimes can be to use software. I wanted to blog about something but ended up fixing build and then writing this tutorial instead. A big part of using software is about putting through with things like this. There is always some trade-off with tools and approaches we use. For me personally it’s important above all to be able to go and fix issues myself without going completely crazy in the process. Both Haskell and Nix are good tools for this. On the other hand neither of them is the most effective way of avoiding problems like in first place.
I’m not going to rant about the state of the software. Neither I’m going to praise tools I use. I’m writing this post with two goals:
- As a mostly copy paste tutorial for folks who might run into similar issues.
- As an exercise for reader to see both power and cost of getting things done with tools like these.
Whole code can be found on GitHub
jailbreak
attribute ordoJailbreak
function are used to remove version constrains from cabal file so package can be build with other version of dependencies present in used haskellPackages set.↩︎