Including flakes in the same repo

Many things to love about Nix flakes so far. But one major source of frustration we’ve been running into adopting is using multiple flakes in the same repo. The architecture I had in mind is:

  • a root flake with the master development shell for a monorepo
  • a bunch of flakes sprinkled in folders that inherit the root flake (or even inherit something that inherits from root shell) and adds whatever that specific monorepo folder requires.

The way I accomplished that, briefly is:
./flake.nix

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

  outputs = { self, nixpkgs, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
      in rec {
        devShells.base = pkgs.mkShellNoCC {
          packages = with pkgs; [
            # ...
          ];
        };
      });
}

./some-folder/flake.nix

{
  description = "Neuralink infrastructure-specific flake";

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

  outputs = { self, nixpkgs, flake-utils, sw, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
        swShells = sw.devShells.${system};
        swPackages = sw.packages.${system};
        swOverlays = sw.overlays.${system};
      in rec {
        devShells.default = pkgs.mkShellNoCC {
          packages = with pkgs;
            [
              # ...
            ];

          inputsFrom = [swShells.default];
        };
      });
}

There might be some typos redacting these down to the bare essentials, but this generally works. However, the recurring issue seems to be that the narHash in the flake.lock in the sub-folder will change:

  • whenever the root flake changes (fine)
  • between developer machines? (not fine)

This is what the lock stanza looks like:

"sw": {
      "inputs": {
        "flake-utils": "flake-utils",
        "nixpkgs": "nixpkgs_2"
      },
      "locked": {
        "lastModified": 0,
        "narHash": "sha256-AoVnviwh3Fx0wjd1bBMzFPiHL/xqgmDrTfFyI3860pU=",
        "path": "../",
        "type": "path"
      },
      "original": {
        "path": "../",
        "type": "path"
      }
    },

Same git commit, clean repo, two people on two different darwin_aarch64 machines get different narHash. The failure case here looks like this:

$ nix develop
path '<REDACTED>' does not contain a 'flake.nix', searching up
error:
       … in the condition of the assert statement

         at «string»:66:13:

           65|           if node.flake or true then
           66|             assert builtins.isFunction flake.outputs;
             |             ^
           67|             result

       … while calling the 'isFunction' builtin

         at «string»:66:20:

           65|           if node.flake or true then
           66|             assert builtins.isFunction flake.outputs;
             |                    ^
           67|             result

       (stack trace truncated; use '--show-trace' to show the full trace)

       error: cannot fetch input 'path:../?lastModified=0&narHash=sha256-JdMWfTmMQpwVag%2B/lhwEFI9NgYykJnDkKdpr93G4Guo%3D' because it uses a relative path

What am I missing here? Given that nix searches up for the nearest flake, this design should almost be the encouraged pattern. I did try git+file but that had other issues I can get into, if that’s the path we should be following here.

1 Like

My understanding is that this isn’t very well-supported, and in general the right number of flakes per repo is 1.

So for a monorepo with hundreds of projects, all dev shells need to land in a single root/flake.nix?

You may have better luck if you don’t have flakes referencing each other unless they don’t overlap. Don’t use a subdirectory as an input, don’t use a parent directory as an input, but maybe you can get away with siblings and such.

Land as in being exported from one flake.nix, yes. But they don’t have to be defined in that file. You can add a shell.nix file on each project and import it (probably you’ll want to use callPackage) from the main flake.

I’ve been doing that for a side project, although in the nested projects I use the old CLI tooling main to avoid having to refer to the top level directory.

One thing that I haven’t figured out yet is how to have the arguments of a nix file refer the inputs of a flake. ej in a file default.nix I have

{ system ? builtins.currentSystem
, nixpkgs ? fetchTarball {
    url = "https://github.com/NixOS/nixpkgs/archive/refs/tags/23.05.tar.gz";
    sha256 = "10wn0l08j9lgqcw8177nh2ljrnxdrpri7bp0g7nvrsn9rkawvlbf";
  }
, pkgs ? import nixpkgs {
    overlays = [ ];
    config = { };
    inherit system;
  }
}:

When importing to the flake I can use pkgs.callPackage ./path/to/default.nix. But I’d like to specifcy the default nixpkgs to use the version from the flake.lock file. Anyone know how to do so?

What exactly is the fundamental nix concept preventing me from doing the exact thing I’m trying to do?

i.e. this seems like a bug. That could easily be resolved by not generating a narHash for includes from the same repo. Or am I missing something?

1 Like