Skip to content

Commit 799d013

Browse files
Ma27Gerrit Code Review
authored andcommitted
Merge "Revert "Revert "Merge pull request NixOS#6621 from Kha/nested-follows""" into main
2 parents 79d0ae6 + 0e38720 commit 799d013

File tree

3 files changed

+115
-13
lines changed

3 files changed

+115
-13
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
synopsis: Fix nested flake input `follows`
3+
prs: 6621
4+
cls: 994
5+
---
6+
7+
Previously nested-input overrides were ignored; that is, the following did not
8+
override anything, in spite of the `nix3-flake` manual documenting it working:
9+
10+
```
11+
{
12+
inputs = {
13+
foo.url = "github:bar/foo";
14+
foo.inputs.bar.inputs.nixpkgs = "nixpkgs";
15+
};
16+
}
17+
```
18+
19+
This is useful to avoid the 1000 instances of nixpkgs problem without having
20+
each flake in the dependency tree to expose all of its transitive dependencies
21+
for modification.

src/libexpr/flake/flake.cc

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,11 @@ static void expectType(EvalState & state, ValueType type,
9191

9292
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
9393
EvalState & state, Value * value, const PosIdx pos,
94-
const std::optional<Path> & baseDir, InputPath lockRootPath);
94+
const std::optional<Path> & baseDir, InputPath lockRootPath, unsigned depth);
9595

9696
static FlakeInput parseFlakeInput(EvalState & state,
9797
const std::string & inputName, Value * value, const PosIdx pos,
98-
const std::optional<Path> & baseDir, InputPath lockRootPath)
98+
const std::optional<Path> & baseDir, InputPath lockRootPath, unsigned depth)
9999
{
100100
expectType(state, nAttrs, *value, pos);
101101

@@ -119,7 +119,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
119119
expectType(state, nBool, *attr.value, attr.pos);
120120
input.isFlake = attr.value->boolean;
121121
} else if (attr.name == sInputs) {
122-
input.overrides = parseFlakeInputs(state, attr.value, attr.pos, baseDir, lockRootPath);
122+
input.overrides = parseFlakeInputs(state, attr.value, attr.pos, baseDir, lockRootPath, depth + 1);
123123
} else if (attr.name == sFollows) {
124124
expectType(state, nString, *attr.value, attr.pos);
125125
auto follows(parseInputPath(attr.value->string.s));
@@ -168,15 +168,19 @@ static FlakeInput parseFlakeInput(EvalState & state,
168168
input.ref = parseFlakeRef(*url, baseDir, true, input.isFlake);
169169
}
170170

171-
if (!input.follows && !input.ref)
171+
if (!input.follows && !input.ref && depth == 0)
172+
// in `input.nixops.inputs.nixpkgs.url = ...`, we assume `nixops` is from
173+
// the flake registry absent `ref`/`follows`, but we should not assume so
174+
// about `nixpkgs` (where `depth == 1`) as the `nixops` flake should
175+
// determine its default source
172176
input.ref = FlakeRef::fromAttrs({{"type", "indirect"}, {"id", inputName}});
173177

174178
return input;
175179
}
176180

177181
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
178182
EvalState & state, Value * value, const PosIdx pos,
179-
const std::optional<Path> & baseDir, InputPath lockRootPath)
183+
const std::optional<Path> & baseDir, InputPath lockRootPath, unsigned depth)
180184
{
181185
std::map<FlakeId, FlakeInput> inputs;
182186

@@ -189,7 +193,8 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
189193
inputAttr.value,
190194
inputAttr.pos,
191195
baseDir,
192-
lockRootPath));
196+
lockRootPath,
197+
depth));
193198
}
194199

195200
return inputs;
@@ -239,7 +244,7 @@ static Flake getFlake(
239244
auto sInputs = state.symbols.create("inputs");
240245

241246
if (auto inputs = vInfo.attrs->get(sInputs))
242-
flake.inputs = parseFlakeInputs(state, inputs->value, inputs->pos, flakeDir, lockRootPath);
247+
flake.inputs = parseFlakeInputs(state, inputs->value, inputs->pos, flakeDir, lockRootPath, 0);
243248

244249
auto sOutputs = state.symbols.create("outputs");
245250

@@ -322,6 +327,19 @@ Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup
322327
return getFlake(state, originalRef, allowLookup, flakeCache);
323328
}
324329

330+
/* Recursively merge `overrides` into `overrideMap` */
331+
static void updateOverrides(std::map<InputPath, FlakeInput> & overrideMap, const FlakeInputs & overrides,
332+
const InputPath & inputPathPrefix)
333+
{
334+
for (auto & [id, input] : overrides) {
335+
auto inputPath(inputPathPrefix);
336+
inputPath.push_back(id);
337+
// Do not override existing assignment from outer flake
338+
overrideMap.insert({inputPath, input});
339+
updateOverrides(overrideMap, input.overrides, inputPath);
340+
}
341+
}
342+
325343
/* Compute an in-memory lock file for the specified top-level flake,
326344
and optionally write it to file, if the flake is writable. */
327345
LockedFlake lockFlake(
@@ -394,12 +412,9 @@ LockedFlake lockFlake(
394412
/* Get the overrides (i.e. attributes of the form
395413
'inputs.nixops.inputs.nixpkgs.url = ...'). */
396414
for (auto & [id, input] : flakeInputs) {
397-
for (auto & [idOverride, inputOverride] : input.overrides) {
398-
auto inputPath(inputPathPrefix);
399-
inputPath.push_back(id);
400-
inputPath.push_back(idOverride);
401-
overrides.insert_or_assign(inputPath, inputOverride);
402-
}
415+
auto inputPath(inputPathPrefix);
416+
inputPath.push_back(id);
417+
updateOverrides(overrides, input.overrides, inputPath);
403418
}
404419

405420
/* Check whether this input has overrides for a
@@ -434,6 +449,12 @@ LockedFlake lockFlake(
434449
// Respect the “flakeness” of the input even if we
435450
// override it
436451
i->second.isFlake = input2.isFlake;
452+
if (!i->second.ref)
453+
i->second.ref = input2.ref;
454+
if (!i->second.follows)
455+
i->second.follows = input2.follows;
456+
// Note that `input.overrides` is not used in the following,
457+
// so no need to merge it here (already done by `updateOverrides`)
437458
}
438459
auto & input = hasOverride ? i->second : input2;
439460

tests/functional/flakes/follow-paths.sh

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,63 @@ git -C "$flakeFollowsOverloadA" add flake.nix flakeB/flake.nix \
230230
nix flake metadata "$flakeFollowsOverloadA"
231231
nix flake update --flake "$flakeFollowsOverloadA"
232232
nix flake lock "$flakeFollowsOverloadA"
233+
234+
# Test nested flake overrides: A overrides B/C/D
235+
236+
cat <<EOF > $flakeFollowsD/flake.nix
237+
{ outputs = _: {}; }
238+
EOF
239+
cat <<EOF > $flakeFollowsC/flake.nix
240+
{
241+
inputs.D.url = "path:nosuchflake";
242+
outputs = _: {};
243+
}
244+
EOF
245+
cat <<EOF > $flakeFollowsB/flake.nix
246+
{
247+
inputs.C.url = "path:$flakeFollowsC";
248+
outputs = _: {};
249+
}
250+
EOF
251+
cat <<EOF > $flakeFollowsA/flake.nix
252+
{
253+
inputs.B.url = "path:$flakeFollowsB";
254+
inputs.D.url = "path:$flakeFollowsD";
255+
inputs.B.inputs.C.inputs.D.follows = "D";
256+
outputs = _: {};
257+
}
258+
EOF
259+
260+
nix flake lock $flakeFollowsA
261+
262+
[[ $(jq -c .nodes.C.inputs.D $flakeFollowsA/flake.lock) = '["D"]' ]]
263+
264+
# Test overlapping flake follows: B has D follow C/D, while A has B/C follow C
265+
266+
cat <<EOF > $flakeFollowsC/flake.nix
267+
{
268+
inputs.D.url = "path:$flakeFollowsD";
269+
outputs = _: {};
270+
}
271+
EOF
272+
cat <<EOF > $flakeFollowsB/flake.nix
273+
{
274+
inputs.C.url = "path:nosuchflake";
275+
inputs.D.url = "path:nosuchflake";
276+
inputs.D.follows = "C/D";
277+
outputs = _: {};
278+
}
279+
EOF
280+
cat <<EOF > $flakeFollowsA/flake.nix
281+
{
282+
inputs.B.url = "path:$flakeFollowsB";
283+
inputs.C.url = "path:$flakeFollowsC";
284+
inputs.B.inputs.C.follows = "C";
285+
outputs = _: {};
286+
}
287+
EOF
288+
289+
# bug was not triggered without recreating the lockfile
290+
nix flake update --flake $flakeFollowsA
291+
292+
[[ $(jq -c .nodes.B.inputs.D $flakeFollowsA/flake.lock) = '["B","C","D"]' ]]

0 commit comments

Comments
 (0)