From 154244adc6c9831e00a41bf7799a2d29c6a3a3b4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 8 Apr 2019 14:21:13 +0200 Subject: [PATCH] nix: New installables syntax The general syntax for an installable is now :. The attrpath is relative to the flake's 'provides.packages' or 'provides' if the former doesn't yield a result. E.g. $ nix build nixpkgs:hello is equivalent to $ nix build nixpkgs:packages.hello Also, ':' can be omitted, in which case it defaults to 'nixpkgs', e.g. $ nix build hello --- src/libexpr/primops/flake.cc | 55 +++++++++------------------- src/libexpr/primops/flake.hh | 2 +- src/nix/installables.cc | 71 +++++++++++++++++++++++++++--------- 3 files changed, 72 insertions(+), 56 deletions(-) diff --git a/src/libexpr/primops/flake.cc b/src/libexpr/primops/flake.cc index a8d46825f..dedd2f737 100644 --- a/src/libexpr/primops/flake.cc +++ b/src/libexpr/primops/flake.cc @@ -57,8 +57,9 @@ Path getUserRegistryPath() std::shared_ptr getGlobalRegistry() { - // TODO: Make a global registry, and return it here. - return std::make_shared(); + // FIXME: get from nixos.org. + Path registryFile = settings.nixDataDir + "/nix/flake-registry.json"; + return readRegistry(registryFile); } std::shared_ptr getUserRegistry() @@ -66,33 +67,17 @@ std::shared_ptr getUserRegistry() return readRegistry(getUserRegistryPath()); } -// Project-specific registry saved in flake-registry.json. -std::shared_ptr getLocalRegistry() -{ - Path registryFile = settings.nixDataDir + "/nix/flake-registry.json"; - return readRegistry(registryFile); -} - std::shared_ptr getFlagRegistry() { return std::make_shared(); // TODO: Implement this once the right flags are implemented. } -// This always returns a vector with globalReg, userReg, localReg, flakeReg. -// If one of them doesn't exist, the registry is left empty but does exist. const std::vector> EvalState::getFlakeRegistries() { std::vector> registries; - if (!evalSettings.pureEval) { - registries.push_back(std::make_shared()); // global - registries.push_back(std::make_shared()); // user - registries.push_back(std::make_shared()); // local - } else { - registries.push_back(getGlobalRegistry()); - registries.push_back(getUserRegistry()); - registries.push_back(getLocalRegistry()); - } + registries.push_back(getGlobalRegistry()); + registries.push_back(getUserRegistry()); registries.push_back(getFlagRegistry()); return registries; } @@ -149,8 +134,7 @@ struct FlakeSourceInfo static FlakeSourceInfo fetchFlake(EvalState & state, const FlakeRef & flakeRef) { FlakeRef directFlakeRef = FlakeRef(flakeRef); - if (!flakeRef.isDirect()) - { + if (!flakeRef.isDirect()) { directFlakeRef = lookupFlake(state, flakeRef, state.getFlakeRegistries()); } assert(directFlakeRef.isDirect()); @@ -274,8 +258,8 @@ static std::tuple> resolveFlake(EvalState & st std::optional topFlakeId; /// FIXME: ambiguous todo.push({topRef, true}); - std::vector> registries = state.getFlakeRegistries(); - std::shared_ptr localRegistry = registries.at(2); + auto registries = state.getFlakeRegistries(); + //std::shared_ptr localRegistry = registries.at(2); while (!todo.empty()) { auto [flakeRef, toplevel] = todo.front(); @@ -283,12 +267,15 @@ static std::tuple> resolveFlake(EvalState & st if (auto refData = std::get_if(&flakeRef.data)) { if (done.count(refData->id)) continue; // optimization - flakeRef = lookupFlake(state, flakeRef, registries); + flakeRef = lookupFlake(state, flakeRef, + !evalSettings.pureEval || (toplevel && impureTopRef) ? registries : std::vector>()); // This is why we need the `registries`. } +#if 0 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()); +#endif auto flake = getFlake(state, flakeRef); @@ -299,6 +286,7 @@ static std::tuple> resolveFlake(EvalState & st for (auto & require : flake.requires) todo.push({require, false}); +#if 0 // The following piece of code basically adds the FlakeRefs from // the lockfiles of dependencies to the localRegistry. This is used // to resolve future `FlakeId`s, in `lookupFlake` a bit above this. @@ -307,6 +295,7 @@ static std::tuple> resolveFlake(EvalState & st if (localRegistry->entries.count(entry.first)) continue; localRegistry->entries.emplace(entry.first, entry.second); } +#endif done.emplace(flake.id, std::move(flake)); } @@ -344,19 +333,9 @@ void updateLockFile(EvalState & state, std::string path) } } -Value * makeFlakeValue(EvalState & state, std::string flakeUri, Value & v) +Value * makeFlakeValue(EvalState & state, const FlakeRef & flakeRef, bool impureTopRef, Value & v) { - // FIXME: temporary hack to make the default installation source - // work. - bool impure = false; - if (hasPrefix(flakeUri, "impure:")) { - flakeUri = std::string(flakeUri, 7); - impure = true; - } - - auto flakeRef = FlakeRef(flakeUri); - - auto [topFlakeId, flakes] = resolveFlake(state, flakeUri, impure); + auto [topFlakeId, flakes] = resolveFlake(state, flakeRef, impureTopRef); // FIXME: we should call each flake with only its dependencies // (rather than the closure of the top-level flake). @@ -387,7 +366,7 @@ Value * makeFlakeValue(EvalState & state, std::string flakeUri, Value & v) static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v) { - makeFlakeValue(state, state.forceStringNoCtx(*args[0], pos), v); + makeFlakeValue(state, state.forceStringNoCtx(*args[0], pos), false, v); } static RegisterPrimOp r2("getFlake", 1, prim_getFlake); diff --git a/src/libexpr/primops/flake.hh b/src/libexpr/primops/flake.hh index 53cea1cc2..df8cf9efb 100644 --- a/src/libexpr/primops/flake.hh +++ b/src/libexpr/primops/flake.hh @@ -23,7 +23,7 @@ Path getUserRegistryPath(); Value * makeFlakeRegistryValue(EvalState & state); -Value * makeFlakeValue(EvalState & state, std::string flakeUri, Value & v); +Value * makeFlakeValue(EvalState & state, const FlakeRef & flakeRef, bool impureTopRef, Value & v); std::shared_ptr readRegistry(const Path &); diff --git a/src/nix/installables.cc b/src/nix/installables.cc index 0453c72c2..5e4ea6054 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -21,13 +21,6 @@ SourceExprCommand::SourceExprCommand() .label("file") .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) @@ -36,17 +29,9 @@ Value * SourceExprCommand::getSourceExpr(EvalState & state) vSourceExpr = state.allocValue(); - 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 { + else { // FIXME: remove "impure" hack, call some non-user-accessible // variant of getFlake instead. auto fun = state.parseExprFromString( @@ -176,6 +161,43 @@ struct InstallableAttrPath : InstallableValue } }; +struct InstallableFlake : InstallableValue +{ + FlakeRef flakeRef; + std::string attrPath; + + InstallableFlake(SourceExprCommand & cmd, FlakeRef && flakeRef, const std::string & attrPath) + : InstallableValue(cmd), flakeRef(flakeRef), attrPath(attrPath) + { } + + std::string what() override { return flakeRef.to_string() + ":" + attrPath; } + + Value * toValue(EvalState & state) override + { + auto vTemp = state.allocValue(); + auto vFlake = *makeFlakeValue(state, flakeRef, true, *vTemp); + + auto vProvides = (*vFlake.attrs->get(state.symbols.create("provides")))->value; + + state.forceValue(*vProvides); + + auto emptyArgs = state.allocBindings(0); + + if (auto aPackages = *vProvides->attrs->get(state.symbols.create("packages"))) { + try { + auto * v = findAlongAttrPath(state, attrPath, *emptyArgs, *aPackages->value); + state.forceValue(*v); + return v; + } catch (AttrPathNotFound & e) { + } + } + + auto * v = findAlongAttrPath(state, attrPath, *emptyArgs, *vProvides); + state.forceValue(*v); + return v; + } +}; + // FIXME: extend std::string attrRegex = R"([A-Za-z_][A-Za-z0-9-_+]*)"; static std::regex attrPathRegex(fmt(R"(%1%(\.%1%)*)", attrRegex)); @@ -196,19 +218,34 @@ static std::vector> parseInstallables( if (s.compare(0, 1, "(") == 0) result.push_back(std::make_shared(cmd, s)); - else if (s.find("/") != std::string::npos) { + /* + else if (s.find('/') != std::string::npos) { auto path = store->toStorePath(store->followLinksToStore(s)); if (store->isStorePath(path)) result.push_back(std::make_shared(path)); } + */ + else { + auto colon = s.rfind(':'); + if (colon != std::string::npos) { + auto flakeRef = std::string(s, 0, colon); + auto attrPath = std::string(s, colon + 1); + result.push_back(std::make_shared(cmd, FlakeRef(flakeRef), attrPath)); + } else { + result.push_back(std::make_shared(cmd, FlakeRef("nixpkgs"), s)); + } + } + + /* else if (s == "" || std::regex_match(s, attrPathRegex)) result.push_back(std::make_shared(cmd, s)); else throw UsageError("don't know what to do with argument '%s'", s); + */ } return result;