diff --git a/src/libflake/flake-primops.cc b/src/libflake/flake-primops.cc index 754a2f72b60..4d27c684827 100644 --- a/src/libflake/flake-primops.cc +++ b/src/libflake/flake-primops.cc @@ -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{ diff --git a/src/libflake/flake.cc b/src/libflake/flake.cc index 66d18f09f1a..5833c79d76d 100644 --- a/src/libflake/flake.cc +++ b/src/libflake/flake.cc @@ -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(); @@ -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 makeInternalFS() { auto internalFS = make_ref(MemorySourceAccessor{}); diff --git a/src/libflake/include/nix/flake/flake.hh b/src/libflake/include/nix/flake/flake.hh index 0ca6094175e..962933b97bd 100644 --- a/src/libflake/include/nix/flake/flake.hh +++ b/src/libflake/include/nix/flake/flake.hh @@ -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); /** diff --git a/tests/functional/flakes/get-flake.sh b/tests/functional/flakes/get-flake.sh index 4fae472079d..1e130317ec1 100644 --- a/tests/functional/flakes/get-flake.sh +++ b/tests/functional/flakes/get-flake.sh @@ -9,9 +9,13 @@ cat > "$flake1Dir/subflake/flake.nix" <