Each dep_ has its own flake and lockfile. It is important because each dependency will rely on different version of nixpkgs.
My goal is to create in the root flake a shell that provides all outputs of dep_.
I am able to successfully build each dep_ with its own flake. However, when running nix flake build on the root, I noticed that I have build errors.
Tracing back to comparing the dep_ lockfiles and the root lockfile, it seems that I updated the inputs in my dep_, but those updates are not propagated up to the root lockfile (specifically, nixpkgs revisions are not the same).
How can I ensure that my subflakes lock inputs updates are propagated up to the root lock file?
Call nix flake lock --update-input dep_n for each subflake n whenever it’s modified; this should propogate updates from subflakes to the root flake.
Store each package inside their own separate git repositories, and import them via URL in the root flake.
Don’t use sub-flakes.
I decided to stop trying to use sub-flakes.
Instead, I used raw Nix (default.nix + shell.nix) that the flake imports.
You actually don’t lose any functionality this way; the dependencies are just declared in the root flake.nix.
For your use case, you need different versions of nixpkgs for your modules; so declare more than one in your root flake, and pass them to your modules.
root/flake.nix
{
description = "root flake for each dep_n";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
nixpkgs-dep-n.url = "github:nixos/nixpkgs/nixos-24.05";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, nixpkgs-dep-n, flake-utils, ... }: let
name = "root-flake";
version = "0.0.0";
utils = flake-utils;
in utils.lib.eachDefaultSystem (
system: let
pkgs = import nixpkgs { inherit system; };
pkgs-dep-n = import nixpkgs-dep-n { inherit system; };
# import each package from vendor/ in "modules"
modules = {
dep-n = import ./vendor/dep_n { inherit pkgs-dep-n; };
}
in rec {
packages = {
# TODO insert some nix derivation that consumes packages from "modules"
# modules.dep-n.package-name
default = ;
};
apps = {
default = utils.lib.mkApp { drv = packages.default; };
};
# the default devShell for each module is provided by nix develop
devShells = (pkgs.lib.attrsets.mapAttrs
(n: v: v.devShells.default)
modules
) // {
# the root devShell provides additional packages
root = pkgs.mkShell {
packages = [
# TODO declare some additional dev packages for root flake
];
shellHook = ''
# TODO export ENV_VAR=""
'';
};
# the default devShell is a combination of every devShell
default = pkgs.mkShell {
inputsFrom = (
pkgs.lib.attrsets.mapAttrsToList
(n: v: devShells."${n}")
modules
) ++ [ devShells.root ];
};
};
}
);
}
Which exact version of nix are you using? I assume it’s at least 2.26 because there were some important changes in 2.26 relating to relative path inputs.
I have done some testing of subflakes using nix 2.26.1. In general, things are working very well for me.
Testing with your example, changes to ./dep_n/flake.lockaren’t automatically propagated to the root flake.lock.
Certainly, the changes to ./dep_n/flake.lock are only propagated to flake.lock after running nix flake update dep_n.
I’m not sure whether this is a bug or intended behaviour.
On one hand, I would expect lock files to keep inputs locked, unless the user asks for an update. On the other hand, with relative path inputs, changes to ./dep_n/flake.nix take effect immediately, so perhaps changes to ./dep_n/flake.lock should also take effect immediately.
Indeed, I see that there is no evident choice. My expectation would be that an update on the lockfile of the subdependency would be propagated to the root flake (given that the root flake does not constraint anything on the subflake deps). But I see how this may become a problem if the root flake would constraint the inputs (for instance, in case of inputs.nixpkgs.follows AND a local subflake, who should take precedence?).
Thanks for your answer. I read your initial post thoroughly. I may consider switching to this, but I think having separate lockfiles for the dependencies is actually more sustainable in the long run (the root lockfile is clogged already).
So after a change to ./dep_a/flake.lock, next time the root flake is evaluated, nix should print:
$ nix build .
warning: Git tree '/home/user/root' is dirty
unpacking 'github:nixos/nixpkgs/<rev>'' into the Git cache
warning: updating lock file '"/home/user/root/flake.lock"':
• Updated input 'dep_a/nixpkgs':
<previous value>
→ <value from ./dep_a/flake.lock>
Yes, that way seems to me the better choice.
In the case of a follows from the root flake, I would expect it to work the same way it does for any non-relative-path flake input. That is, the root flake follows overrides both ./dep_n/flake.lock and ./dep_n/flake.nix.