Nix Overlays: Add attribute to “lib” and avoid “infinite recursion error”

Published by Philipp Schuster on

Today, I was about to add an attribute to the lib attribute of nixpkgs using a nixpkgs overlay. I thought that this is as straight forward as adding regular packages directly in an overlay. This wasn’t my first time using overlays, but my first time adding something to lib. There was a tiny but ugly pitfalls, that made me create this short blog post.

Let’s look at the following code foo.nix:

# This script can be invoked like this: $ nix-build ./foo.nix
let
  # Overlay that adds functionality to "pkgs.lib".
  overlay = (self: super:
    let
      pkgs = super.pkgs;
      lib = super.lib;
      toPretty = lib.generators.toPretty;
      # Function that takes a value to trace and an expression to return. The
      # semantics is the same as for builtins.trace, but the traced value is
      # prettified before being printed.
      tracePretty = val: ret: (builtins.trace (toPretty { } val) ret);
      # Wraper around tracePretty that traces the given value and returns it.
      tracePrettyVal = val: tracePretty val val;
    in
    {
      # Must be super.lib and not super.pkgs.lib, otherwise: infinite recursion
      lib = lib // {
      	inherit tracePretty tracePrettyVal;
      };
    }
  );
  # pkgs with the applied overlay.
  pkgs = import <nixpkgs> { overlays = [ overlay ]; };
in
# Usage of the overlayed attribute.
pkgs.lib.tracePrettyVal { arr = [ 1 2 3 ]; }

With

overlay = (self: super:
    let
      pkgs = super.pkgs;
      # Attention: super.lib, not super.pkgs.lib!
      lib = super.lib;
      toPretty = lib.generators.toPretty;
      # Function that takes a value to trace and an expression to return. The
      # semantics is the same as for builtins.trace, but the traced value is
      # prettified before being printed.
      tracePretty = val: ret: (builtins.trace (toPretty { } val) ret);
      # Wraper around tracePretty that traces the given value and returns it.
      tracePrettyVal = val: tracePretty val val;
    in
    {
      # Must be super.lib and not super.pkgs.lib, otherwise: infinite recursion.
      # We must merge that with the original lib module, as overlays do not 
      # perform deep merges.
      lib = lib // {
      	inherit tracePretty tracePrettyVal;
      };
    }
  );

I define a basic overlay that adds the functions tracePretty and tracePrettyVal to a lib attribute that is merged with super.lib. We must be very careful that we merge it with super.lib but not super.pkgs.lib. As I occasionally write Nix files like this:

{ pkgs ? import <nixpkgs> {}
, lib ? pkgs.lib
}: ...

I was tricked to think that I have to use super.pkgs.lib. However, actually, super refers to what pkgs is in the example above and super.pkgs.lib in my overlay code refers to pkgs.pkgs.lib in the example above. So, be careful that you merge your library functions with super.lib!

The rest of the code in the beginning of the blog post is just necessary clutter to use a Nix overlay. So, as a summary: Write an overlay that adds something to lib like this:

(self: super: {
  lib = super.lib // {
    foo = "bar";
  };
})

Otherwise, when you accidentally use super.pkgs.lib, you end up with an ugly “infinite recursion error”.

TIL

Today I learned that pkgs = import <nixpkgs> {} also has a pkgs.pkgs sub-attribute, which is a reference to the top-level pkgs. So you can use pkgs.pkgs.pkgs..... I use Nix for a few months now, but I never encountered this. However, the problem above shows that overlays should always add stuff to the top layer.


Philipp Schuster

Hi, I'm Philipp and interested in Computer Science. I especially like low level development, making ugly things nice, and de-mystify "low level magic".

0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *