How to consume a `eachDefaultSystem` flake overlay?

I am not sure how to consume the flake that is created with flake-utils.lib.eachDefaultSystem with an overlays property inside it. In the consumer I can specify neovim-custom.overlay.x64_86-darwin, but that won’t work since I have more than one type of system I run Nix on (a Mac and a couple NixOS systems).

For more specific background, the submodule flake is my neovim config. I’d like to host it in it’s own folder so I can call nix run ... from other systems. The consumer is my entire config and I am trying to consume the neovim config as an overlay.

The two flakes in question are detailed below.

Flake “submodule” - neovim-custom

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

    flake-utils = {
      url = "github:numtide/flake-utils";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    neovim-nightly-overlay = {
      url = "github:nix-community/neovim-nightly-overlay";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = inputs @
  { self
  , nixpkgs
  , flake-utils
  , neovim-nightly-overlay
  }:
    flake-utils.lib.eachDefaultSystem (system: let
      pkgs = import nixpkgs {
        inherit system;
      };
      configDir = ./config;
    in rec {
      packages.neovim-custom = import ./neovim.nix { inherit pkgs configDir; };
      packages.default = packages.neovim-custom;

      apps.neovim = flake-utils.lib.mkApp {
        drv = packages.neovim-custom;
        name = "nvim";
      };
      defaultApp = apps.neovim;

      devShells.default = pkgs.mkShell {
        packages = with pkgs; [ rnix-lsp terraform-ls tree-sitter ];
        buildInputs = [
          packages.neovim-custom
        ];
      };

      # I named the overlay `neovim-custom`. I don't know if that's the best route.
      overlays = final: prev: rec {
        inherit (packages) neovim-custom;
      };
    }
  );
}

Flake “consumer” - this flake consumes the one above

{
  description = "Milo's flake";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

    darwin = {
      url = "github:LnL7/nix-darwin/master";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    flake-utils = {
      url = "github:numtide/flake-utils";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    neovim-custom = {
      url = "path:modules/neovim";
      inputs.nixpkgs.follows = "nixpkgs";
      inputs.flake-utils.follows = "flake-utils";
    };
  };

  outputs = inputs @
  { self
  , nixpkgs
  , darwin
  , flake-utils
  , home-manager
  , neovim-custom
  }:
    let
      overlays = final: prev:
        {
          # Series of overlays that currently work properly.
        };

      nixpkgsConfig = with inputs; {
        overlays = [
          neovim-custom.overlay.x86_64-darwin # Problem may here!
          overlays
        ];
      };
    in rec {
      darwinConfigurations = {
        worktop = darwin.lib.darwinSystem {
          # Configure home manager in here and install `neovim-custom` package (from my submodule flake).
        };
      };

      # Other configurations for other systems.
    };
}

Notes

I may be going about this the wrong way. I know what I’d like to accomplish and I was trying to model my flake off of the neovim-nightly overlay, but I don’t know if this is the best route.

I am happy to answer any questions and thanks in advance for any help!

1 Like

That’s the problem, it doesn’t belong there. Move it outside.

Do know if there is documentation to that effect? I looked through the flake-utils repo and I can’t seem to find much there, but I was under the impression that the eachDefaultSystem function was to essentially generate an output that is appropriate for each system that might be calling it.

Additionally, all the examples of using eachDefaultSystem have it as the top level, not inside a block or anything. i.e.

{
  outputs = inputs @
  { ... }:
    flake-utils.lib.eachDefaultSystem (system: let
      # ...
    in rec {
      # ...
    }
}

The flake schema wants that overlays is a list or a set of overlays. It doesn’t say anything about system.

withEachSystem unconditionally puts the $system as second attribute level. It is not smart in any way.

In my opinion, it’s easier to just not use flake utils, but specify all supported filesystems explicitly, and only those that you are willing to and able to support.

I usually do something like https://github.com/jonringer/nix-template/blob/ef02e1de78f541ec7b9ae4bc11871385b312213d/flake.nix#L14-L20

relevant part:

      localOverlay = import ./nix/overlay.nix;

      pkgsForSystem = system: import nixpkgs {
        overlays = [
          localOverlay
        ];
        inherit system;
      };
    in utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ] (system: rec {
      legacyPackages = pkgsForSystem system;
      ...

Then just pull out the packages you want from legacyPackages

1 Like

Yep, jonringer posted the correct solution. Here is TL;DR version of it:

outputs = { nixpkgs, flake-utils, ... }:
{ 
  overlays.default = prev: final: { abc = xyz; };
} // flake-utils.lib.eachDefaultSystem(system: { packages = pkgs.neovim; })

Note that overlay will give you a deprecation warning, nowadays it should be overlays.default.

Personally my template looks something like this (though I’ve considered getting rid of flake-utils)


{
  inputs = {
    flake-utils.url = "github:numtide/flake-utils";
    nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05";
  };

  outputs = {
    self,
    flake-utils,
    nixpkgs,
    ...
  }:
  let
    inherit (nixpkgs) lib;

    eachDefaultEnvironment = f:
      flake-utils.lib.eachDefaultSystem
      (
        system:
          f {
            inherit system;
            pkgs = import nixpkgs {
              inherit system;
              # You likely (but not necessarily) want the default overlay from your flake here
              overlays = [self.overlays.default];
            };
          }
      );
  in

    # Per system outputs:
    eachDefaultEnvironment ({ system, pkgs }: {
        packages = {
          my-neovim = pkgs.neovim;
        };
        # devShells = { }; # A develop shell for each of your systems
        # formatter = { }; # A nix formatter formatter for each of your systems
        # nixosConfigurations = { }; # questionable
        # nixopsConfigurations = { }; # questionable
    })

    # Generic outputs:
    // {
      lib = {
        identity = x: x;
        const = x: y: x;
      };

      nixosModules = rec {
        x = import ./nixos-modules/x;
        y = import ./nixos-modules/y;
        z = import ./nixos-modules/z;
        default = {
          imports = [ x y z ];
        };
      };

      overlays = rec {
        a = import ./overlays/a;
        b = import ./overlays/b;
        c = import ./overlays/c;
        default = final: prev:
          lib.composeManyExtensions [ a b c ] final prev;
      };
    };
}

That being said, there’s always some slight variation (which is why I’d like to get away from flake-utils one day)

1 Like

Thanks everybody for the responses, I am not sure I am going about this the right way, but the ./neovim.nix file that I reference in the “submodule” file uses pkgs, which I was under the impression depended upon the system that the flake is being built for.

I am now wondering if I am just going about this the wrong way. Should I even be using an overlay here? The core of neovim config is just a some of config options in programs.neovim, plus my configuration files for neovim itself.

The thing that made me think about this is I just want to use the standard neovim distributed in unstable, but provide a config that is all wrapped up in a single flake that can be used in nix develop, run from nix run, and imported into my larger system config.

Thanks again and I can totally provide more information if needed!

Should I even be using an overlay here?

@milogert I think I see your confusion.

The simple answer is no you don’t need an overlay.


But for the sake of argument, suppose you did want an overlay.

This is (roughly) the “wrong” approach:

outputs = { self, ... }:
let

  pkgs = import nixpkgs { system = "x86_64-linux"; };

in
{

  # This is totally OK (and probably all you need):
  packages.x86_64-linux.neovim-custom = pkgs.callPackage ./neovim-custom { };

  overlays.default = final: prev: {

    # But this is "incorrect" because neovim-custom was constructed from pkgs.
    # Notice that we didn't need to use final or prev at all, so it's pointless putting this here.
    neovim-custom = self.packages.x86_64-linux.neovim-custom;

  };

}

This is (roughly) the “right” approach:

outputs = { self, ... }: {
  
  overlays.default = final: prev: {

    # The "correct" approach is to use final (and/or prev) to construct your package
    # Notice that pkgs is not involved at all.
    neovim-custom = final.callPackage ./neovim-custom { };

  };

  packages.x86_64-linux = 
    let 

      # So-called "legacy" packages, extended by your overlay.
      pkgs = import nixpkgs {
        system = "x86_64-linux";
        overlays = [ self.overlays.default ];
      };

    in {

      # Now you can just export neovim-custom from pkgs
      neovim-custom = pkgs.neovim-custom;

    };

}

Hope that helps.

Note: Overlays is about not relying on a fixed package set. With flakes a common alternative is to just override the flake inputs if you need to swap out for a different nixpkgs. However, overlays can be helpful when there is complicated layering of overrides that need to happen in a specific order.

The core of neovim config is just a some of config options in programs.neovim , plus my configuration files for neovim itself.

Also ok would be (roughly) something like:

programs.neovim.package = pkgs.callPackage ./neovim-custom {}; 

You don’t need to export your neovim package in a flake output at all if you don’t want to.

Reasons for exporting your package would be:

  1. Keep your code organized in a canonical structure
  2. Share your your packages with others in a clean and reproducible form
3 Likes

Ok @rehno that was extremely helpful. Thanks for the explanation. This is resolved for me now!

I decided to do the flake + overlay approach for the exporting benefit. Now I can easily change a tiny thing in my neovim config, test it, and if it’s good update my full system to use the new config.

Thanks again!

1 Like