Search code examples
functional-programmingnixnixos

Nix Pill 14: How to make callPackage overridable?


I am having trouble understanding Nix Pill 14. The author provides makeOverridable, and then challenges the user to integrate it with callPackage. makeOverridable and default.nix are provided, as follows, where makeOverridable is in the file lib.nix, and callPackage is in the file default.nix:

# file: lib.nix

rec {
  makeOverridable = f: origArgs:
    let
      origRes = f origArgs;
    in
      origRes // { override = newArgs: makeOverridable f (origArgs // newArgs); };
}
# file: default.nix

let
  nixpkgs = import <nixpkgs> {};
  allPkgs = nixpkgs // pkgs;
  callPackage = path: overrides:
    let f = import path;
    in f ((builtins.intersectAttrs (builtins.functionArgs f) allPkgs) // overrides);
  pkgs = with nixpkgs; {
    mkDerivation = import ./autotools.nix nixpkgs;
    hello = callPackage ./hello.nix { };
    graphviz = callPackage ./graphviz.nix { };
    graphvizCore = callPackage ./graphviz.nix { gdSupport = false; };
  };
in pkgs

This is what I have come up with:

# file: default.nix (my implementation)

let
  nixpkgs = import <nixpkgs> {};
  allPkgs = nixpkgs // pkgs;
  callPackage = path: overrides:
    let
      f = import path;
      origRes = f ((builtins.intersectAttrs (builtins.functionArgs f) allPkgs) // overrides);
    in
      origRes // { override = newArgs: callPackage f (overrides // newArgs); };
  pkgs = with nixpkgs; {
    mkDerivation = import ./autotools.nix nixpkgs;
    hello = import ./hello.nix {};
    graphviz = import ./graphviz.nix {};
    graphvizCore = graphviz.override { gdSupport = false; };
  };
in pkgs

I think I have a fundamental misunderstanding of what is going on here. Could you please provide the correct implementation and explain what I am doing wrong?

EDIT: I managed to get it to work, however, it is still not recursive.

# file: default.nix

let
  nixpkgs = import <nixpkgs> {};
  allPkgs = nixpkgs // pkgs;
  callPackage = path: overrides:
  let
    f = import path;
    origArgs = f ((builtins.intersectAttrs (builtins.functionArgs f) allPkgs) // overrides);
    makeOverridable = { override = newArgs: (origArgs // newArgs); };
  in
    origArgs // makeOverridable;
pkgs = with nixpkgs; rec {
  mkDerivation = import ./autotools.nix nixpkgs;
  hello = callPackage ./hello.nix { };
  graphviz = callPackage ./graphviz.nix { };
  graphvizCore = graphviz.override { gdSupport = false; };
};
in pkgs

EDIT 2:

# file: default.nix

let
  nixpkgs = import <nixpkgs> {};
  allPkgs = nixpkgs // pkgs;
  makeOverridable = f: origArgs:
    let origRes = f origArgs;
    in origRes // { override = newArgs: makeOverridable f (origArgs // newArgs); };
  callPackage1 = path: overrides:
    let f = import path;
    in f ((builtins.intersectAttrs (builtins.functionArgs f) allPkgs) // overrides);
  callPackage = makeOverridable callPackage1;
  pkgs = with nixpkgs; {
    mkDerivation = import ./autotools.nix nixpkgs;
    hello = callPackage ./hello.nix { };
    graphviz = callPackage ./graphviz.nix { };
    graphvizCore = graphviz.override { gdSupport = false; };
  };
in pkgs

SOLUTION:

# file: default.nix

let
  nixpkgs = import <nixpkgs> {};
  allPkgs = nixpkgs // pkgs;
  makeOverridable = f: origArgs:
    let origRes = f origArgs;
    in origRes // { override = newArgs: makeOverridable f (origArgs // newArgs); };
  callPackage1 = path: overrides:
    let f = import path;
    in f ((builtins.intersectAttrs (builtins.functionArgs f) allPkgs) // overrides);
  callPackage = path: makeOverridable (callPackage1 path);
  pkgs = with nixpkgs; rec {
    mkDerivation = import ./autotools.nix nixpkgs;
    hello = callPackage ./hello.nix { };
    local_graphviz = callPackage ./graphviz.nix { };
    graphvizCore = local_graphviz.override { gdSupport = false; };
    graphvizCore2 = graphvizCore.override { gdSupport = false; };
  };
in pkgs

Solution

  • If makeOverridable is defined properly using one of the definitions in Nix Pills, you can think of it as a higher-order function: it takes a normal function named f, and it changes it into a new function whose results can be overridden.

    Assuming you already have a function named callPackage1, you can make an overridable version like this:

    rec {
      callPackage1 = ...;
      callPackage = makeOverridable callPackage1;
    }
    

    EDIT 1:

    Actually, callPackage1 needs a path in order to return the type of function that is expected by makeOverridable (a function that takes a set and returns an object to which we can add the override attribute). So let's try this definition of callPackage:

    callPackage = path: makeOverridable (callPackage1 path);