Customized nixpkgs to override the inputs of other flakes

Context

Recently, I wrote a few flakes myself (on a non-NixOS system) using flake-parts that define some devshells providing Python environments for my other projects. nixpkgs does not provide a few specific Python versions one of my legacy projects needs and I started to use nixpkgs-python for these older versions of python. The setup works as I intended.
However, when I tried to add a few more different versions of Python to the setup, I soon realized that because nix is doing input-addressed storage, a lot of the Python packages need to be rebuilt and re-stored since they depend on different versions of the interpreter, but the content of the packages are the same. Even though there is not a lot of rebuilding to happen, I want to take the chance and take a look into the content-addressing experimental feature of nix.

However, nixpkgs-python does not provide the python modules itself, it copies over the Python packages from its input nixpkgs, and to enable content addressing I need to set __contentAddressed = true on the Python packages of the input nixpkgs to nixpkgs-python.

I do find a few other ways that could work, but I feel they are not very ideal:

  • I could set config.contentAddressedByDefault = true in $HOME/.config/nixpkgs/config.nix, but this enables CA globally and I would rather not do that to the other parts of my setup.
  • I could do python.override {packageOverrides = ...} at my call site, but this creates a lot of boilerplate and make the intention of the code very hard to read since you need to enumerate through the packages.

I now need a way to modify the default behavior of a flake (here it is nixpkgs), using either overlays or config, and then supply the customized flake as input to another flake, without modifying the other flake’s code.

I figured that one way to do this is to write a new flake (I call it proxy flake) that emulates itself as nixpkgs flake and modifies the default behavior. However since many call sites of nixpkgs would do import nixpkgs {...}, that behavior needs to be modified as well and this makes everything a bit complicated. Eventually, I had to pin down a locked flake version and use getFlake to access unmodified nixpkgs from default.nix, and this is what I got working:

flake.nix

{
  description = "Patched nixpkgs";
  inputs.nixpkgs.url = "flake:nixpkgs";
  outputs = {self, nixpkgs} :
  let 
    lib = nixpkgs.lib;
    forAllSystems = lib.genAttrs lib.systems.flakeExposed;
    libVersionInfoOverlay = import
      ( nixpkgs + "/lib/flake-version-info.nix") nixpkgs;
    patchedNixpkgs = nixpkgs // {
      legacyPackages = forAllSystems (system:
        let
          pkgs = (import ./. { inherit system; }).extend (final: prev: {
            lib = prev.lib.extend libVersionInfoOverlay;
          });
        in pkgs
      );
    };
    lockFile = builtins.fromJSON (builtins.readFile ./flake.lock);
    lockedNarHash = lockFile.nodes.nixpkgs.locked.narHash;
    inputIsLocked = (lockedNarHash == nixpkgs.narHash);
  in if inputIsLocked then patchedNixpkgs else abort ''

  Input to this patched nixpkgs flake should not be overridden. Doing so may
  introduce inconsistent behavior between flake usage and import usage.

  Please modify the Patched nixpkgs flake directly if you want to use another
  version of nixpkgs as base.
  '';
}

default.nix

args:
let 
  lockFile = builtins.fromJSON (builtins.readFile ./flake.lock);
  lockedURL = "github:NixOS/nixpkgs/${lockFile.nodes.nixpkgs.locked.rev}";
  origNixpkgs = builtins.getFlake lockedURL;
in
  import origNixpkgs (args // {
    config.contentAddressedByDefault = true;
  })

However this still feels a bit hacky to me, so my questions are:

  • What is the recommended way of applying a customized nixpkgs to a part of the system? For whole systems, we have global config/overlays; for individual packages we can use overrides, but it seems to be tricky to go anywhere in between.
  • Is there any recommended way of overriding the behavior of an input flake to another flake without modifying its code? Is making a proxy flake a good idea/practice?
  • How to address the problem of default.nix not having the context of flake inputs when building a proxy flake for nixpkgs? Is pinning down the flake version the only way to do it?
1 Like