diff options
Diffstat (limited to 'src/nix/installables.cc')
-rw-r--r-- | src/nix/installables.cc | 773 |
1 files changed, 604 insertions, 169 deletions
diff --git a/src/nix/installables.cc b/src/nix/installables.cc index 708a0dc88..9bf6b7caa 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,80 +8,261 @@ #include "get-drvs.hh" #include "store-api.hh" #include "shared.hh" +#include "flake/flake.hh" +#include "eval-cache.hh" +#include "url.hh" +#include "registry.hh" #include <regex> +#include <queue> namespace nix { +void completeFlakeInputPath( + ref<EvalState> evalState, + const FlakeRef & flakeRef, + std::string_view prefix) +{ + auto flake = flake::getFlake(*evalState, flakeRef, true); + for (auto & input : flake.inputs) + if (hasPrefix(input.first, prefix)) + completions->insert(input.first); +} -SourceExprCommand::SourceExprCommand() +MixFlakeOptions::MixFlakeOptions() { addFlag({ - .longName = "file", - .shortName = 'f', - .description = "evaluate FILE rather than the default", - .labels = {"file"}, - .handler = {&file} + .longName = "recreate-lock-file", + .description = "recreate lock file from scratch", + .handler = {&lockFlags.recreateLockFile, true} }); -} -Value * SourceExprCommand::getSourceExpr(EvalState & state) -{ - if (vSourceExpr) return *vSourceExpr; + addFlag({ + .longName = "no-update-lock-file", + .description = "do not allow any updates to the lock file", + .handler = {&lockFlags.updateLockFile, false} + }); - auto sToplevel = state.symbols.create("_toplevel"); + addFlag({ + .longName = "no-write-lock-file", + .description = "do not write the newly generated lock file", + .handler = {&lockFlags.writeLockFile, false} + }); + + addFlag({ + .longName = "no-registries", + .description = "don't use flake registries", + .handler = {&lockFlags.useRegistries, false} + }); - vSourceExpr = allocRootValue(state.allocValue()); + addFlag({ + .longName = "commit-lock-file", + .description = "commit changes to the lock file", + .handler = {&lockFlags.commitLockFile, true} + }); - if (file != "") - state.evalFile(lookupFileArg(state, file), **vSourceExpr); + addFlag({ + .longName = "update-input", + .description = "update a specific flake input", + .labels = {"input-path"}, + .handler = {[&](std::string s) { + lockFlags.inputUpdates.insert(flake::parseInputPath(s)); + }}, + .completer = {[&](size_t, std::string_view prefix) { + if (auto flakeRef = getFlakeRefForCompletion()) + completeFlakeInputPath(getEvalState(), *flakeRef, prefix); + }} + }); - else { + addFlag({ + .longName = "override-input", + .description = "override a specific flake input (e.g. `dwarffs/nixpkgs`)", + .labels = {"input-path", "flake-url"}, + .handler = {[&](std::string inputPath, std::string flakeRef) { + lockFlags.inputOverrides.insert_or_assign( + flake::parseInputPath(inputPath), + parseFlakeRef(flakeRef, absPath("."))); + }} + }); - /* Construct the installation source from $NIX_PATH. */ + addFlag({ + .longName = "inputs-from", + .description = "use the inputs of the specified flake as registry entries", + .labels = {"flake-url"}, + .handler = {[&](std::string flakeRef) { + auto evalState = getEvalState(); + auto flake = flake::lockFlake( + *evalState, + parseFlakeRef(flakeRef, absPath(".")), + { .writeLockFile = false }); + for (auto & [inputName, input] : flake.lockFile.root->inputs) { + auto input2 = flake.lockFile.findInput({inputName}); // resolve 'follows' nodes + if (auto input3 = std::dynamic_pointer_cast<const flake::LockedNode>(input2)) { + overrideRegistry( + fetchers::Input::fromAttrs({{"type","indirect"}, {"id", inputName}}), + input3->lockedRef.input, + {}); + } + } + }}, + .completer = {[&](size_t, std::string_view prefix) { + completeFlakeRef(getEvalState()->store, prefix); + }} + }); +} - auto searchPath = state.getSearchPath(); +SourceExprCommand::SourceExprCommand() +{ + addFlag({ + .longName = "file", + .shortName = 'f', + .description = "evaluate *file* rather than the default", + .labels = {"file"}, + .handler = {&file}, + .completer = completePath + }); - state.mkAttrs(**vSourceExpr, 1024); + addFlag({ + .longName ="expr", + .description = "evaluate attributes from *expr*", + .labels = {"expr"}, + .handler = {&expr} + }); - mkBool(*state.allocAttr(**vSourceExpr, sToplevel), true); + addFlag({ + .longName ="derivation", + .description = "operate on the store derivation rather than its outputs", + .handler = {&operateOn, OperateOn::Derivation}, + }); +} - std::unordered_set<std::string> seen; +Strings SourceExprCommand::getDefaultFlakeAttrPaths() +{ + return {"defaultPackage." + settings.thisSystem.get()}; +} - 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); - }; +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() + "." + }; +} + +void SourceExprCommand::completeInstallable(std::string_view prefix) +{ + if (file) return; // FIXME + + completeFlakeRefWithFragment( + getEvalState(), + lockFlags, + getDefaultFlakeAttrPathPrefixes(), + getDefaultFlakeAttrPaths(), + prefix); +} - 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); - - (*vSourceExpr)->attrs->sort(); +void completeFlakeRefWithFragment( + ref<EvalState> evalState, + flake::LockFlags lockFlags, + Strings attrPathPrefixes, + const Strings & defaultFlakeAttrPaths, + std::string_view prefix) +{ + /* Look for flake output attributes that match the + prefix. */ + try { + auto hash = prefix.find('#'); + if (hash != std::string::npos) { + auto fragment = prefix.substr(hash + 1); + auto flakeRefS = std::string(prefix.substr(0, hash)); + // FIXME: do tilde expansion. + auto flakeRef = parseFlakeRef(flakeRefS, absPath(".")); + + auto evalCache = openEvalCache(*evalState, + std::make_shared<flake::LockedFlake>(lockFlake(*evalState, flakeRef, lockFlags))); + + auto root = evalCache->getRoot(); + + /* Complete 'fragment' relative to all the + attrpath prefixes as well as the root of the + flake. */ + attrPathPrefixes.push_back(""); + + for (auto & attrPathPrefixS : attrPathPrefixes) { + auto attrPathPrefix = parseAttrPath(*evalState, attrPathPrefixS); + auto attrPathS = attrPathPrefixS + std::string(fragment); + auto attrPath = parseAttrPath(*evalState, attrPathS); + + std::string lastAttr; + if (!attrPath.empty() && !hasSuffix(attrPathS, ".")) { + lastAttr = attrPath.back(); + attrPath.pop_back(); + } + + auto attr = root->findAlongAttrPath(attrPath); + if (!attr) continue; + + for (auto & attr2 : attr->getAttrs()) { + if (hasPrefix(attr2, lastAttr)) { + auto attrPath2 = attr->getAttrPath(attr2); + /* Strip the attrpath prefix. */ + attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size()); + completions->insert(flakeRefS + "#" + concatStringsSep(".", attrPath2)); + } + } + } + + /* And add an empty completion for the default + attrpaths. */ + if (fragment.empty()) { + for (auto & attrPath : defaultFlakeAttrPaths) { + auto attr = root->findAlongAttrPath(parseAttrPath(*evalState, attrPath)); + if (!attr) continue; + completions->insert(flakeRefS + "#"); + } + } + } + } catch (Error & e) { + warn(e.msg()); } - return *vSourceExpr; + completeFlakeRef(evalState->store, prefix); } -ref<EvalState> SourceExprCommand::getEvalState() +ref<EvalState> EvalCommand::getEvalState() { if (!evalState) evalState = std::make_shared<EvalState>(searchPath, getStore()); return ref<EvalState>(evalState); } +void completeFlakeRef(ref<Store> store, std::string_view prefix) +{ + if (prefix == "") + completions->insert("."); + + completeDir(0, prefix); + + /* Look for registry entries that match the prefix. */ + for (auto & registry : fetchers::getRegistries(store)) { + for (auto & entry : registry->entries) { + auto from = entry.from.to_string(); + if (!hasPrefix(prefix, "flake:") && hasPrefix(from, "flake:")) { + std::string from2(from, 6); + if (hasPrefix(from2, prefix)) + completions->insert(from2); + } else { + if (hasPrefix(from, prefix)) + completions->insert(from); + } + } + } +} + Buildable Installable::toBuildable() { auto buildables = toBuildables(); @@ -89,27 +271,54 @@ Buildable Installable::toBuildable() return std::move(buildables[0]); } +std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>> +Installable::getCursors(EvalState & state) +{ + auto evalCache = + std::make_shared<nix::eval_cache::EvalCache>(std::nullopt, state, + [&]() { return toValue(state).first; }); + return {{evalCache->getRoot(), ""}}; +} + +std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string> +Installable::getCursor(EvalState & state) +{ + auto cursors = getCursors(state); + if (cursors.empty()) + throw Error("cannot find flake attribute '%s'", what()); + return cursors[0]; +} + struct InstallableStorePath : Installable { ref<Store> store; StorePath storePath; - InstallableStorePath(ref<Store> store, const Path & storePath) - : store(store), storePath(store->parseStorePath(storePath)) { } + InstallableStorePath(ref<Store> store, StorePath && storePath) + : store(store), storePath(std::move(storePath)) { } std::string what() override { return store->printStorePath(storePath); } Buildables toBuildables() override { - std::map<std::string, StorePath> outputs; - outputs.insert_or_assign("out", storePath); - Buildable b{ - .drvPath = storePath.isDerivation() ? storePath : std::optional<StorePath>(), - .outputs = std::move(outputs) - }; - Buildables bs; - bs.push_back(std::move(b)); - return bs; + if (storePath.isDerivation()) { + std::map<std::string, std::optional<StorePath>> outputs; + auto drv = store->readDerivation(storePath); + for (auto & [name, output] : drv.outputsAndOptPaths(*store)) + outputs.emplace(name, output.second); + return { + BuildableFromDrv { + .drvPath = storePath, + .outputs = std::move(outputs) + } + }; + } else { + return { + BuildableOpaque { + .path = storePath, + } + }; + } } std::optional<StorePath> getStorePath() override @@ -118,146 +327,309 @@ struct InstallableStorePath : Installable } }; -struct InstallableValue : Installable +Buildables InstallableValue::toBuildables() +{ + Buildables res; + + std::map<StorePath, std::map<std::string, std::optional<StorePath>>> drvsToOutputs; + + // Group by derivation, helps with .all in particular + for (auto & drv : toDerivations()) { + auto outputName = drv.outputName; + if (outputName == "") + throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(drv.drvPath)); + drvsToOutputs[drv.drvPath].insert_or_assign(outputName, drv.outPath); + } + + for (auto & i : drvsToOutputs) + res.push_back(BuildableFromDrv { i.first, i.second }); + + return res; +} + +struct InstallableAttrPath : InstallableValue { SourceExprCommand & cmd; + RootValue v; + std::string attrPath; - InstallableValue(SourceExprCommand & cmd) : cmd(cmd) { } + InstallableAttrPath(ref<EvalState> state, SourceExprCommand & cmd, Value * v, const std::string & attrPath) + : InstallableValue(state), cmd(cmd), v(allocRootValue(v)), attrPath(attrPath) + { } - Buildables toBuildables() override + std::string what() override { return attrPath; } + + std::pair<Value *, Pos> toValue(EvalState & state) override { - auto state = cmd.getEvalState(); + auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), **v); + state.forceValue(*vRes); + return {vRes, pos}; + } + + virtual std::vector<InstallableValue::DerivationInfo> toDerivations() override; +}; - auto v = toValue(*state).first; +std::vector<InstallableValue::DerivationInfo> InstallableAttrPath::toDerivations() +{ + auto v = toValue(*state).first; - Bindings & autoArgs = *cmd.getAutoArgs(*state); + Bindings & autoArgs = *cmd.getAutoArgs(*state); - DrvInfos drvs; - getDerivations(*state, *v, "", autoArgs, drvs, false); + DrvInfos drvInfos; + getDerivations(*state, *v, "", autoArgs, drvInfos, false); - Buildables res; + std::vector<DerivationInfo> res; + for (auto & drvInfo : drvInfos) { + res.push_back({ + state->store->parseStorePath(drvInfo.queryDrvPath()), + state->store->parseStorePath(drvInfo.queryOutPath()), + drvInfo.queryOutputName() + }); + } - StorePathSet drvPaths; + return res; +} - for (auto & drv : drvs) { - Buildable b{.drvPath = state->store->parseStorePath(drv.queryDrvPath())}; - drvPaths.insert(*b.drvPath); +std::vector<std::string> InstallableFlake::getActualAttrPaths() +{ + std::vector<std::string> res; - auto outputName = drv.queryOutputName(); - if (outputName == "") - throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(*b.drvPath)); + for (auto & prefix : prefixes) + res.push_back(prefix + *attrPaths.begin()); - b.outputs.emplace(outputName, state->store->parseStorePath(drv.queryOutPath())); + for (auto & s : attrPaths) + res.push_back(s); - res.push_back(std::move(b)); - } + 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; +} + +ref<eval_cache::EvalCache> openEvalCache( + EvalState & state, + std::shared_ptr<flake::LockedFlake> lockedFlake) +{ + auto fingerprint = lockedFlake->getFingerprint(); + return make_ref<nix::eval_cache::EvalCache>( + evalSettings.useEvalCache && evalSettings.pureEval + ? std::optional { std::cref(fingerprint) } + : std::nullopt, + state, + [&state, lockedFlake]() + { + /* For testing whether the evaluation cache is + complete. */ + if (getEnv("NIX_ALLOW_EVAL").value_or("1") == "0") + throw Error("not everything is cached, but evaluation is not allowed"); + + auto vFlake = state.allocValue(); + flake::callFlake(state, *lockedFlake, *vFlake); + + state.forceAttrs(*vFlake); + + auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs")); + assert(aOutputs); + + return aOutputs->value; + }); +} - // Hack to recognize .all: if all drvs have the same drvPath, - // merge the buildables. - if (drvPaths.size() == 1) { - Buildable b{.drvPath = *drvPaths.begin()}; - for (auto & b2 : res) - for (auto & output : b2.outputs) - b.outputs.insert_or_assign(output.first, output.second); - Buildables bs; - bs.push_back(std::move(b)); - return bs; - } else - return res; +static std::string showAttrPaths(const std::vector<std::string> & paths) +{ + std::string s; + for (const auto & [n, i] : enumerate(paths)) { + if (n > 0) s += n + 1 == paths.size() ? " or " : ", "; + s += '\''; s += i; s += '\''; } -}; + return s; +} -struct InstallableExpr : InstallableValue +std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableFlake::toDerivation() { - std::string text; + auto lockedFlake = getLockedFlake(); - InstallableExpr(SourceExprCommand & cmd, const std::string & text) - : InstallableValue(cmd), text(text) { } + auto cache = openEvalCache(*state, lockedFlake); + auto root = cache->getRoot(); - std::string what() override { return text; } + for (auto & attrPath : getActualAttrPaths()) { + auto attr = root->findAlongAttrPath(parseAttrPath(*state, attrPath)); + if (!attr) continue; - std::pair<Value *, Pos> toValue(EvalState & state) override - { - auto v = state.allocValue(); - state.eval(state.parseExprFromString(text, absPath(".")), *v); - return {v, noPos}; + if (!attr->isDerivation()) + throw Error("flake output attribute '%s' is not a derivation", attrPath); + + auto drvPath = attr->forceDerivation(); + + auto drvInfo = DerivationInfo{ + std::move(drvPath), + state->store->parseStorePath(attr->getAttr(state->sOutPath)->getString()), + attr->getAttr(state->sOutputName)->getString() + }; + + return {attrPath, lockedFlake->flake.lockedRef, std::move(drvInfo)}; } -}; -struct InstallableAttrPath : InstallableValue + throw Error("flake '%s' does not provide attribute %s", + flakeRef, showAttrPaths(getActualAttrPaths())); +} + +std::vector<InstallableValue::DerivationInfo> InstallableFlake::toDerivations() { - std::string attrPath; + std::vector<DerivationInfo> res; + res.push_back(std::get<2>(toDerivation())); + return res; +} - InstallableAttrPath(SourceExprCommand & cmd, const std::string & attrPath) - : InstallableValue(cmd), attrPath(attrPath) - { } +std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state) +{ + auto lockedFlake = getLockedFlake(); - std::string what() override { return attrPath; } + auto vOutputs = getFlakeOutputs(state, *lockedFlake); - std::pair<Value *, Pos> toValue(EvalState & state) override - { - auto source = cmd.getSourceExpr(state); + 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, showAttrPaths(getActualAttrPaths())); +} + +std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>> +InstallableFlake::getCursors(EvalState & state) +{ + auto evalCache = openEvalCache(state, + std::make_shared<flake::LockedFlake>(lockFlake(state, flakeRef, lockFlags))); - Bindings & autoArgs = *cmd.getAutoArgs(state); + auto root = evalCache->getRoot(); - auto v = findAlongAttrPath(state, attrPath, autoArgs, *source).first; - state.forceValue(*v); + std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>> res; - return {v, noPos}; + for (auto & attrPath : getActualAttrPaths()) { + auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath)); + if (attr) res.push_back({attr, attrPath}); } -}; -// FIXME: extend -std::string attrRegex = R"([A-Za-z_][A-Za-z0-9-_+]*)"; -static std::regex attrPathRegex(fmt(R"(%1%(\.%1%)*)", attrRegex)); + return res; +} + +std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const +{ + if (!_lockedFlake) + _lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlags)); + return _lockedFlake; +} -static std::vector<std::shared_ptr<Installable>> parseInstallables( - SourceExprCommand & cmd, ref<Store> store, std::vector<std::string> ss, bool useDefaultInstallables) +FlakeRef InstallableFlake::nixpkgsFlakeRef() const { - std::vector<std::shared_ptr<Installable>> result; + auto lockedFlake = getLockedFlake(); - if (ss.empty() && useDefaultInstallables) { - if (cmd.file == "") - cmd.file = "."; - ss = {""}; + if (auto nixpkgsInput = lockedFlake->lockFile.findInput({"nixpkgs"})) { + if (auto lockedNode = std::dynamic_pointer_cast<const flake::LockedNode>(nixpkgsInput)) { + debug("using nixpkgs flake '%s'", lockedNode->lockedRef); + return std::move(lockedNode->lockedRef); + } } - for (auto & s : ss) { + return Installable::nixpkgsFlakeRef(); +} - if (s.compare(0, 1, "(") == 0) - result.push_back(std::make_shared<InstallableExpr>(cmd, s)); +std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables( + ref<Store> store, std::vector<std::string> ss) +{ + std::vector<std::shared_ptr<Installable>> result; - else if (s.find("/") != std::string::npos) { + if (file || expr) { + if (file && expr) + throw UsageError("'--file' and '--expr' are exclusive"); - auto path = store->toStorePath(store->followLinksToStore(s)); + // FIXME: backward compatibility hack + if (file) evalSettings.pureEval = false; - if (store->isStorePath(path)) - result.push_back(std::make_shared<InstallableStorePath>(store, path)); + auto state = getEvalState(); + auto vFile = state->allocValue(); + + if (file) + state->evalFile(lookupFileArg(*state, *file), *vFile); + else { + auto e = state->parseExprFromString(*expr, absPath(".")); + state->eval(e, *vFile); } - else if (s == "" || std::regex_match(s, attrPathRegex)) - result.push_back(std::make_shared<InstallableAttrPath>(cmd, s)); + for (auto & s : ss) + result.push_back(std::make_shared<InstallableAttrPath>(state, *this, vFile, s == "." ? "" : s)); + + } else { + + for (auto & s : ss) { + std::exception_ptr ex; + + try { + auto [flakeRef, fragment] = parseFlakeRefWithFragment(s, absPath(".")); + result.push_back(std::make_shared<InstallableFlake>( + getEvalState(), std::move(flakeRef), + fragment == "" ? getDefaultFlakeAttrPaths() : Strings{fragment}, + getDefaultFlakeAttrPathPrefixes(), lockFlags)); + continue; + } catch (...) { + ex = std::current_exception(); + } + + if (s.find('/') != std::string::npos) { + try { + result.push_back(std::make_shared<InstallableStorePath>(store, store->followLinksToStorePath(s))); + continue; + } catch (BadStorePath &) { + } catch (...) { + if (!ex) + ex = std::current_exception(); + } + } - else - throw UsageError("don't know what to do with argument '%s'", s); + std::rethrow_exception(ex); + + /* + throw Error( + pathExists(s) + ? "path '%s' is not a flake or a store path" + : "don't know how to handle 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(); } -Buildables build(ref<Store> store, RealiseMode mode, - std::vector<std::shared_ptr<Installable>> installables) +Buildables build(ref<Store> store, Realise mode, + std::vector<std::shared_ptr<Installable>> installables, BuildMode bMode) { - if (mode != Build) + if (mode == Realise::Nothing) settings.readOnlyMode = true; Buildables buildables; @@ -266,45 +638,70 @@ Buildables build(ref<Store> store, RealiseMode mode, for (auto & i : installables) { for (auto & b : i->toBuildables()) { - if (b.drvPath) { - StringSet outputNames; - for (auto & output : b.outputs) - outputNames.insert(output.first); - pathsToBuild.push_back({*b.drvPath, outputNames}); - } else - for (auto & output : b.outputs) - pathsToBuild.push_back({output.second}); + std::visit(overloaded { + [&](BuildableOpaque bo) { + pathsToBuild.push_back({bo.path}); + }, + [&](BuildableFromDrv bfd) { + StringSet outputNames; + for (auto & output : bfd.outputs) + outputNames.insert(output.first); + pathsToBuild.push_back({bfd.drvPath, outputNames}); + }, + }, b); buildables.push_back(std::move(b)); } } - if (mode == DryRun) + if (mode == Realise::Nothing) printMissing(store, pathsToBuild, lvlError); - else if (mode == Build) - store->buildPaths(pathsToBuild); + else if (mode == Realise::Outputs) + store->buildPaths(pathsToBuild, bMode); return buildables; } -StorePathSet toStorePaths(ref<Store> store, RealiseMode mode, +StorePathSet toStorePaths(ref<Store> store, + Realise mode, OperateOn operateOn, std::vector<std::shared_ptr<Installable>> installables) { StorePathSet outPaths; - for (auto & b : build(store, mode, installables)) - for (auto & output : b.outputs) - outPaths.insert(output.second); + if (operateOn == OperateOn::Output) { + for (auto & b : build(store, mode, installables)) + std::visit(overloaded { + [&](BuildableOpaque bo) { + outPaths.insert(bo.path); + }, + [&](BuildableFromDrv bfd) { + for (auto & output : bfd.outputs) { + if (!output.second) + throw Error("Cannot operate on output of unbuilt CA drv"); + outPaths.insert(*output.second); + } + }, + }, b); + } else { + if (mode == Realise::Nothing) + settings.readOnlyMode = true; + + for (auto & i : installables) + for (auto & b : i->toBuildables()) + if (auto bfd = std::get_if<BuildableFromDrv>(&b)) + outPaths.insert(bfd->drvPath); + } return outPaths; } -StorePath toStorePath(ref<Store> store, RealiseMode mode, +StorePath toStorePath(ref<Store> store, + Realise mode, OperateOn operateOn, std::shared_ptr<Installable> installable) { - auto paths = toStorePaths(store, mode, {installable}); + auto paths = toStorePaths(store, mode, operateOn, {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(); } @@ -315,32 +712,70 @@ StorePathSet toDerivations(ref<Store> store, StorePathSet drvPaths; for (auto & i : installables) - for (auto & b : i->toBuildables()) { - if (!b.drvPath) { - if (!useDeriver) - throw Error("argument '%s' did not evaluate to a derivation", i->what()); - for (auto & output : b.outputs) { - auto derivers = store->queryValidDerivers(output.second); + for (auto & b : i->toBuildables()) + std::visit(overloaded { + [&](BuildableOpaque bo) { + if (!useDeriver) + throw Error("argument '%s' did not evaluate to a derivation", i->what()); + auto derivers = store->queryValidDerivers(bo.path); if (derivers.empty()) throw Error("'%s' does not have a known deriver", i->what()); // FIXME: use all derivers? drvPaths.insert(*derivers.begin()); - } - } else - drvPaths.insert(*b.drvPath); - } + }, + [&](BuildableFromDrv bfd) { + drvPaths.insert(bfd.drvPath); + }, + }, b); return drvPaths; } +InstallablesCommand::InstallablesCommand() +{ + expectArgs({ + .label = "installables", + .handler = {&_installables}, + .completer = {[&](size_t, std::string_view prefix) { + completeInstallable(prefix); + }} + }); +} + 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); +} + +std::optional<FlakeRef> InstallablesCommand::getFlakeRefForCompletion() +{ + if (_installables.empty()) { + if (useDefaultInstallables()) + return parseFlakeRef(".", absPath(".")); + return {}; + } + return parseFlakeRef(_installables.front(), absPath(".")); +} + +InstallableCommand::InstallableCommand() +{ + expectArgs({ + .label = "installable", + .optional = true, + .handler = {&_installable}, + .completer = {[&](size_t, std::string_view prefix) { + completeInstallable(prefix); + }} + }); } void InstallableCommand::prepare() { - installable = parseInstallable(*this, getStore(), _installable, false); + installable = parseInstallable(getStore(), _installable); } } |