Skip to content

Commit

Permalink
Fix nested flake input overrides
Browse files Browse the repository at this point in the history
  • Loading branch information
Kha committed Jul 17, 2022
1 parent 59764eb commit c043a27
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 13 deletions.
47 changes: 34 additions & 13 deletions src/libexpr/flake/flake.cc
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,11 @@ static void expectType(EvalState & state, ValueType type,

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

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

Expand All @@ -118,7 +118,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
expectType(state, nBool, *attr.value, attr.pos);
input.isFlake = attr.value->boolean;
} else if (attr.name == sInputs) {
input.overrides = parseFlakeInputs(state, attr.value, attr.pos, baseDir, lockRootPath);
input.overrides = parseFlakeInputs(state, attr.value, attr.pos, baseDir, lockRootPath, depth + 1);
} else if (attr.name == sFollows) {
expectType(state, nString, *attr.value, attr.pos);
auto follows(parseInputPath(attr.value->string.s));
Expand Down Expand Up @@ -163,15 +163,19 @@ static FlakeInput parseFlakeInput(EvalState & state,
input.ref = parseFlakeRef(*url, baseDir, true, input.isFlake);
}

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

return input;
}

static std::map<FlakeId, FlakeInput> parseFlakeInputs(
EvalState & state, Value * value, const PosIdx pos,
const std::optional<Path> & baseDir, InputPath lockRootPath)
const std::optional<Path> & baseDir, InputPath lockRootPath, unsigned depth)
{
std::map<FlakeId, FlakeInput> inputs;

Expand All @@ -184,7 +188,8 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
inputAttr.value,
inputAttr.pos,
baseDir,
lockRootPath));
lockRootPath,
depth));
}

return inputs;
Expand Down Expand Up @@ -230,7 +235,7 @@ static Flake getFlake(
auto sInputs = state.symbols.create("inputs");

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

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

Expand Down Expand Up @@ -313,6 +318,19 @@ Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup
return getFlake(state, originalRef, allowLookup, flakeCache);
}

/* Recursively merge `overrides` into `overrideMap` */
static void updateOverrides(std::map<InputPath, FlakeInput> & overrideMap, const FlakeInputs & overrides,
const InputPath & inputPathPrefix)
{
for (auto & [id, input] : overrides) {
auto inputPath(inputPathPrefix);
inputPath.push_back(id);
// Do not override existing assignment from outer flake
overrideMap.insert({inputPath, input});
updateOverrides(overrideMap, input.overrides, inputPath);
}
}

/* Compute an in-memory lock file for the specified top-level flake,
and optionally write it to file, if the flake is writable. */
LockedFlake lockFlake(
Expand Down Expand Up @@ -376,12 +394,9 @@ LockedFlake lockFlake(
/* Get the overrides (i.e. attributes of the form
'inputs.nixops.inputs.nixpkgs.url = ...'). */
for (auto & [id, input] : flakeInputs) {
for (auto & [idOverride, inputOverride] : input.overrides) {
auto inputPath(inputPathPrefix);
inputPath.push_back(id);
inputPath.push_back(idOverride);
overrides.insert_or_assign(inputPath, inputOverride);
}
auto inputPath(inputPathPrefix);
inputPath.push_back(id);
updateOverrides(overrides, input.overrides, inputPath);
}

/* Check whether this input has overrides for a
Expand Down Expand Up @@ -416,6 +431,12 @@ LockedFlake lockFlake(
// Respect the “flakeness” of the input even if we
// override it
i->second.isFlake = input2.isFlake;
if (!i->second.ref)
i->second.ref = input2.ref;
if (!i->second.follows)
i->second.follows = input2.follows;
// Note that `input.overrides` is not used in the following,
// so no need to merge it here (already done by `updateOverrides`)
}
auto & input = hasOverride ? i->second : input2;

Expand Down
30 changes: 30 additions & 0 deletions tests/flakes/follow-paths.sh
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,33 @@ git -C $flakeFollowsA add flake.nix

nix flake lock $flakeFollowsA 2>&1 | grep "warning: input 'B' has an override for a non-existent input 'invalid'"
nix flake lock $flakeFollowsA 2>&1 | grep "warning: input 'B' has an override for a non-existent input 'invalid2'"

# Test nested flake overrides

cat <<EOF > $flakeFollowsD/flake.nix
{ outputs = _: {}; }
EOF
cat <<EOF > $flakeFollowsC/flake.nix
{
inputs.D.url = "path:nosuchflake";
outputs = _: {};
}
EOF
cat <<EOF > $flakeFollowsB/flake.nix
{
inputs.C.url = "path:$flakeFollowsC";
outputs = _: {};
}
EOF
cat <<EOF > $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"]' ]]

0 comments on commit c043a27

Please sign in to comment.