From 4548e4a354475dafb0ffefb70d4979ee10f023e0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 21 Apr 2026 20:18:30 +0200 Subject: [PATCH 01/15] AttrPath::to_string(): Escape attribute names properly --- src/libexpr/attr-path.cc | 8 +++++++- tests/functional/flakes/dubious-query.sh | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc index 63197e4634fd..0a8c9f2c2c07 100644 --- a/src/libexpr/attr-path.cc +++ b/src/libexpr/attr-path.cc @@ -50,7 +50,13 @@ AttrPath AttrPath::fromStrings(EvalState & state, const std::vector std::string AttrPath::to_string(EvalState & state) const { - return dropEmptyInitThenConcatStringsSep(".", state.symbols.resolve({*this})); + std::ostringstream ss; + for (const auto & [idx, attr] : enumerate(state.symbols.resolve({*this}))) { + if (idx) + ss << '.'; + printAttributeName(ss, attr); + } + return ss.str(); } std::vector AttrPath::resolve(EvalState & state) const diff --git a/tests/functional/flakes/dubious-query.sh b/tests/functional/flakes/dubious-query.sh index 114def179a8c..bda5b8bf50f4 100644 --- a/tests/functional/flakes/dubious-query.sh +++ b/tests/functional/flakes/dubious-query.sh @@ -17,7 +17,7 @@ expectStderr 0 nix --offline build --dry-run "git+file://$repoDir?bar#foo" \ # Check that the anchor (#) is taken as a whole, not split, and throws an error. expectStderr 1 nix --offline build --dry-run "git+file://$repoDir#foo?bar" \ - | grepQuiet "error: flake 'git+file://$repoDir' does not provide attribute 'packages.$system.foo?bar', 'defaultPackage.$system.foo?bar', 'legacyPackages.$system.foo?bar' or 'foo?bar'" + | grepQuiet "error: flake 'git+file://$repoDir' does not provide attribute 'packages.$system.\"foo?bar\"', 'defaultPackage.$system.\"foo?bar\"', 'legacyPackages.$system.\"foo?bar\"' or '\"foo?bar\"'" # Check that a literal `?` in the query doesn't print dubious query warning. expectStderr 0 nix --offline build --dry-run "git+file://$repoDir?#foo" \ From 901c231752088eb5839593e27fcc9da76238993f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 21 Apr 2026 20:19:00 +0200 Subject: [PATCH 02/15] WIP --- src/libexpr/primops/fakeDerivation.cc | 136 ++++++++++++++++++++++++++ src/libexpr/primops/meson.build | 1 + src/libstore/derivations.cc | 3 + src/nix/flake-bake.cc | 129 ++++++++++++++++++++++++ src/nix/flake-bake.md | 7 ++ src/nix/meson.build | 1 + 6 files changed, 277 insertions(+) create mode 100644 src/libexpr/primops/fakeDerivation.cc create mode 100644 src/nix/flake-bake.cc create mode 100644 src/nix/flake-bake.md diff --git a/src/libexpr/primops/fakeDerivation.cc b/src/libexpr/primops/fakeDerivation.cc new file mode 100644 index 000000000000..046c8db1949e --- /dev/null +++ b/src/libexpr/primops/fakeDerivation.cc @@ -0,0 +1,136 @@ +#include "nix/expr/primops.hh" +#include "nix/expr/eval-inline.hh" +#include "nix/store/derivations.hh" +#include "nix/store/store-api.hh" +#include "nix/util/hash.hh" + +namespace nix { + +static void prim_fakeDerivation(EvalState & state, const PosIdx pos, Value ** args, Value & v) +{ + state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fakeDerivation"); + + std::optional name; + + struct FakeDerivationOutput + { + StorePath path; + std::optional narHash; + }; + + std::map outputs; + + for (auto & attr : *args[0]->attrs()) { + std::string_view attrName = state.symbols[attr.name]; + auto attrHint = fmt("while evaluating the attribute '%s' passed to builtins.fakeDerivation", attrName); + + if (attrName == "name") { + name = state.forceStringNoCtx(*attr.value, attr.pos, attrHint); + } + + else if (attrName == "outputs") { + state.forceAttrs(*attr.value, attr.pos, attrHint); + for (auto & outAttr : *attr.value->attrs()) { + std::string_view outName = state.symbols[outAttr.name]; + + state.forceAttrs( + *outAttr.value, + outAttr.pos, + fmt("while evaluating the output '%s' passed to builtins.fakeDerivation", outName)); + + std::optional path; + std::optional narHash; + + for (auto & outField : *outAttr.value->attrs()) { + std::string_view fieldName = state.symbols[outField.name]; + auto fieldHint = + fmt("while evaluating the attribute '%s' of output '%s' passed to builtins.fakeDerivation", + fieldName, + outName); + + if (fieldName == "path") { + auto s = state.forceStringNoCtx(*outField.value, outField.pos, fieldHint); + path = state.store->parseStorePath(s); + } + + else if (fieldName == "narHash") { + auto s = state.forceStringNoCtx(*outField.value, outField.pos, fieldHint); + narHash = Hash::parseAny(s, std::nullopt); + } + + else + state + .error( + "attribute '%s' isn't supported in an output passed to 'builtins.fakeDerivation'", + fieldName) + .atPos(outField.pos) + .debugThrow(); + } + + if (!path) + state + .error( + "attribute 'path' is missing in output '%s' passed to 'builtins.fakeDerivation'", outName) + .atPos(outAttr.pos) + .debugThrow(); + + outputs.emplace(std::string(outName), FakeDerivationOutput{std::move(*path), std::move(narHash)}); + } + } + + else + state.error("attribute '%s' isn't supported in call to 'builtins.fakeDerivation'", attrName) + .atPos(attr.pos) + .debugThrow(); + } + + if (!name) + state.error("attribute 'name' is missing in call to 'builtins.fakeDerivation'") + .atPos(pos) + .debugThrow(); + + if (outputs.empty()) + state.error("attribute 'outputs' is missing or empty in call to 'builtins.fakeDerivation'") + .atPos(pos) + .debugThrow(); + + Derivation drv; + drv.name = *name; + drv.platform = "builtin"; + drv.builder = "builtin:fake-derivation"; + + for (auto & [outName, out] : outputs) { +#if 0 + if (out.narHash) + drv.outputs.insert_or_assign( + outName, + DerivationOutput::CAFixed{ + .ca = + ContentAddress{ + .method = ContentAddressMethod::Raw::NixArchive, + .hash = *out.narHash, + }, + }); + else +#endif + drv.outputs.insert_or_assign(outName, DerivationOutput::InputAddressed{.path = out.path}); + } + + auto drvPath = writeDerivation(*state.store, *state.asyncPathWriter, drv, state.repair); + + // FIXME + state.waitForPath(drvPath); + + state.mkStorePathString(drvPath, v); +} + +static RegisterPrimOp primop_fakeDerivation({ + .name = "__fakeDerivation", + .args = {"attrs"}, + .doc = R"( + Placeholder. + )", + .fun = prim_fakeDerivation, +}); + +} // namespace nix diff --git a/src/libexpr/primops/meson.build b/src/libexpr/primops/meson.build index d62b6df4ea20..78f40d331ec3 100644 --- a/src/libexpr/primops/meson.build +++ b/src/libexpr/primops/meson.build @@ -5,6 +5,7 @@ generated_headers += gen_header.process( sources += files( 'context.cc', + 'fakeDerivation.cc', 'fetchClosure.cc', 'fetchMercurial.cc', 'fetchTree.cc', diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index 719f238549e3..06fa0d44b42d 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -1327,6 +1327,7 @@ static void processDerivationOutputPaths(Store & store, auto && drv, std::string auto outPath = store.makeOutputPath(outputName, *h, drvName); if constexpr (std::is_same_v) { +#if 0 if (outputVariant.path == outPath) { return; // Correct case } @@ -1336,6 +1337,8 @@ static void processDerivationOutputPaths(Store & store, auto && drv, std::string "derivation has incorrect output '%s', should be '%s'", store.printStorePath(outputVariant.path), store.printStorePath(outPath)); +#endif + return; // FIXME } else if constexpr (std::is_same_v) { if constexpr (fillIn) /* Fill in output path for Deferred diff --git a/src/nix/flake-bake.cc b/src/nix/flake-bake.cc new file mode 100644 index 000000000000..fd6f905fb637 --- /dev/null +++ b/src/nix/flake-bake.cc @@ -0,0 +1,129 @@ +#include "flake-command.hh" +#include "nix/cmd/flake-schemas.hh" +#include "nix/expr/parallel-eval.hh" +#include "nix/store/derivations.hh" + +using namespace nix; +using namespace nix::flake; + +struct CmdFlakeBake : FlakeCommand, MixFlakeSchemas +{ + std::string description() override + { + return "bake a flake"; + } + + std::string doc() override + { + return +#include "flake-bake.md" + ; + } + + void run(nix::ref store) override + { + auto state = getEvalState(); + auto evalStore = getEvalStore(); + auto flake = make_ref(lockFlake()); + + auto cache = flake_schemas::call(*state, flake, getDefaultFlakeSchemas()); + + auto inventory = cache->getRoot()->getAttr("inventory"); + auto outputs = cache->getRoot()->getAttr("outputs"); + + FutureVector futures(*state->executor); + + // FIXME: generate an AST. + Sync result; + + *result.lock() = R""( +{ + outputs = + { self }: + { +)""; + + auto visit = [&](this auto & visit, ref node) -> void { + flake_schemas::visit( + {"x86_64-linux"}, // FIXME + true, + node, + flake->flake.provenance, + + [&](const flake_schemas::Leaf & leaf) { + printError("AT %s", leaf.node->getAttrPathStr()); + + auto drv = leaf.derivation(outputs); + if (!drv) + // FIXME: emit attrs that throw a useful error? + return; + + // FIXME: do we need to force the derivation? + auto drvPath = drv->forceDerivation(); + auto storeDrv = evalStore->derivationFromPath(drvPath); + std::map outputs; + for (auto & i : storeDrv.outputsAndOptPaths(*store)) { + if (auto outPath = i.second.second) + outputs.emplace(i.first, *outPath); + else + // FIXME: we could build the derivation to get the outputs. + return; + } + + auto name = drv->getAttr(state->s.name)->getString(); + + auto r(result.lock()); + *r += " " + leaf.node->getAttrPathStr() + " = {\n"; + *r += " drvPath = builtins.fakeDerivation {\n"; + *r += " name = \"" + name + "\";\n"; + for (auto & i : outputs) + *r += " outputs." + i.first + ".path = \"" + store->printStorePath(i.second) + "\";\n"; + *r += " };\n"; + *r += " type = \"derivation\";\n"; + *r += " name = \"" + name + "\";\n"; + *r += " system = \"x86_64-linux\";\n"; // FIXME + // meta.description = "Docker image with Nix for x86_64-linux"; + *r += " };\n\n"; + }, + + [&](std::function forEachChild) { + forEachChild([&](Symbol attrName, ref node, bool isLast) { + state->spawn(futures, 1, [&visit, node]() { + try { + visit(node); + } catch (EvalError & e) { + // FIXME: make it a flake schema attribute whether to ignore evaluation errors. + if (node->root->state.symbols[node->getAttrPath()[0]] != "legacyPackages") + throw; + } + }); + }); + }, + + [&](ref node, const std::vector & systems) {}, + + [&](ref node) {}); + }; + + flake_schemas::forEachOutput( + inventory, + [&](Symbol outputName, + std::shared_ptr output, + const std::string & doc, + bool isLast) { + if (output) + state->spawn(futures, 1, [&visit, output]() { visit(ref(output)); }); + }); + + futures.finishAll(); + + *result.lock() += R""( + }; +} +)""; + + logger->cout(*result.lock()); + } +}; + +static auto rCmdFlakeBake = registerCommand2({"flake", "bake"}); diff --git a/src/nix/flake-bake.md b/src/nix/flake-bake.md new file mode 100644 index 000000000000..e0a6d5872e1c --- /dev/null +++ b/src/nix/flake-bake.md @@ -0,0 +1,7 @@ +R""( + +# Description + +Bake a flake. + +)"" diff --git a/src/nix/meson.build b/src/nix/meson.build index f5602e730013..722d6f636fdf 100644 --- a/src/nix/meson.build +++ b/src/nix/meson.build @@ -89,6 +89,7 @@ nix_sources = [ config_priv_h ] + files( 'edit.cc', 'env.cc', 'eval.cc', + 'flake-bake.cc', 'flake-prefetch-inputs.cc', 'flake-prefetch-inputs.cc', 'flake.cc', From 3ee6df2632ac885c66145763c5dc1ac387933715 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 22 Apr 2026 16:39:46 +0200 Subject: [PATCH 03/15] Move most of `nix flake show` into libcmd --- src/libcmd/flake-schemas.cc | 111 ++++++++++++++++++ src/libcmd/include/nix/cmd/flake-schemas.hh | 20 ++++ src/nix/flake.cc | 119 ++------------------ 3 files changed, 140 insertions(+), 110 deletions(-) diff --git a/src/libcmd/flake-schemas.cc b/src/libcmd/flake-schemas.cc index c8e2992be7e2..1771c47ae1ac 100644 --- a/src/libcmd/flake-schemas.cc +++ b/src/libcmd/flake-schemas.cc @@ -4,6 +4,10 @@ #include "nix/util/memory-source-accessor.hh" #include "nix/util/mounted-source-accessor.hh" #include "nix/flake/provenance.hh" +#include "nix/expr/parallel-eval.hh" +#include "nix/store/globals.hh" + +#include namespace nix::flake_schemas { @@ -360,6 +364,113 @@ Schemas getSchemas(ref inventory) return schemas; } +nlohmann::json getFlakeInventory( + EvalState & state, + Store & evalStore, + LockedFlake & flake, + ref cache, + const FlakeInventoryOptions & options) +{ + auto inventory = cache->getRoot()->getAttr("inventory"); + auto outputs = cache->getRoot()->getAttr("outputs"); + + auto localSystem = std::string(settings.thisSystem.get()); + + FutureVector futures(*state.executor); + + std::function node, nlohmann::json & obj)> visit; + + visit = [&](ref node, nlohmann::json & obj) { + flake_schemas::visit( + options.showAllSystems ? std::optional() : localSystem, + options.showLegacy, + node, + flake.flake.provenance, + + [&](const flake_schemas::Leaf & leaf) { + if (auto what = leaf.what()) + obj.emplace("what", *what); + + if (auto shortDescription = leaf.shortDescription()) + obj.emplace("shortDescription", *shortDescription); + + if (auto drv = leaf.derivation(outputs)) { + auto drvObj = nlohmann::json::object(); + + if (options.showDrvNames) + drvObj.emplace("name", drv->getAttr(state.s.name)->getString()); + + if (options.showDrvPaths) { + auto drvPath = drv->forceDerivation(); + drvObj.emplace("path", state.store->printStorePath(drvPath)); + } + + if (options.showOutputPaths) { + auto outputs = nlohmann::json::object(); + auto drvPath = drv->forceDerivation(); + auto drv = evalStore.derivationFromPath(drvPath); + for (auto & i : drv.outputsAndOptPaths(*state.store)) { + if (auto outPath = i.second.second) + outputs.emplace(i.first, state.store->printStorePath(*outPath)); + else + outputs.emplace(i.first, nullptr); + } + drvObj.emplace("outputs", std::move(outputs)); + } + + obj.emplace("derivation", std::move(drvObj)); + } + + if (auto forSystems = leaf.forSystems()) + obj.emplace("forSystems", *forSystems); + }, + + [&](std::function forEachChild) { + auto children = nlohmann::json::object(); + forEachChild([&](Symbol attrName, ref node, bool isLast) { + auto & j = children.emplace(state.symbols[attrName], nlohmann::json::object()).first.value(); + state.spawn(futures, 1, [&visit, &j, node]() { + try { + visit(node, j); + } catch (EvalError & e) { + // FIXME: make it a flake schema attribute whether to ignore evaluation errors. + if (node->root->state.symbols[node->getAttrPath()[0]] == "legacyPackages") + j.emplace("failed", true); + else + throw; + } + }); + }); + obj.emplace("children", std::move(children)); + }, + + [&](ref node, const std::vector & systems) { + obj.emplace("filtered", true); + }, + + [&](ref node) { obj.emplace("isLegacy", true); }); + }; + + auto inv = nlohmann::json::object(); + + flake_schemas::forEachOutput( + inventory, + [&](Symbol outputName, std::shared_ptr output, const std::string & doc, bool isLast) { + auto & j = inv.emplace(state.symbols[outputName], nlohmann::json::object()).first.value(); + + if (output) { + j.emplace("doc", doc); + auto & j2 = j.emplace("output", nlohmann::json::object()).first.value(); + state.spawn(futures, 1, [&visit, output, &j2]() { visit(ref(output), j2); }); + } else + j.emplace("unknown", true); + }); + + futures.finishAll(); + + return inv; +} + } // namespace nix::flake_schemas namespace nix { diff --git a/src/libcmd/include/nix/cmd/flake-schemas.hh b/src/libcmd/include/nix/cmd/flake-schemas.hh index bfc2a38cdef0..2084ab503642 100644 --- a/src/libcmd/include/nix/cmd/flake-schemas.hh +++ b/src/libcmd/include/nix/cmd/flake-schemas.hh @@ -4,6 +4,10 @@ #include "nix/flake/flake.hh" #include "nix/cmd/command.hh" +namespace nix::flake { +struct LockedFlake; +} + namespace nix::flake_schemas { using namespace eval_cache; @@ -93,4 +97,20 @@ using Schemas = std::map; Schemas getSchemas(ref root); +struct FlakeInventoryOptions +{ + bool showLegacy = false; + bool showAllSystems = false; + bool showOutputPaths = false; + bool showDrvPaths = false; + bool showDrvNames = false; +}; + +nlohmann::json getFlakeInventory( + EvalState & state, + Store & evalStore, + flake::LockedFlake & flake, + ref cache, + const FlakeInventoryOptions & options); + } // namespace nix::flake_schemas diff --git a/src/nix/flake.cc b/src/nix/flake.cc index f50a2b1bc305..57f4977dacea 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -840,38 +840,34 @@ struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun, MixNoCheckSigs struct CmdFlakeShow : FlakeCommand, MixJSON, MixFlakeSchemas { - bool showLegacy = false; - bool showAllSystems = false; - bool showOutputPaths = false; - bool showDrvPaths = false; - bool showDrvNames = false; + flake_schemas::FlakeInventoryOptions options; CmdFlakeShow() { addFlag({ .longName = "legacy", .description = "Show the contents of the `legacyPackages` output.", - .handler = {&showLegacy, true}, + .handler = {&options.showLegacy, true}, }); addFlag({ .longName = "all-systems", .description = "Show the contents of outputs for all systems.", - .handler = {&showAllSystems, true}, + .handler = {&options.showAllSystems, true}, }); addFlag({ .longName = "output-paths", .description = "Include the store paths of derivation outputs in the JSON output.", - .handler = {&showOutputPaths, true}, + .handler = {&options.showOutputPaths, true}, }); addFlag({ .longName = "drv-paths", .description = "Include the store paths of derivations in the JSON output.", - .handler = {&showDrvPaths, true}, + .handler = {&options.showDrvPaths, true}, }); addFlag({ .longName = "drv-names", .description = "Show the names and versions of derivations.", - .handler = {&showDrvNames, true}, + .handler = {&options.showDrvNames, true}, }); } @@ -889,115 +885,18 @@ struct CmdFlakeShow : FlakeCommand, MixJSON, MixFlakeSchemas void run(nix::ref store) override { - if (showOutputPaths && !json) + if (options.showOutputPaths && !json) throw UsageError("The '--output-paths' flag requires '--json'."); - if (showDrvPaths && !json) + if (options.showDrvPaths && !json) throw UsageError("The '--drv-paths' flag requires '--json'."); auto state = getEvalState(); auto flake = make_ref(lockFlake()); - auto localSystem = std::string(settings.thisSystem.get()); auto cache = flake_schemas::call(*state, flake, getDefaultFlakeSchemas()); - auto inventory = cache->getRoot()->getAttr("inventory"); - auto outputs = cache->getRoot()->getAttr("outputs"); - - FutureVector futures(*state->executor); - - std::function node, nlohmann::json & obj)> visit; - - visit = [&](ref node, nlohmann::json & obj) { - flake_schemas::visit( - showAllSystems ? std::optional() : localSystem, - showLegacy, - node, - flake->flake.provenance, - - [&](const flake_schemas::Leaf & leaf) { - if (auto what = leaf.what()) - obj.emplace("what", *what); - - if (auto shortDescription = leaf.shortDescription()) - obj.emplace("shortDescription", *shortDescription); - - if (auto drv = leaf.derivation(outputs)) { - auto drvObj = nlohmann::json::object(); - - if (json || showDrvNames) - drvObj.emplace("name", drv->getAttr(state->s.name)->getString()); - - if (showDrvPaths) { - auto drvPath = drv->forceDerivation(); - drvObj.emplace("path", store->printStorePath(drvPath)); - } - - if (showOutputPaths) { - auto outputs = nlohmann::json::object(); - auto drvPath = drv->forceDerivation(); - auto drv = getEvalStore()->derivationFromPath(drvPath); - for (auto & i : drv.outputsAndOptPaths(*store)) { - if (auto outPath = i.second.second) - outputs.emplace(i.first, store->printStorePath(*outPath)); - else - outputs.emplace(i.first, nullptr); - } - drvObj.emplace("outputs", std::move(outputs)); - } - - obj.emplace("derivation", std::move(drvObj)); - } - - if (auto forSystems = leaf.forSystems()) - obj.emplace("forSystems", *forSystems); - }, - - [&](std::function forEachChild) { - auto children = nlohmann::json::object(); - forEachChild([&](Symbol attrName, ref node, bool isLast) { - auto & j = children.emplace(state->symbols[attrName], nlohmann::json::object()).first.value(); - state->spawn(futures, 1, [&visit, &j, node]() { - try { - visit(node, j); - } catch (EvalError & e) { - // FIXME: make it a flake schema attribute whether to ignore evaluation errors. - if (node->root->state.symbols[node->getAttrPath()[0]] == "legacyPackages") - j.emplace("failed", true); - else - throw; - } - }); - }); - obj.emplace("children", std::move(children)); - }, - - [&](ref node, const std::vector & systems) { - obj.emplace("filtered", true); - }, - - [&](ref node) { obj.emplace("isLegacy", true); }); - }; - - auto inv = nlohmann::json::object(); - - flake_schemas::forEachOutput( - inventory, - [&](Symbol outputName, - std::shared_ptr output, - const std::string & doc, - bool isLast) { - auto & j = inv.emplace(state->symbols[outputName], nlohmann::json::object()).first.value(); - - if (output) { - j.emplace("doc", doc); - auto & j2 = j.emplace("output", nlohmann::json::object()).first.value(); - state->spawn(futures, 1, [&visit, output, &j2]() { visit(ref(output), j2); }); - } else - j.emplace("unknown", true); - }); - - futures.finishAll(); + auto inv = flake_schemas::getFlakeInventory(*state, *getEvalStore(), *flake, cache, options); if (json) { auto res = nlohmann::json{{"version", 2}, {"inventory", std::move(inv)}}; From 2774073bc8ebc5b6d0e7674229a9ef6ae7d705d8 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 24 Apr 2026 16:41:30 +0200 Subject: [PATCH 04/15] Fix clang warning --- src/libexpr/primops/wasm.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/primops/wasm.cc b/src/libexpr/primops/wasm.cc index 05b8a3ca38d9..b59cbec93e90 100644 --- a/src/libexpr/primops/wasm.cc +++ b/src/libexpr/primops/wasm.cc @@ -460,7 +460,7 @@ struct NixWasmInstance while (!args.empty()) { auto arg = &getValue(args[0]); auto tmp = state.allocValue(); - tmp->mkApp(res, {arg}); + tmp->mkApp(res, arg); res = tmp; args = args.subspan(1); } From 7b467edf6dbfddc96cdbb1d01d96b606b82f5079 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 5 May 2026 16:06:31 +0200 Subject: [PATCH 05/15] WIP --- src/libexpr/primops/fakeDerivation.cc | 4 +- src/nix/baked-flake.nix | 32 +++++++ src/nix/flake-bake.cc | 127 ++++++-------------------- src/nix/meson.build | 1 + tests/functional/meson.build | 1 + 5 files changed, 66 insertions(+), 99 deletions(-) create mode 100644 src/nix/baked-flake.nix diff --git a/src/libexpr/primops/fakeDerivation.cc b/src/libexpr/primops/fakeDerivation.cc index 046c8db1949e..03d1d96e0d22 100644 --- a/src/libexpr/primops/fakeDerivation.cc +++ b/src/libexpr/primops/fakeDerivation.cc @@ -116,7 +116,7 @@ static void prim_fakeDerivation(EvalState & state, const PosIdx pos, Value ** ar drv.outputs.insert_or_assign(outName, DerivationOutput::InputAddressed{.path = out.path}); } - auto drvPath = writeDerivation(*state.store, *state.asyncPathWriter, drv, state.repair); + auto drvPath = state.store->writeDerivation(*state.asyncPathWriter, drv, state.repair); // FIXME state.waitForPath(drvPath); @@ -130,7 +130,7 @@ static RegisterPrimOp primop_fakeDerivation({ .doc = R"( Placeholder. )", - .fun = prim_fakeDerivation, + .impl = prim_fakeDerivation, }); } // namespace nix diff --git a/src/nix/baked-flake.nix b/src/nix/baked-flake.nix new file mode 100644 index 000000000000..2f5344543eee --- /dev/null +++ b/src/nix/baked-flake.nix @@ -0,0 +1,32 @@ +{ + outputs = { self }: + let + data = builtins.fromJSON (builtins.readFile ./outputs.json); + + convert = output: + if output ? children then + builtins.mapAttrs (childName: child: + convert child + ) output.children + else if output ? "derivation" then + { + type = "derivation"; + name = output.derivation.name; + system = builtins.head output.forSystems; # FIXME + meta.description = output.shortDescription; + drvPath = builtins.fakeDerivation { + name = output.derivation.name; + outputs = builtins.mapAttrs (outputName: output: + { path = output; } + ) output.derivation.outputs; + } ; + outPath = output.derivation.outputs.out; # FIXME + outputName = "out"; # FIXME + } + else + throw "Output not supported in a baked flake."; + in + builtins.mapAttrs (outputName: output: + convert output.output + ) data; +} diff --git a/src/nix/flake-bake.cc b/src/nix/flake-bake.cc index fd6f905fb637..51cb18082a03 100644 --- a/src/nix/flake-bake.cc +++ b/src/nix/flake-bake.cc @@ -1,13 +1,28 @@ #include "flake-command.hh" #include "nix/cmd/flake-schemas.hh" -#include "nix/expr/parallel-eval.hh" -#include "nix/store/derivations.hh" +#include "nix/util/file-system.hh" + +#include using namespace nix; using namespace nix::flake; struct CmdFlakeBake : FlakeCommand, MixFlakeSchemas { + std::filesystem::path destDir; + + CmdFlakeBake() + { + addFlag({ + .longName = "dest-dir", + .description = "Directory in which to write the baked `flake.nix`.", + .labels = {"path"}, + .handler = {&destDir}, + .completer = completePath, + .required = true, + }); + } + std::string description() override { return "bake a flake"; @@ -28,101 +43,19 @@ struct CmdFlakeBake : FlakeCommand, MixFlakeSchemas auto cache = flake_schemas::call(*state, flake, getDefaultFlakeSchemas()); - auto inventory = cache->getRoot()->getAttr("inventory"); - auto outputs = cache->getRoot()->getAttr("outputs"); - - FutureVector futures(*state->executor); - - // FIXME: generate an AST. - Sync result; - - *result.lock() = R""( -{ - outputs = - { self }: - { -)""; - - auto visit = [&](this auto & visit, ref node) -> void { - flake_schemas::visit( - {"x86_64-linux"}, // FIXME - true, - node, - flake->flake.provenance, - - [&](const flake_schemas::Leaf & leaf) { - printError("AT %s", leaf.node->getAttrPathStr()); - - auto drv = leaf.derivation(outputs); - if (!drv) - // FIXME: emit attrs that throw a useful error? - return; - - // FIXME: do we need to force the derivation? - auto drvPath = drv->forceDerivation(); - auto storeDrv = evalStore->derivationFromPath(drvPath); - std::map outputs; - for (auto & i : storeDrv.outputsAndOptPaths(*store)) { - if (auto outPath = i.second.second) - outputs.emplace(i.first, *outPath); - else - // FIXME: we could build the derivation to get the outputs. - return; - } - - auto name = drv->getAttr(state->s.name)->getString(); - - auto r(result.lock()); - *r += " " + leaf.node->getAttrPathStr() + " = {\n"; - *r += " drvPath = builtins.fakeDerivation {\n"; - *r += " name = \"" + name + "\";\n"; - for (auto & i : outputs) - *r += " outputs." + i.first + ".path = \"" + store->printStorePath(i.second) + "\";\n"; - *r += " };\n"; - *r += " type = \"derivation\";\n"; - *r += " name = \"" + name + "\";\n"; - *r += " system = \"x86_64-linux\";\n"; // FIXME - // meta.description = "Docker image with Nix for x86_64-linux"; - *r += " };\n\n"; - }, - - [&](std::function forEachChild) { - forEachChild([&](Symbol attrName, ref node, bool isLast) { - state->spawn(futures, 1, [&visit, node]() { - try { - visit(node); - } catch (EvalError & e) { - // FIXME: make it a flake schema attribute whether to ignore evaluation errors. - if (node->root->state.symbols[node->getAttrPath()[0]] != "legacyPackages") - throw; - } - }); - }); - }, - - [&](ref node, const std::vector & systems) {}, - - [&](ref node) {}); - }; - - flake_schemas::forEachOutput( - inventory, - [&](Symbol outputName, - std::shared_ptr output, - const std::string & doc, - bool isLast) { - if (output) - state->spawn(futures, 1, [&visit, output]() { visit(ref(output)); }); - }); - - futures.finishAll(); - - *result.lock() += R""( - }; -} -)""; - - logger->cout(*result.lock()); + auto inv = flake_schemas::getFlakeInventory( + *state, + *getEvalStore(), + *flake, + cache, + {.showLegacy = true, .showOutputPaths = true, .showDrvNames = true}); + + std::filesystem::create_directories(destDir); + writeFile(destDir / "outputs.json", inv.dump()); + writeFile( + destDir / "flake.nix", +#include "baked-flake.nix.gen.hh" + ); } }; diff --git a/src/nix/meson.build b/src/nix/meson.build index 722d6f636fdf..28fa858d742a 100644 --- a/src/nix/meson.build +++ b/src/nix/meson.build @@ -142,6 +142,7 @@ nix_sources += [ gen_header.process('get-env.sh'), gen_header.process('profiles.md'), gen_header.process('help-stores.md'), + gen_header.process('baked-flake.nix'), ] # The rest of the subdirectories aren't separate components, diff --git a/tests/functional/meson.build b/tests/functional/meson.build index d3d4232a388a..908aac98f8fe 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -97,6 +97,7 @@ suites = [ 'restricted.sh', 'fetchGitSubmodules.sh', 'fetchGitVerification.sh', + 'fetchGitLegacySubmodulesEmptyDir.sh', 'readfile-context.sh', 'nix-channel.sh', 'recursive.sh', From acc264e94e2f7ce6d27d9fcd655b0d93f7ceca18 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 5 May 2026 16:25:17 +0200 Subject: [PATCH 06/15] Formatting --- src/nix/baked-flake.nix | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/nix/baked-flake.nix b/src/nix/baked-flake.nix index 2f5344543eee..c3563761a223 100644 --- a/src/nix/baked-flake.nix +++ b/src/nix/baked-flake.nix @@ -1,13 +1,13 @@ { - outputs = { self }: + outputs = + { self }: let data = builtins.fromJSON (builtins.readFile ./outputs.json); - convert = output: + convert = + output: if output ? children then - builtins.mapAttrs (childName: child: - convert child - ) output.children + builtins.mapAttrs (childName: child: convert child) output.children else if output ? "derivation" then { type = "derivation"; @@ -16,17 +16,13 @@ meta.description = output.shortDescription; drvPath = builtins.fakeDerivation { name = output.derivation.name; - outputs = builtins.mapAttrs (outputName: output: - { path = output; } - ) output.derivation.outputs; - } ; + outputs = builtins.mapAttrs (outputName: output: { path = output; }) output.derivation.outputs; + }; outPath = output.derivation.outputs.out; # FIXME outputName = "out"; # FIXME } else throw "Output not supported in a baked flake."; in - builtins.mapAttrs (outputName: output: - convert output.output - ) data; + builtins.mapAttrs (outputName: output: convert output.output) data; } From 0b398e578d751f8ae853c37b7fb1582903daf5fe Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 5 May 2026 16:25:23 +0200 Subject: [PATCH 07/15] Short-circuit fake derivations --- .../build/derivation-trampoline-goal.cc | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/libstore/build/derivation-trampoline-goal.cc b/src/libstore/build/derivation-trampoline-goal.cc index 58a43c043a2a..8205c7224c89 100644 --- a/src/libstore/build/derivation-trampoline-goal.cc +++ b/src/libstore/build/derivation-trampoline-goal.cc @@ -145,6 +145,53 @@ Goal::Co DerivationTrampolineGoal::haveDerivation(StorePath drvPath, Derivation }, wantedOutputs.raw); + /* Short-circuit fake derivations. Since these are never actually built, just create substitution goals for the + * outputs. */ + if (drv.builder == "builtin:fake-derivation") { + std::vector> outputGoals; + Goals waitees; + for (auto & outputName : resolvedWantedOutputs) { + auto i = drv.outputs.find(outputName); + if (i == drv.outputs.end()) + throw Error( + "derivation '%s' does not have wanted output '%s'", + worker.store.printStorePath(drvPath), + outputName); + auto outPath = i->second.path(worker.store, drv.name, outputName); + if (!outPath) + throw Error( + "output '%s' of derivation '%s' has no store path", + outputName, + worker.store.printStorePath(drvPath)); + auto g = upcast_goal(worker.makePathSubstitutionGoal(*outPath)); + outputGoals.emplace_back(*outPath, g); + waitees.insert(g); + } + + co_await await(std::move(waitees)); + + if (nrFailed != 0) { + StringSet failedPaths; + for (auto & [path, g] : outputGoals) + if (g->exitCode != ecSuccess) + failedPaths.insert(worker.store.printStorePath(path)); + co_return doneFailure( + ecFailed, + BuildResult::Failure{{ + .status = BuildResult::Failure::DependencyFailed, + .msg = HintFmt( + "failed to substitute outputs of '%s': %s", + worker.store.printStorePath(drvPath), + concatStringsSep(", ", quoteStrings(failedPaths))), + }}); + } + + co_return doneSuccess( + BuildResult::Success{ + .status = BuildResult::Success::Substituted, + }); + } + Goals concreteDrvGoals; /* Build this step! */ From 6384d6d505a1c6c8137a83dca51945cf62d9def6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 5 May 2026 16:27:46 +0200 Subject: [PATCH 08/15] builtin:fake-derivation -> builtin:substitute --- src/libexpr/primops/fakeDerivation.cc | 2 +- src/libstore/build/derivation-trampoline-goal.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libexpr/primops/fakeDerivation.cc b/src/libexpr/primops/fakeDerivation.cc index 03d1d96e0d22..40190ceadc40 100644 --- a/src/libexpr/primops/fakeDerivation.cc +++ b/src/libexpr/primops/fakeDerivation.cc @@ -97,7 +97,7 @@ static void prim_fakeDerivation(EvalState & state, const PosIdx pos, Value ** ar Derivation drv; drv.name = *name; drv.platform = "builtin"; - drv.builder = "builtin:fake-derivation"; + drv.builder = "builtin:substitute"; for (auto & [outName, out] : outputs) { #if 0 diff --git a/src/libstore/build/derivation-trampoline-goal.cc b/src/libstore/build/derivation-trampoline-goal.cc index 8205c7224c89..0663ca3a396b 100644 --- a/src/libstore/build/derivation-trampoline-goal.cc +++ b/src/libstore/build/derivation-trampoline-goal.cc @@ -145,9 +145,9 @@ Goal::Co DerivationTrampolineGoal::haveDerivation(StorePath drvPath, Derivation }, wantedOutputs.raw); - /* Short-circuit fake derivations. Since these are never actually built, just create substitution goals for the + /* Short-circuit `builtin:substitute`. Since these are never actually built, just create substitution goals for the * outputs. */ - if (drv.builder == "builtin:fake-derivation") { + if (drv.builder == "builtin:substitute") { std::vector> outputGoals; Goals waitees; for (auto & outputName : resolvedWantedOutputs) { From bfcf463f5bb78b8eecbc171f85b07e0bf0693df4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 5 May 2026 19:25:17 +0200 Subject: [PATCH 09/15] Introduce a "substituted" derivation type --- src/libexpr/primops/fakeDerivation.cc | 4 +-- .../build/derivation-trampoline-goal.cc | 2 +- src/libstore/derivations.cc | 32 ++++++++++++++++--- src/libstore/include/nix/store/derivations.hh | 13 ++++++-- 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/libexpr/primops/fakeDerivation.cc b/src/libexpr/primops/fakeDerivation.cc index 40190ceadc40..a5b7134b92e8 100644 --- a/src/libexpr/primops/fakeDerivation.cc +++ b/src/libexpr/primops/fakeDerivation.cc @@ -100,7 +100,6 @@ static void prim_fakeDerivation(EvalState & state, const PosIdx pos, Value ** ar drv.builder = "builtin:substitute"; for (auto & [outName, out] : outputs) { -#if 0 if (out.narHash) drv.outputs.insert_or_assign( outName, @@ -112,8 +111,7 @@ static void prim_fakeDerivation(EvalState & state, const PosIdx pos, Value ** ar }, }); else -#endif - drv.outputs.insert_or_assign(outName, DerivationOutput::InputAddressed{.path = out.path}); + drv.outputs.insert_or_assign(outName, DerivationOutput::InputAddressed{.path = out.path}); } auto drvPath = state.store->writeDerivation(*state.asyncPathWriter, drv, state.repair); diff --git a/src/libstore/build/derivation-trampoline-goal.cc b/src/libstore/build/derivation-trampoline-goal.cc index 0663ca3a396b..2c5e6a3a1af2 100644 --- a/src/libstore/build/derivation-trampoline-goal.cc +++ b/src/libstore/build/derivation-trampoline-goal.cc @@ -147,7 +147,7 @@ Goal::Co DerivationTrampolineGoal::haveDerivation(StorePath drvPath, Derivation /* Short-circuit `builtin:substitute`. Since these are never actually built, just create substitution goals for the * outputs. */ - if (drv.builder == "builtin:substitute") { + if (drv.type() == DerivationType::Substituted{}) { std::vector> outputGoals; Goals waitees; for (auto & outputName : resolvedWantedOutputs) { diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index 06fa0d44b42d..8c7714d2d012 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -54,6 +54,7 @@ bool DerivationType::isCA() const [](const InputAddressed & ia) { return false; }, [](const ContentAddressed & ca) { return true; }, [](const Impure &) { return true; }, + [](const Substituted &) { return false; }, }, raw); } @@ -65,6 +66,7 @@ bool DerivationType::isFixed() const [](const InputAddressed & ia) { return false; }, [](const ContentAddressed & ca) { return ca.fixed; }, [](const Impure &) { return false; }, + [](const Substituted &) { return false; }, }, raw); } @@ -76,6 +78,7 @@ bool DerivationType::hasKnownOutputPaths() const [](const InputAddressed & ia) { return !ia.deferred; }, [](const ContentAddressed & ca) { return ca.fixed; }, [](const Impure &) { return false; }, + [](const Substituted &) { return true; }, }, raw); } @@ -87,6 +90,7 @@ bool DerivationType::isSandboxed() const [](const InputAddressed & ia) { return true; }, [](const ContentAddressed & ca) { return ca.sandboxed; }, [](const Impure &) { return false; }, + [](const Substituted &) { return true; }, }, raw); } @@ -98,6 +102,7 @@ bool DerivationType::isImpure() const [](const InputAddressed & ia) { return false; }, [](const ContentAddressed & ca) { return false; }, [](const Impure &) { return true; }, + [](const Substituted &) { return false; }, }, raw); } @@ -866,6 +871,12 @@ DerivationType BasicDerivation::type() const if (!ty) throw Error("must have at least one output"); + if (builder == "builtin:substitute") { + if (!std::holds_alternative(ty.value().raw)) + throw Error("'builtin:substitute' derivation must have input-addressed outputs"); + return DerivationType::Substituted{}; + } + return ty.value(); } @@ -930,14 +941,15 @@ DrvHash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOut auto kind = std::visit( overloaded{ [](const DerivationType::InputAddressed & ia) { - /* This might be a "pesimistically" deferred output, so we don't + /* This might be a "pessimistically" deferred output, so we don't "taint" the kind yet. */ return DrvHash::Kind::Regular; }, [](const DerivationType::ContentAddressed & ca) { return ca.fixed ? DrvHash::Kind::Regular : DrvHash::Kind::Deferred; }, - [](const DerivationType::Impure &) -> DrvHash::Kind { return DrvHash::Kind::Deferred; }}, + [](const DerivationType::Impure &) -> DrvHash::Kind { return DrvHash::Kind::Deferred; }, + [](const DerivationType::Substituted &) -> DrvHash::Kind { return DrvHash::Kind::Regular; }}, drv.type().raw); DerivedPathMap::ChildNode::Map inputs2; @@ -1161,6 +1173,7 @@ bool Derivation::shouldResolve() const : true; }, [&](const DerivationType::Impure &) { return true; }, + [&](const DerivationType::Substituted &) { return false; }, }, drvType.raw); @@ -1278,6 +1291,18 @@ std::optional Derivation::tryResolve( template static void processDerivationOutputPaths(Store & store, auto && drv, std::string_view drvName) { + if (drv.builder == "builtin:substitute") { + if (drv.platform != "builtin") + throw Error("'builtin:substitute' derivation must be a builtin"); + if (!drv.args.empty()) + throw Error("'builtin:substitute' derivation must have no arguments"); + if (!drv.env.empty()) + throw Error("'builtin:substitute' derivation must have no environment variables"); + if (!drv.inputSrcs.empty()) + throw Error("'builtin:substitute' derivation must have no inputs"); + return; + } + std::optional hashesModulo; for (auto & [outputName, output] : drv.outputs) { @@ -1327,7 +1352,6 @@ static void processDerivationOutputPaths(Store & store, auto && drv, std::string auto outPath = store.makeOutputPath(outputName, *h, drvName); if constexpr (std::is_same_v) { -#if 0 if (outputVariant.path == outPath) { return; // Correct case } @@ -1337,8 +1361,6 @@ static void processDerivationOutputPaths(Store & store, auto && drv, std::string "derivation has incorrect output '%s', should be '%s'", store.printStorePath(outputVariant.path), store.printStorePath(outPath)); -#endif - return; // FIXME } else if constexpr (std::is_same_v) { if constexpr (fillIn) /* Fill in output path for Deferred diff --git a/src/libstore/include/nix/store/derivations.hh b/src/libstore/include/nix/store/derivations.hh index 88bfb6e49be6..e61660063ace 100644 --- a/src/libstore/include/nix/store/derivations.hh +++ b/src/libstore/include/nix/store/derivations.hh @@ -200,7 +200,7 @@ struct DerivationType /** * Impure derivation type * - * This is similar at build-time to the content addressed, not standboxed, not fixed + * This is similar at build-time to the content addressed, not sandboxed, not fixed * type, but has some restrictions on its usage. */ struct Impure @@ -209,7 +209,16 @@ struct DerivationType auto operator<=>(const Impure &) const = default; }; - typedef std::variant Raw; + /** + * A builtin:substitute derivation. + */ + struct Substituted + { + bool operator==(const Substituted &) const = default; + auto operator<=>(const Substituted &) const = default; + }; + + typedef std::variant Raw; Raw raw; From 120d58b4ef3f33828e8780f37baa4c1c2b73db73 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 5 May 2026 19:42:50 +0200 Subject: [PATCH 10/15] StoreDirConfig::parseStorePath(): Don't crash on empty paths --- src/libstore/store-dir-config.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libstore/store-dir-config.cc b/src/libstore/store-dir-config.cc index 61f82029d79f..962a3830f166 100644 --- a/src/libstore/store-dir-config.cc +++ b/src/libstore/store-dir-config.cc @@ -8,6 +8,8 @@ namespace nix { StorePath StoreDirConfig::parseStorePath(std::string_view path) const { + if (path.empty()) + throw BadStorePath("empty path is not a valid store path"); // On Windows, `/nix/store` is not a canonical path. More broadly it // is unclear whether this function should be using the native // notion of a canonical path at all. For example, it makes to From cec10239c53df90fbe81bfdf764650746b14d04e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 5 May 2026 19:43:01 +0200 Subject: [PATCH 11/15] canonPath(): Don't crash on empty paths --- src/libutil-tests/file-system.cc | 2 +- src/libutil/file-system.cc | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libutil-tests/file-system.cc b/src/libutil-tests/file-system.cc index 95bfa16b140c..da50d82bfe69 100644 --- a/src/libutil-tests/file-system.cc +++ b/src/libutil-tests/file-system.cc @@ -121,7 +121,7 @@ TEST(canonPath, requiresAbsolutePath) ASSERT_ANY_THROW(canonPath("."sv)); ASSERT_ANY_THROW(canonPath(".."sv)); ASSERT_ANY_THROW(canonPath("../"sv)); - ASSERT_DEATH({ canonPath(""sv); }, "!path.empty\\(\\)"); + ASSERT_ANY_THROW(canonPath(""sv)); } /* ---------------------------------------------------------------------------- diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc index e1efca0ddf7c..97bbfbb323ca 100644 --- a/src/libutil/file-system.cc +++ b/src/libutil/file-system.cc @@ -96,7 +96,8 @@ absPath(const std::filesystem::path & path0, const std::filesystem::path * dir, std::filesystem::path canonPath(const std::filesystem::path & path, bool resolveSymlinks) { - assert(!path.empty()); + if (path.empty()) + throw Error("cannot canonicalise an empty path"); if (!path.is_absolute()) throw Error("not an absolute path: %s", PathFmt(path)); From 1ec1bc80e72a307d7aa073160991ed5d796b7ede Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 6 May 2026 21:55:17 +0200 Subject: [PATCH 12/15] Doh --- tests/functional/meson.build | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 908aac98f8fe..d3d4232a388a 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -97,7 +97,6 @@ suites = [ 'restricted.sh', 'fetchGitSubmodules.sh', 'fetchGitVerification.sh', - 'fetchGitLegacySubmodulesEmptyDir.sh', 'readfile-context.sh', 'nix-channel.sh', 'recursive.sh', From 88dff86305a1c789aab0aeb247c01fae20d77610 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 6 May 2026 22:43:03 +0200 Subject: [PATCH 13/15] baked-flake.nix: Don't barf on unsupported/skipped outputs --- src/nix/baked-flake.nix | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/nix/baked-flake.nix b/src/nix/baked-flake.nix index c3563761a223..70649e717b72 100644 --- a/src/nix/baked-flake.nix +++ b/src/nix/baked-flake.nix @@ -4,10 +4,12 @@ let data = builtins.fromJSON (builtins.readFile ./outputs.json); + cleanup = builtins.filterAttrs (name: value: value != { }); + convert = output: if output ? children then - builtins.mapAttrs (childName: child: convert child) output.children + cleanup (builtins.mapAttrs (childName: child: convert child) output.children) else if output ? "derivation" then { type = "derivation"; @@ -22,7 +24,8 @@ outputName = "out"; # FIXME } else - throw "Output not supported in a baked flake."; + { + }; in - builtins.mapAttrs (outputName: output: convert output.output) data; + cleanup (builtins.mapAttrs (outputName: output: convert output.output) data); } From 93f8f9ee24a4aa771212b3961a6a0fcfb4496e6d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 6 May 2026 22:56:23 +0200 Subject: [PATCH 14/15] Fix test --- src/nix/flake.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 57f4977dacea..ec4092cab3f0 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -891,6 +891,9 @@ struct CmdFlakeShow : FlakeCommand, MixJSON, MixFlakeSchemas if (options.showDrvPaths && !json) throw UsageError("The '--drv-paths' flag requires '--json'."); + if (json) + options.showDrvNames = true; + auto state = getEvalState(); auto flake = make_ref(lockFlake()); From addfe06bc71f2bfcbc08cb3d956ca62890aac661 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 7 May 2026 00:04:38 +0200 Subject: [PATCH 15/15] Add file to package.nix --- src/nix/package.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nix/package.nix b/src/nix/package.nix index 11962c466f45..4518839ad540 100644 --- a/src/nix/package.nix +++ b/src/nix/package.nix @@ -57,6 +57,7 @@ mkMesonExecutable (finalAttrs: { ../../doc/manual/source/store/types/index.md.in ./profiles.md ../../doc/manual/source/command-ref/files/profiles.md + ./baked-flake.nix # Files ]