aboutsummaryrefslogtreecommitdiff
path: root/src/nix/installables.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/nix/installables.cc')
-rw-r--r--src/nix/installables.cc621
1 files changed, 491 insertions, 130 deletions
diff --git a/src/nix/installables.cc b/src/nix/installables.cc
index 937d69206..86d3bfd20 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,205 @@
#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 {
-
-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;
-
- auto sToplevel = state.symbols.create("_toplevel");
+ addFlag({
+ .longName = "no-update-lock-file",
+ .description = "do not allow any updates to the lock file",
+ .handler = {&lockFlags.updateLockFile, false}
+ });
- vSourceExpr = allocRootValue(state.allocValue());
+ addFlag({
+ .longName = "no-write-lock-file",
+ .description = "do not write the newly generated lock file",
+ .handler = {&lockFlags.writeLockFile, false}
+ });
- if (file != "")
- state.evalFile(lookupFileArg(state, file), **vSourceExpr);
+ addFlag({
+ .longName = "no-registries",
+ .description = "don't use flake registries",
+ .handler = {&lockFlags.useRegistries, false}
+ });
- else {
+ addFlag({
+ .longName = "commit-lock-file",
+ .description = "commit changes to the lock file",
+ .handler = {&lockFlags.commitLockFile, true}
+ });
- /* Construct the installation source from $NIX_PATH. */
+ addFlag({
+ .longName = "update-input",
+ .description = "update a specific flake input",
+ .labels = {"input-path"},
+ .handler = {[&](std::string s) {
+ lockFlags.inputUpdates.insert(flake::parseInputPath(s));
+ }}
+ });
- auto searchPath = state.getSearchPath();
+ 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(".")));
+ }}
+ });
+}
- state.mkAttrs(**vSourceExpr, 1024);
+SourceExprCommand::SourceExprCommand()
+{
+ addFlag({
+ .longName = "file",
+ .shortName = 'f',
+ .description = "evaluate FILE rather than the default",
+ .labels = {"file"},
+ .handler = {&file},
+ .completer = completePath
+ });
- mkBool(*state.allocAttr(**vSourceExpr, sToplevel), true);
+ addFlag({
+ .longName ="expr",
+ .description = "evaluate attributes from EXPR",
+ .labels = {"expr"},
+ .handler = {&expr}
+ });
+}
- 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() + "."
+ };
+}
- 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);
+void SourceExprCommand::completeInstallable(std::string_view prefix)
+{
+ if (file) return; // FIXME
+
+ /* 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 state = getEvalState();
+
+ auto evalCache = openEvalCache(*state,
+ std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlags)),
+ true);
+
+ auto root = evalCache->getRoot();
+
+ /* Complete 'fragment' relative to all the
+ attrpath prefixes as well as the root of the
+ flake. */
+ auto attrPathPrefixes = getDefaultFlakeAttrPathPrefixes();
+ attrPathPrefixes.push_back("");
+
+ for (auto & attrPathPrefixS : attrPathPrefixes) {
+ auto attrPathPrefix = parseAttrPath(*state, attrPathPrefixS);
+ auto attrPathS = attrPathPrefixS + std::string(fragment);
+ auto attrPath = parseAttrPath(*state, attrPathS);
+
+ std::string lastAttr;
+ if (!attrPath.empty() && !hasSuffix(attrPathS, ".")) {
+ lastAttr = attrPath.back();
+ attrPath.pop_back();
+ }
- (*vSourceExpr)->attrs->sort();
+ auto attr = root->findAlongAttrPath(attrPath);
+ if (!attr) continue;
+
+ auto attrs = attr->getAttrs();
+ for (auto & attr2 : attrs) {
+ 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 : getDefaultFlakeAttrPaths()) {
+ auto attr = root->findAlongAttrPath(parseAttrPath(*state, attrPath));
+ if (!attr) continue;
+ completions->insert(flakeRefS + "#");
+ }
+ }
+ }
+ } catch (Error & e) {
+ warn(e.msg());
}
- return *vSourceExpr;
+ completeFlakeRef(prefix);
}
-ref<EvalState> SourceExprCommand::getEvalState()
+ref<EvalState> EvalCommand::getEvalState()
{
if (!evalState)
evalState = std::make_shared<EvalState>(searchPath, getStore());
return ref<EvalState>(evalState);
}
+void EvalCommand::completeFlakeRef(std::string_view prefix)
+{
+ if (prefix == "")
+ completions->insert(".");
+
+ completeDir(0, prefix);
+
+ /* Look for registry entries that match the prefix. */
+ for (auto & registry : fetchers::getRegistries(getStore())) {
+ 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,6 +215,36 @@ 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);
+}
+
+std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
+Installable::getCursor(EvalState & state, bool useEvalCache)
+{
+ auto evalCache =
+ std::make_shared<nix::eval_cache::EvalCache>(false, Hash(), state,
+ [&]() { return toValue(state).first; });
+ return {{evalCache->getRoot(), ""}};
+}
+
struct InstallableStorePath : Installable
{
ref<Store> store;
@@ -118,138 +274,316 @@ struct InstallableStorePath : Installable
}
};
-struct InstallableValue : Installable
+Buildables InstallableValue::toBuildables()
+{
+ Buildables res;
+
+ StorePathSet drvPaths;
+
+ for (auto & drv : toDerivations()) {
+ Buildable b{.drvPath = drv.drvPath.clone()};
+ drvPaths.insert(drv.drvPath.clone());
+
+ auto outputName = drv.outputName;
+ if (outputName == "")
+ throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(*b.drvPath));
+
+ b.outputs.emplace(outputName, drv.outPath.clone());
+
+ 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 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};
+ }
- auto v = toValue(*state).first;
+ virtual std::vector<InstallableValue::DerivationInfo> toDerivations() override;
+};
- Bindings & autoArgs = *cmd.getAutoArgs(*state);
+std::vector<InstallableValue::DerivationInfo> InstallableAttrPath::toDerivations()
+{
+ auto v = toValue(*state).first;
- DrvInfos drvs;
- getDerivations(*state, *v, "", autoArgs, drvs, false);
+ Bindings & autoArgs = *cmd.getAutoArgs(*state);
- Buildables res;
+ DrvInfos drvInfos;
+ getDerivations(*state, *v, "", autoArgs, drvInfos, false);
- StorePathSet drvPaths;
+ std::vector<DerivationInfo> res;
+ for (auto & drvInfo : drvInfos) {
+ res.push_back({
+ state->store->parseStorePath(drvInfo.queryDrvPath()),
+ state->store->parseStorePath(drvInfo.queryOutPath()),
+ drvInfo.queryOutputName()
+ });
+ }
- for (auto & drv : drvs) {
- Buildable b{.drvPath = state->store->parseStorePath(drv.queryDrvPath())};
- drvPaths.insert(b.drvPath->clone());
+ return res;
+}
- auto outputName = drv.queryOutputName();
- if (outputName == "")
- throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(*b.drvPath));
+std::vector<std::string> InstallableFlake::getActualAttrPaths()
+{
+ std::vector<std::string> res;
- b.outputs.emplace(outputName, state->store->parseStorePath(drv.queryOutPath()));
+ for (auto & prefix : prefixes)
+ res.push_back(prefix + *attrPaths.begin());
- res.push_back(std::move(b));
- }
+ for (auto & s : attrPaths)
+ res.push_back(s);
- // 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;
- }
-};
+ return res;
+}
-struct InstallableExpr : InstallableValue
+Value * InstallableFlake::getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake)
{
- std::string text;
+ auto vFlake = state.allocValue();
- InstallableExpr(SourceExprCommand & cmd, const std::string & text)
- : InstallableValue(cmd), text(text) { }
+ callFlake(state, lockedFlake, *vFlake);
- std::string what() override { return text; }
+ auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs"));
+ assert(aOutputs);
- std::pair<Value *, Pos> toValue(EvalState & state) override
- {
- auto v = state.allocValue();
- state.eval(state.parseExprFromString(text, absPath(".")), *v);
- return {v, noPos};
+ state.forceValue(*aOutputs->value);
+
+ return aOutputs->value;
+}
+
+ref<eval_cache::EvalCache> openEvalCache(
+ EvalState & state,
+ std::shared_ptr<flake::LockedFlake> lockedFlake,
+ bool useEvalCache)
+{
+ return ref(std::make_shared<nix::eval_cache::EvalCache>(
+ useEvalCache,
+ lockedFlake->getFingerprint(),
+ 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;
+ }));
+}
+
+std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableFlake::toDerivation()
+{
+
+ auto lockedFlake = getLockedFlake();
+
+ auto cache = openEvalCache(*state, lockedFlake, true);
+ auto root = cache->getRoot();
+
+ for (auto & attrPath : getActualAttrPaths()) {
+ auto attr = root->findAlongAttrPath(parseAttrPath(*state, attrPath));
+ if (!attr) continue;
+
+ if (!attr->isDerivation())
+ throw Error("flake output attribute '%s' is not a derivation", attrPath);
+
+ auto aDrvPath = attr->getAttr(state->sDrvPath);
+ auto drvPath = state->store->parseStorePath(aDrvPath->getString());
+ if (!state->store->isValidPath(drvPath) && !settings.readOnlyMode) {
+ /* The eval cache contains 'drvPath', but the actual path
+ has been garbage-collected. So force it to be
+ regenerated. */
+ aDrvPath->forceValue();
+ if (!state->store->isValidPath(drvPath))
+ throw Error("don't know how to recreate store derivation '%s'!",
+ state->store->printStorePath(drvPath));
+ }
+
+ 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, concatStringsSep(", ", quoteStrings(attrPaths)));
+}
+
+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);
- Bindings & autoArgs = *cmd.getAutoArgs(state);
+ for (auto & attrPath : getActualAttrPaths()) {
+ try {
+ auto [v, pos] = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs);
+ state.forceValue(*v);
+ return {v, pos};
+ } catch (AttrPathNotFound & e) {
+ }
+ }
- auto v = findAlongAttrPath(state, attrPath, autoArgs, *source).first;
- state.forceValue(*v);
+ throw Error("flake '%s' does not provide attribute %s",
+ flakeRef, concatStringsSep(", ", quoteStrings(attrPaths)));
+}
+
+std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
+InstallableFlake::getCursor(EvalState & state, bool useEvalCache)
+{
+ auto evalCache = openEvalCache(state,
+ std::make_shared<flake::LockedFlake>(lockFlake(state, flakeRef, lockFlags)),
+ useEvalCache);
+
+ auto root = evalCache->getRoot();
+
+ 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;
+}
-static std::vector<std::shared_ptr<Installable>> parseInstallables(
- SourceExprCommand & cmd, ref<Store> store, std::vector<std::string> ss, bool useDefaultInstallables)
+std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
{
- std::vector<std::shared_ptr<Installable>> result;
+ if (!_lockedFlake)
+ _lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlags));
+ return _lockedFlake;
+}
- if (ss.empty() && useDefaultInstallables) {
- if (cmd.file == "")
- cmd.file = ".";
- ss = {""};
+FlakeRef InstallableFlake::nixpkgsFlakeRef() const
+{
+ auto lockedFlake = getLockedFlake();
+
+ auto nixpkgsInput = lockedFlake->lockFile.root->inputs.find("nixpkgs");
+ if (nixpkgsInput != lockedFlake->lockFile.root->inputs.end()) {
+ if (auto lockedNode = std::dynamic_pointer_cast<const flake::LockedNode>(nixpkgsInput->second)) {
+ 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;
+
+ if (file || expr) {
+ if (file && expr)
+ throw UsageError("'--file' and '--expr' are exclusive");
- else if (s.find("/") != std::string::npos) {
+ // FIXME: backward compatibility hack
+ if (file) evalSettings.pureEval = false;
- auto path = store->toStorePath(store->followLinksToStore(s));
+ auto state = getEvalState();
+ auto vFile = state->allocValue();
- if (store->isStorePath(path))
- result.push_back(std::make_shared<InstallableStorePath>(store, path));
+ 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->printStorePath(store->followLinksToStorePath(s))));
+ continue;
+ } catch (NotInStore &) {
+ } catch (...) {
+ if (!ex)
+ ex = std::current_exception();
+ }
+ }
+
+ std::rethrow_exception(ex);
- else
- throw UsageError("don't know what to do with argument '%s'", s);
+ /*
+ 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();
}
@@ -304,7 +638,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,14 +667,41 @@ StorePathSet toDerivations(ref<Store> store,
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);
+}
+
+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);
}
}