From 7c0ea143d81559993d3f3005b97b925e28dee39c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 12 Jun 2025 20:19:19 +0200 Subject: [PATCH 1/3] Fix deep overrides An override like inputs.foo.inputs.bar.inputs.nixpkgs.follows = "nixpkgs"; implicitly set `inputs.foo.inputs.bar` to `flake:bar`, which led to an unexpected error like error: cannot find flake 'flake:bar' in the flake registries We now no longer create a parent override (like for `foo.bar` in the example above) if it doesn't set an explicit ref or follows attribute. We only recursively apply its child overrides. Fixes https://github.com/NixOS/nix/issues/8325, https://github.com/DeterminateSystems/nix-src/issues/95, https://github.com/NixOS/nix/issues/12083, https://github.com/NixOS/nix/issues/5790. --- src/libflake/flake.cc | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/libflake/flake.cc b/src/libflake/flake.cc index 3a9bdf43a..2b73dbf0b 100644 --- a/src/libflake/flake.cc +++ b/src/libflake/flake.cc @@ -101,7 +101,6 @@ static void parseFlakeInputAttr( static FlakeInput parseFlakeInput( EvalState & state, - std::string_view inputName, Value * value, const PosIdx pos, const InputAttrPath & lockRootAttrPath, @@ -171,9 +170,6 @@ static FlakeInput parseFlakeInput( input.ref = parseFlakeRef(state.fetchSettings, *url, {}, true, input.isFlake, true); } - if (!input.follows && !input.ref) - input.ref = FlakeRef::fromAttrs(state.fetchSettings, {{"type", "indirect"}, {"id", std::string(inputName)}}); - return input; } @@ -201,7 +197,6 @@ static std::pair, fetchers::Attrs> parseFlakeInput } else { inputs.emplace(inputName, parseFlakeInput(state, - inputName, inputAttr.value, inputAttr.pos, lockRootAttrPath, @@ -483,18 +478,27 @@ LockedFlake lockFlake( /* Get the overrides (i.e. attributes of the form 'inputs.nixops.inputs.nixpkgs.url = ...'). */ - for (auto & [id, input] : flakeInputs) { + std::function addOverrides; + addOverrides = [&](const FlakeInput & input, const InputAttrPath & prefix) + { for (auto & [idOverride, inputOverride] : input.overrides) { - auto inputAttrPath(inputAttrPathPrefix); - inputAttrPath.push_back(id); + auto inputAttrPath(prefix); inputAttrPath.push_back(idOverride); - overrides.emplace(inputAttrPath, - OverrideTarget { - .input = inputOverride, - .sourcePath = sourcePath, - .parentInputAttrPath = inputAttrPathPrefix - }); + if (inputOverride.ref || inputOverride.follows) + overrides.emplace(inputAttrPath, + OverrideTarget { + .input = inputOverride, + .sourcePath = sourcePath, + .parentInputAttrPath = inputAttrPathPrefix + }); + addOverrides(inputOverride, inputAttrPath); } + }; + + for (auto & [id, input] : flakeInputs) { + auto inputAttrPath(inputAttrPathPrefix); + inputAttrPath.push_back(id); + addOverrides(input, inputAttrPath); } /* Check whether this input has overrides for a @@ -550,7 +554,8 @@ LockedFlake lockFlake( continue; } - assert(input.ref); + if (!input.ref) + input.ref = FlakeRef::fromAttrs(state.fetchSettings, {{"type", "indirect"}, {"id", std::string(id)}}); auto overriddenParentPath = input.ref->input.isRelative() From 637c4f3ad75a41ff0f0a34301fdc76451e50f800 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 12 Jun 2025 20:33:28 +0200 Subject: [PATCH 2/3] Add tests for deep overrides Taken from https://github.com/NixOS/nix/pull/6621. Co-authored-by: Sebastian Ullrich --- tests/functional/flakes/follow-paths.sh | 60 +++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/functional/flakes/follow-paths.sh b/tests/functional/flakes/follow-paths.sh index a71d4c6d7..abc09dfc2 100755 --- a/tests/functional/flakes/follow-paths.sh +++ b/tests/functional/flakes/follow-paths.sh @@ -359,3 +359,63 @@ rm "$flakeFollowsCustomUrlA"/flake.lock json=$(nix flake metadata "$flakeFollowsCustomUrlA" --override-input B/C "$flakeFollowsCustomUrlD" --json) echo "$json" | jq .locks.nodes.C.original [[ $(echo "$json" | jq -r .locks.nodes.C.original.path) = './flakeC' ]] + +# Test deep overrides, e.g. `inputs.B.inputs.C.inputs.D.follows = ...`. + +cat < $flakeFollowsD/flake.nix +{ outputs = _: {}; } +EOF +cat < $flakeFollowsC/flake.nix +{ + inputs.D.url = "path:nosuchflake"; + outputs = _: {}; +} +EOF +cat < $flakeFollowsB/flake.nix +{ + inputs.C.url = "path:$flakeFollowsC"; + outputs = _: {}; +} +EOF +cat < $flakeFollowsA/flake.nix +{ + inputs.B.url = "path:$flakeFollowsB"; + inputs.D.url = "path:$flakeFollowsD"; + inputs.B.inputs.C.inputs.D.follows = "D"; + outputs = _: {}; +} +EOF + +nix flake lock $flakeFollowsA + +[[ $(jq -c .nodes.C.inputs.D $flakeFollowsA/flake.lock) = '["D"]' ]] + +# Test overlapping flake follows: B has D follow C/D, while A has B/C follow C + +cat < $flakeFollowsC/flake.nix +{ + inputs.D.url = "path:$flakeFollowsD"; + outputs = _: {}; +} +EOF +cat < $flakeFollowsB/flake.nix +{ + inputs.C.url = "path:nosuchflake"; + inputs.D.url = "path:nosuchflake"; + inputs.D.follows = "C/D"; + outputs = _: {}; +} +EOF +cat < $flakeFollowsA/flake.nix +{ + inputs.B.url = "path:$flakeFollowsB"; + inputs.C.url = "path:$flakeFollowsC"; + inputs.B.inputs.C.follows = "C"; + outputs = _: {}; +} +EOF + +# bug was not triggered without recreating the lockfile +nix flake lock $flakeFollowsA --recreate-lock-file + +[[ $(jq -c .nodes.B.inputs.D $flakeFollowsA/flake.lock) = '["B","C","D"]' ]] From b415faceca9ea32823ec3701580dc3c1d2530d82 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 12 Jun 2025 20:38:51 +0200 Subject: [PATCH 3/3] Don't allow flake inputs to have both a flakeref and a follows Having both doesn't make sense so it's best to disallow it. If this causes issues we could turn into a warning. --- src/libflake/flake.cc | 3 +++ tests/functional/flakes/follow-paths.sh | 13 ++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/libflake/flake.cc b/src/libflake/flake.cc index 2b73dbf0b..e59649fec 100644 --- a/src/libflake/flake.cc +++ b/src/libflake/flake.cc @@ -170,6 +170,9 @@ static FlakeInput parseFlakeInput( input.ref = parseFlakeRef(state.fetchSettings, *url, {}, true, input.isFlake, true); } + if (input.ref && input.follows) + throw Error("flake input has both a flake reference and a follows attribute, at %s", state.positions[pos]); + return input; } diff --git a/tests/functional/flakes/follow-paths.sh b/tests/functional/flakes/follow-paths.sh index abc09dfc2..cf27681cb 100755 --- a/tests/functional/flakes/follow-paths.sh +++ b/tests/functional/flakes/follow-paths.sh @@ -401,7 +401,6 @@ EOF cat < $flakeFollowsB/flake.nix { inputs.C.url = "path:nosuchflake"; - inputs.D.url = "path:nosuchflake"; inputs.D.follows = "C/D"; outputs = _: {}; } @@ -419,3 +418,15 @@ EOF nix flake lock $flakeFollowsA --recreate-lock-file [[ $(jq -c .nodes.B.inputs.D $flakeFollowsA/flake.lock) = '["B","C","D"]' ]] + +# Check that you can't have both a flakeref and a follows attribute on an input. +cat < $flakeFollowsB/flake.nix +{ + inputs.C.url = "path:nosuchflake"; + inputs.D.url = "path:nosuchflake"; + inputs.D.follows = "C/D"; + outputs = _: {}; +} +EOF + +expectStderr 1 nix flake lock $flakeFollowsA --recreate-lock-file | grepQuiet "flake input has both a flake reference and a follows attribute"