diff options
Diffstat (limited to 'src/nix/installables.cc')
-rw-r--r-- | src/nix/installables.cc | 420 |
1 files changed, 305 insertions, 115 deletions
diff --git a/src/nix/installables.cc b/src/nix/installables.cc index f464d0aa1..99bbe9769 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -1,3 +1,4 @@ +#include "installables.hh" #include "command.hh" #include "attr-path.hh" #include "common-eval-args.hh" @@ -7,72 +8,96 @@ #include "get-drvs.hh" #include "store-api.hh" #include "shared.hh" +#include "flake/flake.hh" +#include "flake/eval-cache.hh" +#include "url.hh" #include <regex> +#include <queue> namespace nix { -SourceExprCommand::SourceExprCommand() +MixFlakeOptions::MixFlakeOptions() { mkFlag() - .shortName('f') - .longName("file") - .label("file") - .description("evaluate FILE rather than the default") - .dest(&file); -} - -Value * SourceExprCommand::getSourceExpr(EvalState & state) -{ - if (vSourceExpr) return vSourceExpr; - - auto sToplevel = state.symbols.create("_toplevel"); - - vSourceExpr = state.allocValue(); - - if (file != "") - state.evalFile(lookupFileArg(state, file), *vSourceExpr); + .longName("recreate-lock-file") + .description("recreate lock file from scratch") + .set(&lockFlags.recreateLockFile, true); - else { + mkFlag() + .longName("no-update-lock-file") + .description("do not allow any updates to the lock file") + .set(&lockFlags.updateLockFile, false); - /* Construct the installation source from $NIX_PATH. */ + mkFlag() + .longName("no-write-lock-file") + .description("do not write the newly generated lock file") + .set(&lockFlags.writeLockFile, false); - auto searchPath = state.getSearchPath(); + mkFlag() + .longName("no-registries") + .description("don't use flake registries") + .set(&lockFlags.useRegistries, false); - state.mkAttrs(*vSourceExpr, 1024); + mkFlag() + .longName("commit-lock-file") + .description("commit changes to the lock file") + .set(&lockFlags.commitLockFile, true); - mkBool(*state.allocAttr(*vSourceExpr, sToplevel), true); + mkFlag() + .longName("update-input") + .description("update a specific flake input") + .label("input-path") + .handler([&](std::vector<std::string> ss) { + lockFlags.inputUpdates.insert(flake::parseInputPath(ss[0])); + }); - std::unordered_set<std::string> seen; + mkFlag() + .longName("override-input") + .description("override a specific flake input (e.g. 'dwarffs/nixpkgs')") + .arity(2) + .labels({"input-path", "flake-url"}) + .handler([&](std::vector<std::string> ss) { + lockFlags.inputOverrides.insert_or_assign( + flake::parseInputPath(ss[0]), + parseFlakeRef(ss[1], absPath("."))); + }); +} - auto addEntry = [&](const std::string & name) { - if (name == "") return; - if (!seen.insert(name).second) return; - Value * v1 = state.allocValue(); - mkPrimOpApp(*v1, state.getBuiltin("findFile"), state.getBuiltin("nixPath")); - Value * v2 = state.allocValue(); - mkApp(*v2, *v1, mkString(*state.allocValue(), name)); - mkApp(*state.allocAttr(*vSourceExpr, state.symbols.create(name)), - state.getBuiltin("import"), *v2); - }; +SourceExprCommand::SourceExprCommand() +{ + mkFlag() + .shortName('f') + .longName("file") + .label("file") + .description("evaluate attributes from FILE") + .dest(&file); - for (auto & i : searchPath) - /* Hack to handle channels. */ - if (i.first.empty() && pathExists(i.second + "/manifest.nix")) { - for (auto & j : readDirectory(i.second)) - if (j.name != "manifest.nix" - && pathExists(fmt("%s/%s/default.nix", i.second, j.name))) - addEntry(j.name); - } else - addEntry(i.first); + mkFlag() + .longName("expr") + .label("expr") + .description("evaluate attributes from EXPR") + .dest(&expr); +} - vSourceExpr->attrs->sort(); - } +Strings SourceExprCommand::getDefaultFlakeAttrPaths() +{ + return {"defaultPackage." + settings.thisSystem.get()}; +} - return vSourceExpr; +Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes() +{ + return { + // As a convenience, look for the attribute in + // 'outputs.packages'. + "packages." + settings.thisSystem.get() + ".", + // As a temporary hack until Nixpkgs is properly converted + // to provide a clean 'packages' set, look in 'legacyPackages'. + "legacyPackages." + settings.thisSystem.get() + "." + }; } -ref<EvalState> SourceExprCommand::getEvalState() +ref<EvalState> EvalCommand::getEvalState() { if (!evalState) evalState = std::make_shared<EvalState>(searchPath, getStore()); @@ -87,6 +112,27 @@ Buildable Installable::toBuildable() return std::move(buildables[0]); } +App::App(EvalState & state, Value & vApp) +{ + state.forceAttrs(vApp); + + auto aType = vApp.attrs->need(state.sType); + if (state.forceStringNoCtx(*aType.value, *aType.pos) != "app") + throw Error("value does not have type 'app', at %s", *aType.pos); + + auto aProgram = vApp.attrs->need(state.symbols.create("program")); + program = state.forceString(*aProgram.value, context, *aProgram.pos); + + // FIXME: check that 'program' is in the closure of 'context'. + if (!state.store->isInStore(program)) + throw Error("app program '%s' is not in the Nix store", program); +} + +App Installable::toApp(EvalState & state) +{ + return App(state, *toValue(state).first); +} + struct InstallableStorePath : Installable { ref<Store> store; @@ -116,54 +162,63 @@ struct InstallableStorePath : Installable } }; -struct InstallableValue : Installable +std::vector<flake::EvalCache::Derivation> InstallableValue::toDerivations() { - SourceExprCommand & cmd; + auto state = cmd.getEvalState(); - InstallableValue(SourceExprCommand & cmd) : cmd(cmd) { } + auto v = toValue(*state).first; - Buildables toBuildables() override - { - auto state = cmd.getEvalState(); + Bindings & autoArgs = *cmd.getAutoArgs(*state); - auto v = toValue(*state).first; + DrvInfos drvInfos; + getDerivations(*state, *v, "", autoArgs, drvInfos, false); - Bindings & autoArgs = *cmd.getAutoArgs(*state); + std::vector<flake::EvalCache::Derivation> res; + for (auto & drvInfo : drvInfos) { + res.push_back({ + state->store->parseStorePath(drvInfo.queryDrvPath()), + state->store->parseStorePath(drvInfo.queryOutPath()), + drvInfo.queryOutputName() + }); + } - DrvInfos drvs; - getDerivations(*state, *v, "", autoArgs, drvs, false); + return res; +} - Buildables res; +Buildables InstallableValue::toBuildables() +{ + auto state = cmd.getEvalState(); - StorePathSet drvPaths; + Buildables res; - for (auto & drv : drvs) { - Buildable b{.drvPath = state->store->parseStorePath(drv.queryDrvPath())}; - drvPaths.insert(b.drvPath->clone()); + StorePathSet drvPaths; - auto outputName = drv.queryOutputName(); - if (outputName == "") - throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(*b.drvPath)); + for (auto & drv : toDerivations()) { + Buildable b{.drvPath = drv.drvPath.clone()}; + drvPaths.insert(drv.drvPath.clone()); - b.outputs.emplace(outputName, state->store->parseStorePath(drv.queryOutPath())); + auto outputName = drv.outputName; + if (outputName == "") + throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(*b.drvPath)); - res.push_back(std::move(b)); - } + b.outputs.emplace(outputName, drv.outPath.clone()); - // Hack to recognize .all: if all drvs have the same drvPath, - // merge the buildables. - if (drvPaths.size() == 1) { - Buildable b{.drvPath = drvPaths.begin()->clone()}; - for (auto & b2 : res) - for (auto & output : b2.outputs) - b.outputs.insert_or_assign(output.first, output.second.clone()); - Buildables bs; - bs.push_back(std::move(b)); - return bs; - } else - return res; + res.push_back(std::move(b)); } -}; + + // Hack to recognize .all: if all drvs have the same drvPath, + // merge the buildables. + if (drvPaths.size() == 1) { + Buildable b{.drvPath = drvPaths.begin()->clone()}; + for (auto & b2 : res) + for (auto & output : b2.outputs) + b.outputs.insert_or_assign(output.first, output.second.clone()); + Buildables bs; + bs.push_back(std::move(b)); + return bs; + } else + return res; +} struct InstallableExpr : InstallableValue { @@ -184,70 +239,201 @@ struct InstallableExpr : InstallableValue struct InstallableAttrPath : InstallableValue { + Value * v; std::string attrPath; - InstallableAttrPath(SourceExprCommand & cmd, const std::string & attrPath) - : InstallableValue(cmd), attrPath(attrPath) + InstallableAttrPath(SourceExprCommand & cmd, Value * v, const std::string & attrPath) + : InstallableValue(cmd), v(v), attrPath(attrPath) { } std::string what() override { return attrPath; } std::pair<Value *, Pos> toValue(EvalState & state) override { - auto source = cmd.getSourceExpr(state); + auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), *v); + state.forceValue(*vRes); + return {vRes, pos}; + } +}; - Bindings & autoArgs = *cmd.getAutoArgs(state); +std::vector<std::string> InstallableFlake::getActualAttrPaths() +{ + std::vector<std::string> res; - auto v = findAlongAttrPath(state, attrPath, autoArgs, *source).first; - state.forceValue(*v); + for (auto & prefix : prefixes) + res.push_back(prefix + *attrPaths.begin()); - return {v, noPos}; + for (auto & s : attrPaths) + res.push_back(s); + + return res; +} + +Value * InstallableFlake::getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake) +{ + auto vFlake = state.allocValue(); + + callFlake(state, lockedFlake, *vFlake); + + auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs")); + assert(aOutputs); + + state.forceValue(*aOutputs->value); + + return aOutputs->value; +} + +std::tuple<std::string, FlakeRef, flake::EvalCache::Derivation> InstallableFlake::toDerivation() +{ + auto state = cmd.getEvalState(); + + auto lockedFlake = lockFlake(*state, flakeRef, cmd.lockFlags); + + Value * vOutputs = nullptr; + + auto emptyArgs = state->allocBindings(0); + + auto & evalCache = flake::EvalCache::singleton(); + + auto fingerprint = lockedFlake.getFingerprint(); + + for (auto & attrPath : getActualAttrPaths()) { + auto drv = evalCache.getDerivation(fingerprint, attrPath); + if (drv) { + if (state->store->isValidPath(drv->drvPath)) + return {attrPath, lockedFlake.flake.lockedRef, std::move(*drv)}; + } + + if (!vOutputs) + vOutputs = getFlakeOutputs(*state, lockedFlake); + + try { + auto * v = findAlongAttrPath(*state, attrPath, *emptyArgs, *vOutputs).first; + state->forceValue(*v); + + auto drvInfo = getDerivation(*state, *v, false); + if (!drvInfo) + throw Error("flake output attribute '%s' is not a derivation", attrPath); + + auto drv = flake::EvalCache::Derivation{ + state->store->parseStorePath(drvInfo->queryDrvPath()), + state->store->parseStorePath(drvInfo->queryOutPath()), + drvInfo->queryOutputName() + }; + + evalCache.addDerivation(fingerprint, attrPath, drv); + + return {attrPath, lockedFlake.flake.lockedRef, std::move(drv)}; + } catch (AttrPathNotFound & e) { + } } -}; + + throw Error("flake '%s' does not provide attribute %s", + flakeRef, concatStringsSep(", ", quoteStrings(attrPaths))); +} + +std::vector<flake::EvalCache::Derivation> InstallableFlake::toDerivations() +{ + std::vector<flake::EvalCache::Derivation> res; + res.push_back(std::get<2>(toDerivation())); + return res; +} + +std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state) +{ + auto lockedFlake = lockFlake(state, flakeRef, cmd.lockFlags); + + auto vOutputs = getFlakeOutputs(state, lockedFlake); + + auto emptyArgs = state.allocBindings(0); + + for (auto & attrPath : getActualAttrPaths()) { + try { + auto [v, pos] = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs); + state.forceValue(*v); + return {v, pos}; + } catch (AttrPathNotFound & e) { + } + } + + throw Error("flake '%s' does not provide attribute %s", + flakeRef, concatStringsSep(", ", quoteStrings(attrPaths))); +} // FIXME: extend std::string attrRegex = R"([A-Za-z_][A-Za-z0-9-_+]*)"; static std::regex attrPathRegex(fmt(R"(%1%(\.%1%)*)", attrRegex)); -static std::vector<std::shared_ptr<Installable>> parseInstallables( - SourceExprCommand & cmd, ref<Store> store, std::vector<std::string> ss, bool useDefaultInstallables) +std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables( + ref<Store> store, std::vector<std::string> ss) { std::vector<std::shared_ptr<Installable>> result; - if (ss.empty() && useDefaultInstallables) { - if (cmd.file == "") - cmd.file = "."; - ss = {""}; - } + if (file || expr) { + if (file && expr) + throw UsageError("'--file' and '--expr' are exclusive"); - for (auto & s : ss) { + // FIXME: backward compatibility hack + if (file) evalSettings.pureEval = false; - if (s.compare(0, 1, "(") == 0) - result.push_back(std::make_shared<InstallableExpr>(cmd, s)); + auto state = getEvalState(); + auto vFile = state->allocValue(); - else if (s.find("/") != std::string::npos) { + if (file) + state->evalFile(lookupFileArg(*state, *file), *vFile); + else { + auto e = state->parseExprFromString(*expr, absPath(".")); + state->eval(e, *vFile); + } - auto path = store->toStorePath(store->followLinksToStore(s)); + for (auto & s : ss) + result.push_back(std::make_shared<InstallableAttrPath>(*this, vFile, s == "." ? "" : s)); - if (store->isStorePath(path)) - result.push_back(std::make_shared<InstallableStorePath>(store, path)); - } + } else { - else if (s == "" || std::regex_match(s, attrPathRegex)) - result.push_back(std::make_shared<InstallableAttrPath>(cmd, s)); + auto follow = [&](const std::string & s) -> std::optional<StorePath> { + try { + return store->followLinksToStorePath(s); + } catch (NotInStore &) { + return {}; + } + }; - else - throw UsageError("don't know what to do with argument '%s'", s); + for (auto & s : ss) { + if (hasPrefix(s, "nixpkgs.")) { + bool static warned; + warnOnce(warned, "the syntax 'nixpkgs.<attr>' is deprecated; use 'nixpkgs#<attr>' instead"); + result.push_back(std::make_shared<InstallableFlake>(*this, + FlakeRef::fromAttrs({{"type", "indirect"}, {"id", "nixpkgs"}}), + Strings{"legacyPackages." + settings.thisSystem.get() + "." + std::string(s, 8)}, Strings{})); + } + + else { + auto res = maybeParseFlakeRefWithFragment(s, absPath(".")); + if (res) { + auto &[flakeRef, fragment] = *res; + result.push_back(std::make_shared<InstallableFlake>( + *this, std::move(flakeRef), + fragment == "" ? getDefaultFlakeAttrPaths() : Strings{fragment}, + getDefaultFlakeAttrPathPrefixes())); + } else { + std::optional<StorePath> storePath; + if (s.find('/') != std::string::npos && (storePath = follow(s))) + result.push_back(std::make_shared<InstallableStorePath>(store, store->printStorePath(*storePath))); + else + throw Error("unrecognized argument '%s'", s); + } + } + } } return result; } -std::shared_ptr<Installable> parseInstallable( - SourceExprCommand & cmd, ref<Store> store, const std::string & installable, - bool useDefaultInstallables) +std::shared_ptr<Installable> SourceExprCommand::parseInstallable( + ref<Store> store, const std::string & installable) { - auto installables = parseInstallables(cmd, store, {installable}, false); + auto installables = parseInstallables(store, {installable}); assert(installables.size() == 1); return installables.front(); } @@ -302,7 +488,7 @@ StorePath toStorePath(ref<Store> store, RealiseMode mode, auto paths = toStorePaths(store, mode, {installable}); if (paths.size() != 1) - throw Error("argument '%s' should evaluate to one store path", installable->what()); + throw Error("argument '%s' should evaluate to one store path", installable->what()); return paths.begin()->clone(); } @@ -333,12 +519,16 @@ StorePathSet toDerivations(ref<Store> store, void InstallablesCommand::prepare() { - installables = parseInstallables(*this, getStore(), _installables, useDefaultInstallables()); + if (_installables.empty() && useDefaultInstallables()) + // FIXME: commands like "nix install" should not have a + // default, probably. + _installables.push_back("."); + installables = parseInstallables(getStore(), _installables); } void InstallableCommand::prepare() { - installable = parseInstallable(*this, getStore(), _installable, false); + installable = parseInstallable(getStore(), _installable); } } |