Jezen Thomas

Jezen Thomas

Co-Founder & CTO at Supercede.

Nix Overrides That Expire Themselves

I recently published yesod-form-1.7.10 to Hackage. My project gets all of its dependencies from nixpkgs, and nixpkgs gradually adopts Haskell packages from Hackage. That means that there’s some time between when the package lands in nixpkgs, and when I need to use the package in my project, which is right now.

You can write an override in your flake.nix to get the newer package you need, but what happens when you eventually bump the nixpkgs version and the override is made redundant? There’s nothing by default which warns you that the override should be removed.

A colleague of mine showed me this pattern which makes the override announce its own obsolescence. When Nix evaluates the flake, it compares package versions and warns when the version is greater than or equal to the version in your override.

packageOverrides = prev.lib.composeExtensions prev.haskell.packageOverrides
  (hfinal: hprev: {

    # we require 1.7.10 for runFormPRG; the pinned nixpkgs only has
    # 1.7.9.2.
    yesod-form =
      let
        noOverride =
          prev.lib.versionAtLeast hprev.yesod-form.version "1.7.10";
      in
      prev.lib.warnIf noOverride ''
        yesod-form >= 1.7.10 is now in nixpkgs, the override can be removed.
      ''
        (if noOverride then
          hprev.yesod-form
        else
          prev.haskell.lib.dontCheck (hfinal.callHackageDirect
            {
              pkg = "yesod-form";
              ver = "1.7.10";
              sha256 = "sha256-9TqA7c2djVaLvrj/rj47LiJ7D1rLbrGhi585FvD9zRE=";
            }
            { }));
  });

In this example, hprev.yesod-form.version is whatever the pinned nixpkgs provides. The lib.versionAtLeast function compares version strings, and lib.warnIf is the part that prints a message during evaluation when the condition is true.

So, when it’s time to remove the override, you’ll see something like this:

evaluation warning: yesod-form >= 1.7.10 is now in nixpkgs, the override can be removed.

This approach isn’t specific to package versions. You can do this anywhere you have an override where the justification for the override can be expressed conditionally. For example, we also use this to clean up overrides where a package was marked as broken in nixpkgs, and is subsequently un-marked.

markUnbrokenWithWarning = p: nixpkgs.lib.warnIfNot p.meta.broken ''
  Package ${p.meta.name} is no longer broken.
  The corresponding override can be removed.
''
  (markUnbroken p);

When someone eventually fixes the package upstream and the broken flag is removed, the warning tells us to delete the workaround.