Skip to content

Commit 1deca0c

Browse files
committed
builtins.getFlake: Allow inputs to overridden
This uses the same syntax as flake inputs in flake.nix, e.g. builtins.getFlake { url = "github:NixOS/nix/55bc52401966fbffa525c574c14f67b00bc4fb3a"; inputs.nixpkgs.url = "github:NixOS/nixpkgs/c69a9bffbecde46b4b939465422ddc59493d3e4d"; } Note that currently it's not supported to set `flake = false` or to use `follows` (because lockFlake() doesn't support that for CLI overrides), but it could be implemented in the future. Fixes NixOS#9154.
1 parent 2b62625 commit 1deca0c

File tree

3 files changed

+100
-19
lines changed

3 files changed

+100
-19
lines changed

src/libflake/flake/flake.cc

Lines changed: 73 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
9999
const std::optional<Path> & baseDir, InputPath lockRootPath);
100100

101101
static FlakeInput parseFlakeInput(EvalState & state,
102-
std::string_view inputName, Value * value, const PosIdx pos,
102+
std::optional<std::string_view> inputName, Value * value, const PosIdx pos,
103103
const std::optional<Path> & baseDir, InputPath lockRootPath)
104104
{
105105
expectType(state, nAttrs, *value, pos);
@@ -185,8 +185,8 @@ static FlakeInput parseFlakeInput(EvalState & state,
185185
input.ref = parseFlakeRef(state.fetchSettings, *url, baseDir, true, input.isFlake);
186186
}
187187

188-
if (!input.follows && !input.ref)
189-
input.ref = FlakeRef::fromAttrs(state.fetchSettings, {{"type", "indirect"}, {"id", std::string(inputName)}});
188+
if (inputName && !input.follows && !input.ref)
189+
input.ref = FlakeRef::fromAttrs(state.fetchSettings, {{"type", "indirect"}, {"id", std::string(*inputName)}});
190190

191191
return input;
192192
}
@@ -735,7 +735,10 @@ LockedFlake lockFlake(
735735
} else
736736
throw Error("cannot write modified lock file of flake '%s' (use '--no-write-lock-file' to ignore)", topRef);
737737
} else {
738-
warn("not writing modified lock file of flake '%s':\n%s", topRef, chomp(diff));
738+
if (lockFlags.warnModifiedLockFile)
739+
warn("not writing modified lock file of flake '%s':\n%s", topRef, chomp(diff));
740+
else
741+
debug("not writing modified lock file of flake '%s':\n%s", topRef, chomp(diff));
739742
flake.forceDirty = true;
740743
}
741744
}
@@ -823,20 +826,63 @@ void initLib(const Settings & settings)
823826
{
824827
auto prim_getFlake = [&settings](EvalState & state, const PosIdx pos, Value * * args, Value & v)
825828
{
826-
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake"));
827-
auto flakeRef = parseFlakeRef(state.fetchSettings, flakeRefS, {}, true);
828-
if (state.settings.pureEval && !flakeRef.input.isLocked())
829-
throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, state.positions[pos]);
830-
831-
callFlake(state,
832-
lockFlake(settings, state, flakeRef,
833-
LockFlags {
834-
.updateLockFile = false,
835-
.writeLockFile = false,
836-
.useRegistries = !state.settings.pureEval && settings.useRegistries,
837-
.allowUnlocked = !state.settings.pureEval,
838-
}),
839-
v);
829+
state.forceValue(*args[0], pos);
830+
831+
LockFlags lockFlags {
832+
.updateLockFile = false,
833+
.writeLockFile = false,
834+
.warnModifiedLockFile = false,
835+
.useRegistries = !state.settings.pureEval && settings.useRegistries,
836+
.allowUnlocked = !state.settings.pureEval,
837+
};
838+
839+
auto flakeRef =
840+
args[0]->type() == nString
841+
? ({
842+
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake"));
843+
auto flakeRef = parseFlakeRef(state.fetchSettings, flakeRefS, {}, true);
844+
if (state.settings.pureEval && !flakeRef.input.isLocked())
845+
throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, state.positions[pos]);
846+
flakeRef;
847+
})
848+
: ({
849+
auto flakeInput = parseFlakeInput(state, std::nullopt, args[0], pos, {}, {});
850+
851+
/* Convert the result of parseFlakeInput() into a
852+
overrides map and a top-level flakeref. */
853+
std::function<void(const InputPath & inputPath, const FlakeInput & input)> recurse;
854+
855+
recurse = [&](const InputPath & inputPath, const FlakeInput & input)
856+
{
857+
if (!input.ref)
858+
state.error<EvalError>("'builtins.getFlake' requires attribute 'url'")
859+
.atPos(*args[0])
860+
.debugThrow();
861+
if (input.follows)
862+
state.error<EvalError>("'builtins.getFlake' does not permit attribute 'follows'")
863+
.atPos(*args[0])
864+
.debugThrow();
865+
if (!input.isFlake)
866+
state.error<EvalError>("'builtins.getFlake' does not permit attribute 'flake = false'; use 'builtins.fetchTree' instead")
867+
.atPos(*args[0])
868+
.debugThrow();
869+
870+
for (auto & [inputName, input2] : input.overrides) {
871+
auto inputPath2{inputPath};
872+
inputPath2.push_back(inputName);
873+
874+
recurse(inputPath2, input2);
875+
876+
lockFlags.inputOverrides.insert_or_assign(inputPath2, input2.ref.value());
877+
}
878+
};
879+
880+
recurse({}, flakeInput);
881+
882+
flakeInput.ref.value();
883+
});
884+
885+
callFlake(state, lockFlake(settings, state, flakeRef, lockFlags), v);
840886
};
841887

842888
RegisterPrimOp::primOps->push_back({
@@ -856,6 +902,15 @@ void initLib(const Settings & settings)
856902
```nix
857903
(builtins.getFlake "github:edolstra/dwarffs").rev
858904
```
905+
906+
It is possible to override inputs of the flake using the same syntax to specify flake inputs in `flake.nix`, e.g.
907+
908+
```nix
909+
builtins.getFlake {
910+
url = "github:NixOS/nix/55bc52401966fbffa525c574c14f67b00bc4fb3a";
911+
inputs.nixpkgs.url = "github:NixOS/nixpkgs/c69a9bffbecde46b4b939465422ddc59493d3e4d";
912+
}
913+
```
859914
)",
860915
.fun = prim_getFlake,
861916
.experimentalFeature = Xp::Flakes,

src/libflake/flake/flake.hh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ struct Flake
9292
*/
9393
SourcePath path;
9494
/**
95-
* pretend that 'lockedRef' is dirty
95+
* Pretend that 'lockedRef' is dirty.
9696
*/
9797
bool forceDirty = false;
9898
std::optional<std::string> description;
@@ -156,6 +156,12 @@ struct LockFlags
156156
*/
157157
bool writeLockFile = true;
158158

159+
/**
160+
* When `writeLockFile` is false, whether we're warning about
161+
* modified lock files.
162+
*/
163+
bool warnModifiedLockFile = true;
164+
159165
/**
160166
* Whether to use the registries to lookup indirect flake
161167
* references like 'nixpkgs'.

tests/functional/flakes/get-flake.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,23 @@ nix build -o "$TEST_ROOT/result" --expr "(builtins.getFlake \"git+file://$flake1
2121

2222
# But should succeed in impure mode.
2323
nix eval --expr "builtins.getFlake \"$flake2Dir\"" --impure
24+
25+
# Test overrides in getFlake.
26+
flake1Copy="$flake1Dir-copy"
27+
rm -rf "$flake1Copy"
28+
cp -r "$flake1Dir" "$flake1Copy"
29+
sed -i "$flake1Copy/simple.builder.sh" -e 's/World/Universe/'
30+
31+
# Should fail in pure mode since the override is unlocked.
32+
(! nix build -o "$TEST_ROOT/result" --expr "(builtins.getFlake { url = \"$flake2Dir\"; inputs.flake1.url = \"$flake1Copy\"; }).packages.$system.bar")
33+
34+
# Should succeed in impure mode.
35+
nix build -o "$TEST_ROOT/result" --expr "(builtins.getFlake { url = \"$flake2Dir\"; inputs.flake1.url = \"$flake1Copy\"; }).packages.$system.bar" --impure
36+
[[ $(cat "$TEST_ROOT/result/hello") = 'Hello Universe!' ]]
37+
38+
# Should succeed if we lock the override.
39+
git -C "$flake1Copy" commit -a -m 'bla'
40+
41+
flake1CopyLocked="$(nix flake metadata --json "$flake1Copy" | jq -r .url)"
42+
43+
nix build -o "$TEST_ROOT/result" --expr "(builtins.getFlake { url = \"$flake2Dir\"; inputs.flake1.url = \"$flake1CopyLocked\"; }).packages.$system.bar"

0 commit comments

Comments
 (0)