From 9868310d6f2d6a2225eaf9d48c94b88b67cc9ac5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 6 Feb 2026 14:58:27 +0100 Subject: [PATCH 1/3] Add test for builtins.getFlake --- tests/functional/flakes/common.sh | 2 ++ tests/functional/flakes/get-flake.sh | 20 ++++++++++++++++++++ tests/functional/flakes/meson.build | 1 + 3 files changed, 23 insertions(+) create mode 100644 tests/functional/flakes/get-flake.sh diff --git a/tests/functional/flakes/common.sh b/tests/functional/flakes/common.sh index 2dcf2e0fd54..6fef7892559 100644 --- a/tests/functional/flakes/common.sh +++ b/tests/functional/flakes/common.sh @@ -32,6 +32,8 @@ writeSimpleFlake() { baseName = builtins.baseNameOf ./.; root = ./.; + + number = 123; }; } EOF diff --git a/tests/functional/flakes/get-flake.sh b/tests/functional/flakes/get-flake.sh new file mode 100644 index 00000000000..85ef8c23d1a --- /dev/null +++ b/tests/functional/flakes/get-flake.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +source ./common.sh + +createFlake1 + +mkdir -p "$flake1Dir/subflake" +cat > "$flake1Dir/subflake/flake.nix" < Date: Fri, 6 Feb 2026 15:48:47 +0100 Subject: [PATCH 2/3] builtins.getFlake: Support path values This allows doing `builtins.getFlake ./subflake` instead of ugly hacks. --- src/libflake/flake-primops.cc | 50 +++++++++++++------------ src/libflake/flake.cc | 24 +++++++++--- src/libflake/include/nix/flake/flake.hh | 7 ++++ tests/functional/flakes/get-flake.sh | 6 +++ 4 files changed, 58 insertions(+), 29 deletions(-) diff --git a/src/libflake/flake-primops.cc b/src/libflake/flake-primops.cc index a43777574f7..6ff66a964e9 100644 --- a/src/libflake/flake-primops.cc +++ b/src/libflake/flake-primops.cc @@ -35,35 +35,39 @@ namespace nix::flake::primops { PrimOp getFlake(const Settings & settings) { auto prim_getFlake = [&settings](EvalState & state, const PosIdx pos, Value ** args, Value & v) { - std::string flakeRefS( - state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake")); - 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.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake")); + + 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{ .name = "__getFlake", .args = {"args"}, .doc = R"( - Fetch a flake from a flake reference, and return its output attributes and some metadata. For example: + Fetch a flake from a flake reference or a path, and return its output attributes and some metadata. For example: ```nix (builtins.getFlake "nix/55bc52401966fbffa525c574c14f67b00bc4fb3a").packages.x86_64-linux.nix diff --git a/src/libflake/flake.cc b/src/libflake/flake.cc index 9dab20c5c10..13999a5c30f 100644 --- a/src/libflake/flake.cc +++ b/src/libflake/flake.cc @@ -416,10 +416,8 @@ 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) { experimentalFeatureSettings.require(Xp::Flakes); @@ -427,8 +425,6 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef, auto useRegistriesTop = useRegistries ? fetchers::UseRegistries::All : fetchers::UseRegistries::No; auto useRegistriesInputs = useRegistries ? fetchers::UseRegistries::Limited : fetchers::UseRegistries::No; - auto flake = getFlake(state, topRef, useRegistriesTop, {}); - if (lockFlags.applyNixConfig) { flake.config.apply(settings); state.store->setOptions(); @@ -908,6 +904,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, {})); +} + +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 3bee6556f64..fd52dbebac5 100644 --- a/src/libflake/include/nix/flake/flake.hh +++ b/src/libflake/include/nix/flake/flake.hh @@ -214,9 +214,16 @@ struct LockFlags std::set inputUpdates; }; +/* + * 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 85ef8c23d1a..e462607db8f 100644 --- a/tests/functional/flakes/get-flake.sh +++ b/tests/functional/flakes/get-flake.sh @@ -9,12 +9,18 @@ cat > "$flake1Dir/subflake/flake.nix" < Date: Wed, 18 Feb 2026 22:50:37 +0100 Subject: [PATCH 3/3] Add release note --- doc/manual/rl-next/getflake-path.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 doc/manual/rl-next/getflake-path.md diff --git a/doc/manual/rl-next/getflake-path.md b/doc/manual/rl-next/getflake-path.md new file mode 100644 index 00000000000..2360fe7693e --- /dev/null +++ b/doc/manual/rl-next/getflake-path.md @@ -0,0 +1,6 @@ +--- +synopsis: "`builtins.getFlake` now supports path values" +prs: [15290] +--- + +`builtins.getFlake` now accepts path values in addition to flakerefs, allowing you to write `builtins.getFlake ./subflake` instead of having to use ugly workarounds to construct a pure flakeref.