nix: Add --flake flag

This allows using an arbitrary "provides" attribute from the specified
flake. For example:

  nix build --flake nixpkgs packages.hello

(Maybe provides.packages should be used for consistency...)
This commit is contained in:
Eelco Dolstra 2019-02-12 21:55:43 +01:00
parent 272b58220d
commit beab05851b
No known key found for this signature in database
GPG key ID: 8170B4726D7198DE
5 changed files with 65 additions and 25 deletions

View file

@ -318,8 +318,6 @@ public:
const FlakeRegistry & getFlakeRegistry(); const FlakeRegistry & getFlakeRegistry();
Value * makeFlakeRegistryValue();
private: private:
std::unique_ptr<FlakeRegistry> _flakeRegistry; std::unique_ptr<FlakeRegistry> _flakeRegistry;
std::once_flag _flakeRegistryInit; std::once_flag _flakeRegistryInit;

View file

@ -40,18 +40,18 @@ const FlakeRegistry & EvalState::getFlakeRegistry()
return *_flakeRegistry; 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) { for (auto & entry : registry.entries) {
auto vEntry = allocAttr(*v, entry.first); auto vEntry = state.allocAttr(*v, entry.first);
mkAttrs(*vEntry, 2); state.mkAttrs(*vEntry, 2);
mkString(*allocAttr(*vEntry, symbols.create("uri")), entry.second.ref.to_string()); mkString(*state.allocAttr(*vEntry, state.symbols.create("uri")), entry.second.ref.to_string());
vEntry->attrs->sort(); vEntry->attrs->sort();
} }
@ -163,16 +163,19 @@ static Flake getFlake(EvalState & state, const FlakeRef & flakeRef)
} }
/* Given a flake reference, recursively fetch it and its /* Given a flake reference, recursively fetch it and its
dependencies. */ dependencies.
static std::map<FlakeId, Flake> resolveFlake(EvalState & state, FIXME: this should return a graph of flakes.
*/
static std::tuple<FlakeId, std::map<FlakeId, Flake>> resolveFlake(EvalState & state,
const FlakeRef & topRef, bool impureTopRef) const FlakeRef & topRef, bool impureTopRef)
{ {
std::map<FlakeId, Flake> done; std::map<FlakeId, Flake> done;
std::queue<std::tuple<FlakeRef, bool>> todo; std::queue<std::tuple<FlakeRef, bool>> todo;
todo.push({topRef, impureTopRef}); std::optional<FlakeId> topFlakeId; /// FIXME: ambiguous
todo.push({topRef, true});
while (!todo.empty()) { while (!todo.empty()) {
auto [flakeRef, impureRef] = todo.front(); auto [flakeRef, toplevel] = todo.front();
todo.pop(); todo.pop();
if (auto refData = std::get_if<FlakeRef::IsFlakeId>(&flakeRef.data)) { if (auto refData = std::get_if<FlakeRef::IsFlakeId>(&flakeRef.data)) {
@ -180,26 +183,27 @@ static std::map<FlakeId, Flake> resolveFlake(EvalState & state,
flakeRef = lookupFlake(state, flakeRef); 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()); 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); auto flake = getFlake(state, flakeRef);
if (done.count(flake.id)) continue; if (done.count(flake.id)) continue;
if (toplevel) topFlakeId = flake.id;
for (auto & require : flake.requires) for (auto & require : flake.requires)
todo.push({require, false}); todo.push({require, false});
done.emplace(flake.id, flake); 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 // FIXME: temporary hack to make the default installation source
// work. // work.
bool impure = false; bool impure = false;
@ -210,14 +214,20 @@ static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Va
auto flakeRef = FlakeRef(flakeUri); 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(); auto vResult = state.allocValue();
state.mkAttrs(*vResult, flakes.size()); state.mkAttrs(*vResult, flakes.size());
Value * vTop = 0;
for (auto & flake : flakes) { for (auto & flake : flakes) {
auto vFlake = state.allocAttr(*vResult, flake.second.id); auto vFlake = state.allocAttr(*vResult, flake.second.id);
if (topFlakeId == flake.second.id) vTop = vFlake;
state.mkAttrs(*vFlake, 2); state.mkAttrs(*vFlake, 2);
mkString(*state.allocAttr(*vFlake, state.sDescription), flake.second.description); mkString(*state.allocAttr(*vFlake, state.sDescription), flake.second.description);
auto vProvides = state.allocAttr(*vFlake, state.symbols.create("provides")); 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(); vResult->attrs->sort();
v = *vResult; 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); static RegisterPrimOp r2("getFlake", 1, prim_getFlake);

View file

@ -5,6 +5,9 @@
namespace nix { namespace nix {
struct Value;
class EvalState;
struct FlakeRegistry struct FlakeRegistry
{ {
struct Entry struct Entry
@ -14,4 +17,8 @@ struct FlakeRegistry
std::map<FlakeId, Entry> entries; std::map<FlakeId, Entry> entries;
}; };
Value * makeFlakeRegistryValue(EvalState & state);
Value * makeFlakeValue(EvalState & state, std::string flakeUri, Value & v);
} }

View file

@ -53,7 +53,8 @@ struct Installable
struct SourceExprCommand : virtual Args, StoreCommand, MixEvalArgs struct SourceExprCommand : virtual Args, StoreCommand, MixEvalArgs
{ {
Path file; std::optional<Path> file;
std::optional<std::string> flakeUri;
SourceExprCommand(); SourceExprCommand();

View file

@ -7,6 +7,7 @@
#include "get-drvs.hh" #include "get-drvs.hh"
#include "store-api.hh" #include "store-api.hh"
#include "shared.hh" #include "shared.hh"
#include "primops/flake.hh"
#include <regex> #include <regex>
@ -18,8 +19,15 @@ SourceExprCommand::SourceExprCommand()
.shortName('f') .shortName('f')
.longName("file") .longName("file")
.label("file") .label("file")
.description("evaluate FILE rather than the default") .description("evaluate FILE rather than use the default installation source")
.dest(&file); .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) Value * SourceExprCommand::getSourceExpr(EvalState & state)
@ -28,9 +36,17 @@ Value * SourceExprCommand::getSourceExpr(EvalState & state)
vSourceExpr = state.allocValue(); vSourceExpr = state.allocValue();
if (file != "") if (file && flakeUri)
state.evalFile(lookupFileArg(state, file), *vSourceExpr); throw Error("cannot use both --file and --flake");
else {
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 // FIXME: remove "impure" hack, call some non-user-accessible
// variant of getFlake instead. // variant of getFlake instead.
auto fun = state.parseExprFromString( auto fun = state.parseExprFromString(
@ -38,7 +54,7 @@ Value * SourceExprCommand::getSourceExpr(EvalState & state)
" (getFlake (\"impure:\" + flakeInfo.uri)).${flakeName}.provides.packages or {})", "/"); " (getFlake (\"impure:\" + flakeInfo.uri)).${flakeName}.provides.packages or {})", "/");
auto vFun = state.allocValue(); auto vFun = state.allocValue();
state.eval(fun, *vFun); state.eval(fun, *vFun);
auto vRegistry = state.makeFlakeRegistryValue(); auto vRegistry = makeFlakeRegistryValue(state);
mkApp(*vSourceExpr, *vFun, *vRegistry); mkApp(*vSourceExpr, *vFun, *vRegistry);
} }