Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 33 additions & 30 deletions src/libflake/flake-primops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,36 +35,39 @@ namespace nix::flake::primops {
PrimOp getFlake(const Settings & settings)
{
auto prim_getFlake = [&settings](EvalState & state, const PosIdx pos, Value ** args, Value & v) {
NixStringContext context;
std::string flakeRefS(
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getFlake"));
auto rewrites = state.realiseContext(context);
flakeRefS = state.devirtualize(rewriteStrings(flakeRefS, rewrites), context);
if (hasContext(context))
// FIXME: this should really be an error.
warn(
"In 'builtins.getFlake', the flakeref '%s' has string context, but that's not allowed. This may become a fatal error in the future.",
flakeRefS);
auto flakeRef = nix::parseFlakeRef(state.fetchSettings, flakeRefS, {}, true);
if (state.settings.pureEval && !flakeRef.input.isLocked(state.fetchSettings))
throw Error(
"cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)",
flakeRefS,
state.positions[pos]);

callFlake(
state,
lockFlake(
settings,
state,
flakeRef,
LockFlags{
.updateLockFile = false,
.writeLockFile = false,
.useRegistries = !state.settings.pureEval && settings.useRegistries,
.allowUnlocked = !state.settings.pureEval,
}),
v);
state.forceValue(*args[0], pos);

LockFlags lockFlags{
.updateLockFile = false,
.writeLockFile = false,
.useRegistries = !state.settings.pureEval && settings.useRegistries,
.allowUnlocked = !state.settings.pureEval,
};

if (args[0]->type() == nPath) {
auto path = state.realisePath(pos, *args[0]);
callFlake(state, lockFlake(settings, state, path, lockFlags), v);
} else {
NixStringContext context;
std::string flakeRefS(
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getFlake"));
auto rewrites = state.realiseContext(context);
flakeRefS = state.devirtualize(rewriteStrings(flakeRefS, rewrites), context);
if (hasContext(context))
// FIXME: this should really be an error.
warn(
"In 'builtins.getFlake', the flakeref '%s' has string context, but that's not allowed. This may become a fatal error in the future.",
flakeRefS);

auto flakeRef = nix::parseFlakeRef(state.fetchSettings, flakeRefS, {}, true);
if (state.settings.pureEval && !flakeRef.input.isLocked(state.fetchSettings))
throw Error(
"cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)",
flakeRefS,
state.positions[pos]);

callFlake(state, lockFlake(settings, state, flakeRef, lockFlags), v);
}
};

return PrimOp{
Expand Down
24 changes: 18 additions & 6 deletions src/libflake/flake.cc
Original file line number Diff line number Diff line change
Expand Up @@ -427,17 +427,13 @@ static LockFile readLockFile(const fetchers::Settings & fetchSettings, const Sou
: LockFile();
}

/* 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(const Settings & settings, EvalState & state, const FlakeRef & topRef, const LockFlags & lockFlags)
LockedFlake lockFlake(
const Settings & settings, EvalState & state, const FlakeRef & topRef, const LockFlags & lockFlags, Flake flake)
{
auto useRegistries = lockFlags.useRegistries.value_or(settings.useRegistries);
auto useRegistriesTop = useRegistries ? fetchers::UseRegistries::All : fetchers::UseRegistries::No;
auto useRegistriesInputs = useRegistries ? fetchers::UseRegistries::Limited : fetchers::UseRegistries::No;

auto flake = getFlake(state, topRef, useRegistriesTop, {}, false);

if (lockFlags.applyNixConfig) {
flake.config.apply(settings);
state.store->setOptions();
Expand Down Expand Up @@ -951,6 +947,22 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef,
}
}

LockedFlake
lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef, const LockFlags & lockFlags)
{
auto useRegistries = lockFlags.useRegistries.value_or(settings.useRegistries);
auto useRegistriesTop = useRegistries ? fetchers::UseRegistries::All : fetchers::UseRegistries::No;
return lockFlake(settings, state, topRef, lockFlags, getFlake(state, topRef, useRegistriesTop, {}, false));
}

LockedFlake
lockFlake(const Settings & settings, EvalState & state, const SourcePath & flakeDir, const LockFlags & lockFlags)
{
/* We need a fake flakeref to put in the `Flake` struct, but it's not used for anything. */
auto fakeRef = parseFlakeRef(state.fetchSettings, "flake:get-flake");
return lockFlake(settings, state, fakeRef, lockFlags, readFlake(state, fakeRef, fakeRef, fakeRef, flakeDir, {}));
}

static ref<SourceAccessor> makeInternalFS()
{
auto internalFS = make_ref<MemorySourceAccessor>(MemorySourceAccessor{});
Expand Down
7 changes: 7 additions & 0 deletions src/libflake/include/nix/flake/flake.hh
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,16 @@ struct LockFlags
bool requireLockable = true;
};

/*
* 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(const Settings & settings, EvalState & state, const FlakeRef & flakeRef, const LockFlags & lockFlags);

LockedFlake
lockFlake(const Settings & settings, EvalState & state, const SourcePath & flakeDir, const LockFlags & lockFlags);

void callFlake(EvalState & state, const LockedFlake & lockedFlake, Value & v);

/**
Expand Down
6 changes: 6 additions & 0 deletions tests/functional/flakes/get-flake.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@ cat > "$flake1Dir/subflake/flake.nix" <<EOF
{
outputs = { self }:
let
# Bad, legacy way of getting a flake from an input.
parentFlake = builtins.getFlake (builtins.flakeRefToString { type = "path"; path = self.sourceInfo.outPath; narHash = self.narHash; });
# Better way using a path value.
parentFlake2 = builtins.getFlake ./..;
in {
x = parentFlake.number;
y = parentFlake2.number;
};
}
EOF
git -C "$flake1Dir" add subflake/flake.nix

expectStderr 0 nix eval "$flake1Dir/subflake#x" | grepQuiet "This may become a fatal error in the future"
[[ $(nix eval "$flake1Dir/subflake#x") = 123 ]]

[[ $(nix eval "$flake1Dir/subflake#y") = 123 ]]