diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 27c6c3da8..35c01b97a 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -318,8 +318,6 @@ public: const FlakeRegistry & getFlakeRegistry(); - Value * makeFlakeRegistryValue(); - private: std::unique_ptr _flakeRegistry; std::once_flag _flakeRegistryInit; diff --git a/src/libexpr/primops/flake.cc b/src/libexpr/primops/flake.cc index 1e70ccbd6..3bd62a50b 100644 --- a/src/libexpr/primops/flake.cc +++ b/src/libexpr/primops/flake.cc @@ -40,18 +40,18 @@ const FlakeRegistry & EvalState::getFlakeRegistry() return *_flakeRegistry; } -Value * EvalState::makeFlakeRegistryValue() +Value * makeFlakeRegistryValue(EvalState & state) { - auto v = allocValue(); + auto v = state.allocValue(); - auto registry = getFlakeRegistry(); + auto registry = state.getFlakeRegistry(); - mkAttrs(*v, registry.entries.size()); + state.mkAttrs(*v, registry.entries.size()); for (auto & entry : registry.entries) { - auto vEntry = allocAttr(*v, entry.first); - mkAttrs(*vEntry, 2); - mkString(*allocAttr(*vEntry, symbols.create("uri")), entry.second.ref.to_string()); + auto vEntry = state.allocAttr(*v, entry.first); + state.mkAttrs(*vEntry, 2); + mkString(*state.allocAttr(*vEntry, state.symbols.create("uri")), entry.second.ref.to_string()); vEntry->attrs->sort(); } @@ -163,16 +163,19 @@ static Flake getFlake(EvalState & state, const FlakeRef & flakeRef) } /* Given a flake reference, recursively fetch it and its - dependencies. */ -static std::map resolveFlake(EvalState & state, + dependencies. + FIXME: this should return a graph of flakes. +*/ +static std::tuple> resolveFlake(EvalState & state, const FlakeRef & topRef, bool impureTopRef) { std::map done; std::queue> todo; - todo.push({topRef, impureTopRef}); + std::optional topFlakeId; /// FIXME: ambiguous + todo.push({topRef, true}); while (!todo.empty()) { - auto [flakeRef, impureRef] = todo.front(); + auto [flakeRef, toplevel] = todo.front(); todo.pop(); if (auto refData = std::get_if(&flakeRef.data)) { @@ -180,26 +183,27 @@ static std::map resolveFlake(EvalState & state, flakeRef = lookupFlake(state, flakeRef); } - if (evalSettings.pureEval && !flakeRef.isImmutable() && !impureRef) + if (evalSettings.pureEval && !flakeRef.isImmutable() && (!toplevel || !impureTopRef)) throw Error("mutable flake '%s' is not allowed in pure mode; use --no-pure-eval to disable", flakeRef.to_string()); auto flake = getFlake(state, flakeRef); if (done.count(flake.id)) continue; + if (toplevel) topFlakeId = flake.id; + for (auto & require : flake.requires) todo.push({require, false}); done.emplace(flake.id, flake); } - return done; + assert(topFlakeId); + return {*topFlakeId, done}; } -static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v) +Value * makeFlakeValue(EvalState & state, std::string flakeUri, Value & v) { - auto flakeUri = state.forceStringNoCtx(*args[0], pos); - // FIXME: temporary hack to make the default installation source // work. bool impure = false; @@ -210,14 +214,20 @@ static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Va auto flakeRef = FlakeRef(flakeUri); - auto flakes = resolveFlake(state, flakeUri, impure); + auto [topFlakeId, flakes] = resolveFlake(state, flakeUri, impure); + + // FIXME: we should call each flake with only its dependencies + // (rather than the closure of the top-level flake). auto vResult = state.allocValue(); state.mkAttrs(*vResult, flakes.size()); + Value * vTop = 0; + for (auto & flake : flakes) { auto vFlake = state.allocAttr(*vResult, flake.second.id); + if (topFlakeId == flake.second.id) vTop = vFlake; state.mkAttrs(*vFlake, 2); mkString(*state.allocAttr(*vFlake, state.sDescription), flake.second.description); auto vProvides = state.allocAttr(*vFlake, state.symbols.create("provides")); @@ -228,6 +238,14 @@ static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Va vResult->attrs->sort(); v = *vResult; + + assert(vTop); + return vTop; +} + +static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v) +{ + makeFlakeValue(state, state.forceStringNoCtx(*args[0], pos), v); } static RegisterPrimOp r2("getFlake", 1, prim_getFlake); diff --git a/src/libexpr/primops/flake.hh b/src/libexpr/primops/flake.hh index 6be6e99d2..e504dc196 100644 --- a/src/libexpr/primops/flake.hh +++ b/src/libexpr/primops/flake.hh @@ -5,6 +5,9 @@ namespace nix { +struct Value; +class EvalState; + struct FlakeRegistry { struct Entry @@ -14,4 +17,8 @@ struct FlakeRegistry std::map entries; }; +Value * makeFlakeRegistryValue(EvalState & state); + +Value * makeFlakeValue(EvalState & state, std::string flakeUri, Value & v); + } diff --git a/src/nix/command.hh b/src/nix/command.hh index 04183c7ed..a08347945 100644 --- a/src/nix/command.hh +++ b/src/nix/command.hh @@ -53,7 +53,8 @@ struct Installable struct SourceExprCommand : virtual Args, StoreCommand, MixEvalArgs { - Path file; + std::optional file; + std::optional flakeUri; SourceExprCommand(); diff --git a/src/nix/installables.cc b/src/nix/installables.cc index b4584f168..0453c72c2 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -7,6 +7,7 @@ #include "get-drvs.hh" #include "store-api.hh" #include "shared.hh" +#include "primops/flake.hh" #include @@ -18,8 +19,15 @@ SourceExprCommand::SourceExprCommand() .shortName('f') .longName("file") .label("file") - .description("evaluate FILE rather than the default") + .description("evaluate FILE rather than use the default installation source") .dest(&file); + + mkFlag() + .shortName('F') + .longName("flake") + .label("flake") + .description("evaluate FLAKE rather than use the default installation source") + .dest(&flakeUri); } Value * SourceExprCommand::getSourceExpr(EvalState & state) @@ -28,9 +36,17 @@ Value * SourceExprCommand::getSourceExpr(EvalState & state) vSourceExpr = state.allocValue(); - if (file != "") - state.evalFile(lookupFileArg(state, file), *vSourceExpr); - else { + if (file && flakeUri) + throw Error("cannot use both --file and --flake"); + + if (file) + state.evalFile(lookupFileArg(state, *file), *vSourceExpr); + else if (flakeUri) { + // FIXME: handle flakeUri being a relative path + auto vTemp = state.allocValue(); + auto vFlake = *makeFlakeValue(state, "impure:" + *flakeUri, *vTemp); + *vSourceExpr = *((*vFlake.attrs->get(state.symbols.create("provides")))->value); + } else { // FIXME: remove "impure" hack, call some non-user-accessible // variant of getFlake instead. auto fun = state.parseExprFromString( @@ -38,7 +54,7 @@ Value * SourceExprCommand::getSourceExpr(EvalState & state) " (getFlake (\"impure:\" + flakeInfo.uri)).${flakeName}.provides.packages or {})", "/"); auto vFun = state.allocValue(); state.eval(fun, *vFun); - auto vRegistry = state.makeFlakeRegistryValue(); + auto vRegistry = makeFlakeRegistryValue(state); mkApp(*vSourceExpr, *vFun, *vRegistry); }