From bb3846e6d8d420d808685f959368a90a19d0020a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 6 Feb 2026 14:07:50 +0100 Subject: [PATCH 1/4] builtins.getFlake: Devirtualize strings Allowing strings with context here is probably a mistake since in a chroot evaluation, it's ambiguous what filesystem store paths refer to. Hence the warning. Fixes #302. --- src/libflake/flake-primops.cc | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/libflake/flake-primops.cc b/src/libflake/flake-primops.cc index 3bbe232b0a1..17447f668a5 100644 --- a/src/libflake/flake-primops.cc +++ b/src/libflake/flake-primops.cc @@ -35,8 +35,16 @@ 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.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake")); + 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( @@ -125,6 +133,7 @@ static void prim_flakeRefToString(EvalState & state, const PosIdx pos, Value ** { state.forceAttrs(*args[0], noPos, "while evaluating the argument passed to builtins.flakeRefToString"); fetchers::Attrs attrs; + NixStringContext context; for (const auto & attr : *args[0]->attrs()) { auto t = attr.value->type(); if (t == nInt) { @@ -142,7 +151,9 @@ static void prim_flakeRefToString(EvalState & state, const PosIdx pos, Value ** } else if (t == nBool) { attrs.emplace(state.symbols[attr.name], Explicit{attr.value->boolean()}); } else if (t == nString) { - attrs.emplace(state.symbols[attr.name], std::string(attr.value->string_view())); + auto s = state.forceString( + *attr.value, context, attr.pos, "while evaluating an attribute in 'builtins.flakeRefToString'"); + attrs.emplace(state.symbols[attr.name], std::string(s)); } else { state .error( @@ -154,7 +165,7 @@ static void prim_flakeRefToString(EvalState & state, const PosIdx pos, Value ** } } auto flakeRef = FlakeRef::fromAttrs(state.fetchSettings, attrs); - v.mkString(flakeRef.to_string(), state.mem); + v.mkString(flakeRef.to_string(), context, state.mem); } nix::PrimOp flakeRefToString({ From 7e9bec346412ac936d5543de9076560a2dadd011 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 6 Feb 2026 14:42:49 +0100 Subject: [PATCH 2/4] fetchTree: Return a lazily computed narHash attribute for compatibility --- src/libexpr/primops.cc | 17 +++++++++++++++++ src/libexpr/primops/fetchTree.cc | 6 +++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 3254ad0bc12..ae0d50c2554 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -2360,6 +2360,23 @@ static RegisterPrimOp primop_hashFile({ .fun = prim_hashFile, }); +static RegisterPrimOp primop_narHash({ + .name = "__narHash", + .args = {"p"}, + .doc = R"( + Return an SRI representation of the SHA-256 hash of the NAR serialisation of the path *p*. + )", + .fun = + [](EvalState & state, const PosIdx pos, Value ** args, Value & v) { + auto path = state.realisePath(pos, *args[0]); + auto hash = + fetchToStore2(state.fetchSettings, *state.store, path.resolveSymlinks(), FetchMode::DryRun).second; + v.mkString(hash.to_string(HashFormat::SRI, true), state.mem); + }, + // FIXME: may be useful to expose to the user. + .internal = true, +}); + static const Value & fileTypeToString(EvalState & state, SourceAccessor::Type type) { struct Constants diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index dda81b9d328..691d13404f6 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -30,12 +30,16 @@ void emitTreeAttrs( { auto attrs = state.buildBindings(100); - state.mkStorePathString(storePath, attrs.alloc(state.s.outPath)); + auto & vStorePath = attrs.alloc(state.s.outPath); + state.mkStorePathString(storePath, vStorePath); // FIXME: support arbitrary input attributes. if (auto narHash = input.getNarHash()) attrs.alloc("narHash").mkString(narHash->to_string(HashFormat::SRI, true), state.mem); + else + // Lazily compute the NAR hash for backward compatibility. + attrs.alloc("narHash").mkApp(*get(state.internalPrimOps, "narHash"), &vStorePath); if (input.getType() == "git") attrs.alloc("submodules").mkBool(fetchers::maybeGetBoolAttr(input.attrs, "submodules").value_or(false)); From c4830a325676f331ca89926532fab768d352ab1e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 6 Feb 2026 14:55:57 +0100 Subject: [PATCH 3/4] builtins.flakeRefToString: Evaluate attributes Fixes "attribute 'x' is a thunk". --- src/libflake/flake-primops.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libflake/flake-primops.cc b/src/libflake/flake-primops.cc index 17447f668a5..754a2f72b60 100644 --- a/src/libflake/flake-primops.cc +++ b/src/libflake/flake-primops.cc @@ -135,6 +135,7 @@ static void prim_flakeRefToString(EvalState & state, const PosIdx pos, Value ** fetchers::Attrs attrs; NixStringContext context; for (const auto & attr : *args[0]->attrs()) { + state.forceValue(*attr.value, attr.pos); auto t = attr.value->type(); if (t == nInt) { auto intValue = attr.value->integer().value; From 850279b4f8edfc5f728a6ad3f89a532a1bf8ed89 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 6 Feb 2026 14:58:27 +0100 Subject: [PATCH 4/4] Add test for builtins.getFlake --- tests/functional/flakes/get-flake.sh | 21 +++++++++++++++++++++ tests/functional/flakes/meson.build | 1 + 2 files changed, 22 insertions(+) create mode 100644 tests/functional/flakes/get-flake.sh diff --git a/tests/functional/flakes/get-flake.sh b/tests/functional/flakes/get-flake.sh new file mode 100644 index 00000000000..4fae472079d --- /dev/null +++ b/tests/functional/flakes/get-flake.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +source ./common.sh + +createFlake1 + +mkdir -p "$flake1Dir/subflake" +cat > "$flake1Dir/subflake/flake.nix" <