diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 635ce19b698..00a3e89b04a 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -599,14 +599,17 @@ InstallableFlake::InstallableFlake( Strings prefixes, const flake::LockFlags & lockFlags) : InstallableValue(state), + cmd(cmd), flakeRef(flakeRef), attrPaths(fragment == "" ? attrPaths : Strings{(std::string) fragment}), prefixes(fragment == "" ? Strings{} : prefixes), outputsSpec(std::move(outputsSpec)), lockFlags(lockFlags) { + #if 0 if (cmd && cmd->getAutoArgs(*state)->size()) throw UsageError("'--arg' and '--argstr' are incompatible with flakes"); + #endif } std::tuple InstallableFlake::toDerivation() @@ -615,42 +618,118 @@ std::tuple InstallableF auto attrPath = attr->getAttrPathStr(); - if (!attr->isDerivation()) - throw Error("flake output attribute '%s' is not a derivation", attrPath); + auto type = attr->maybeGetStringAttr(state->sType).value_or(""); - auto drvPath = attr->forceDerivation(); + if (type == "derivation") { - std::set outputsToInstall; - std::optional priority; + auto drvPath = attr->forceDerivation(); - if (auto aMeta = attr->maybeGetAttr(state->sMeta)) { - if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall")) - for (auto & s : aOutputsToInstall->getListOfStrings()) - outputsToInstall.insert(s); - if (auto aPriority = aMeta->maybeGetAttr("priority")) - priority = aPriority->getInt(); - } + std::set outputsToInstall; + std::optional priority; + + if (auto aMeta = attr->maybeGetAttr(state->sMeta)) { + if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall")) + for (auto & s : aOutputsToInstall->getListOfStrings()) + outputsToInstall.insert(s); + if (auto aPriority = aMeta->maybeGetAttr("priority")) + priority = aPriority->getInt(); + } - if (outputsToInstall.empty() || std::get_if(&outputsSpec)) { - outputsToInstall.clear(); - if (auto aOutputs = attr->maybeGetAttr(state->sOutputs)) - for (auto & s : aOutputs->getListOfStrings()) - outputsToInstall.insert(s); + if (outputsToInstall.empty() || std::get_if(&outputsSpec)) { + outputsToInstall.clear(); + if (auto aOutputs = attr->maybeGetAttr(state->sOutputs)) + for (auto & s : aOutputs->getListOfStrings()) + outputsToInstall.insert(s); + } + + if (outputsToInstall.empty()) + outputsToInstall.insert("out"); + + if (auto outputNames = std::get_if(&outputsSpec)) + outputsToInstall = *outputNames; + + auto drvInfo = DerivationInfo { + .drvPath = std::move(drvPath), + .outputsToInstall = std::move(outputsToInstall), + .priority = priority, + }; + + return {attrPath, getLockedFlake()->flake.lockedRef, std::move(drvInfo)}; } - if (outputsToInstall.empty()) - outputsToInstall.insert("out"); + else if (type == "configurable") { - if (auto outputNames = std::get_if(&outputsSpec)) - outputsToInstall = *outputNames; + // FIXME: use eval cache - auto drvInfo = DerivationInfo { - .drvPath = std::move(drvPath), - .outputsToInstall = std::move(outputsToInstall), - .priority = priority, - }; + auto & build = attr->getAttr("build")->forceValue(); - return {attrPath, getLockedFlake()->flake.lockedRef, std::move(drvInfo)}; + struct ValueTree + { + std::map> children; + }; + + ValueTree tree; + + if (cmd) { + auto autoArgs = cmd->getAutoArgs(*state); + + for (auto & attr : *autoArgs) { + auto ss = tokenizeString>(state->symbols[attr.name], "."); + auto * pos = &tree; + for (const auto & [n, s] : enumerate(ss)) { + if (n + 1 == ss.size()) { + if (pos->children.find(s) != pos->children.end()) + throw Error("definition of '%s' clashes with a previous definition", state->symbols[attr.name]); + pos->children.emplace(s, attr.value); + } else { + auto i = pos->children.emplace(s, ValueTree()).first; + if (auto pos2 = std::get_if(&i->second)) + pos = pos2; + else + throw Error("definition of '%s' clashes with a previous definition", state->symbols[attr.name]); + } + } + } + } + + std::function buildAttrs; + buildAttrs = [&](const ValueTree & tree) + { + auto attrs = state->buildBindings(tree.children.size()); + for (auto & [name, child] : tree.children) { + if (auto tree2 = std::get_if(&child)) + attrs.insert(state->symbols.create(name), buildAttrs(*tree2)); + else if (auto v = std::get_if(&child)) + attrs.insert(state->symbols.create(name), *v); + } + auto vAttrs = state->allocValue(); + vAttrs->mkAttrs(attrs); + return vAttrs; + }; + + auto vArgs = buildAttrs(tree); + + auto vRes = state->allocValue(); + state->callFunction(build, *vArgs, *vRes, noPos); + + state->forceAttrs(*vRes, noPos); + + auto aDrvPath = vRes->attrs->get(state->sDrvPath); + assert(aDrvPath); + PathSet context; + auto drvPath = state->coerceToStorePath(aDrvPath->pos, *aDrvPath->value, context); + + auto drvInfo = DerivationInfo { + .drvPath = std::move(drvPath), + .outputsToInstall = {"out"}, + //.priority = priority, + }; + + return {attrPath, getLockedFlake()->flake.lockedRef, std::move(drvInfo)}; + } + + else + throw Error("flake output attribute '%s' is not a derivation", attrPath); } std::vector InstallableFlake::toDerivations() diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh index 5d715210e92..fdfcbb5060e 100644 --- a/src/libcmd/installables.hh +++ b/src/libcmd/installables.hh @@ -154,6 +154,7 @@ struct InstallableValue : Installable struct InstallableFlake : InstallableValue { + SourceExprCommand * cmd; FlakeRef flakeRef; Strings attrPaths; Strings prefixes; diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index d77b25898bf..b1dac47b7c1 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -552,6 +552,13 @@ ref AttrCursor::getAttr(std::string_view name) return getAttr(root->state.symbols.create(name)); } +std::optional AttrCursor::maybeGetStringAttr(Symbol name) +{ + auto cursor = maybeGetAttr(name); + if (!cursor) return std::nullopt; + return cursor->getString(); +} + OrSuggestions> AttrCursor::findAlongAttrPath(const std::vector & attrPath, bool force) { auto res = shared_from_this(); diff --git a/src/libexpr/eval-cache.hh b/src/libexpr/eval-cache.hh index c93e55b93d0..6880573b054 100644 --- a/src/libexpr/eval-cache.hh +++ b/src/libexpr/eval-cache.hh @@ -109,6 +109,8 @@ public: ref getAttr(std::string_view name); + std::optional maybeGetStringAttr(Symbol name); + /* Get an attribute along a chain of attrsets. Note that this does not auto-call functors or functions. */ OrSuggestions> findAlongAttrPath(const std::vector & attrPath, bool force = false); diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index d616b392191..e6956e801d1 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -70,12 +70,11 @@ std::string DrvInfo::querySystem() const std::optional DrvInfo::queryDrvPath() const { if (!drvPath && attrs) { - Bindings::iterator i = attrs->find(state->sDrvPath); - PathSet context; - if (i == attrs->end()) - drvPath = {std::nullopt}; - else + if (auto i = attrs->get(state->sDrvPath)) { + PathSet context; drvPath = {state->coerceToStorePath(i->pos, *i->value, context)}; + } else + drvPath = {std::nullopt}; } return drvPath.value_or(std::nullopt); } diff --git a/src/libutil/error.cc b/src/libutil/error.cc index 9172f67a65d..d9d8d5a4abb 100644 --- a/src/libutil/error.cc +++ b/src/libutil/error.cc @@ -198,7 +198,7 @@ void printAtPos(const ErrPos & pos, std::ostream & out) } } -static std::string indent(std::string_view indentFirst, std::string_view indentRest, std::string_view s) +std::string indent(std::string_view indentFirst, std::string_view indentRest, std::string_view s) { std::string res; bool first = true; diff --git a/src/libutil/error.hh b/src/libutil/error.hh index a53e9802e01..79f675751d6 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -107,6 +107,8 @@ void printCodeLines(std::ostream & out, void printAtPos(const ErrPos & pos, std::ostream & out); +std::string indent(std::string_view indentFirst, std::string_view indentRest, std::string_view s); + struct Trace { std::optional pos; hintformat hint; diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 09ccfa591f9..16fa6c54c88 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -700,4 +700,19 @@ template overloaded(Ts...) -> overloaded; std::string showBytes(uint64_t bytes); +/* Provide an addition operator between strings and string_views + inexplicably omitted from the standard library. */ +inline std::string operator + (const std::string & s1, std::string_view s2) +{ + auto s = s1; + s.append(s2); + return s; +} + +inline std::string operator + (std::string && s, std::string_view s2) +{ + s.append(s2); + return s; +} + } diff --git a/src/nix/app.cc b/src/nix/app.cc index 821964f8625..1a55043d9fb 100644 --- a/src/nix/app.cc +++ b/src/nix/app.cc @@ -64,7 +64,7 @@ UnresolvedApp Installable::toApp(EvalState & state) auto cursor = getCursor(state); auto attrPath = cursor->getAttrPath(); - auto type = cursor->getAttr("type")->getString(); + auto type = cursor->getAttr(state.sType)->getString(); std::string expected = !attrPath.empty() && state.symbols[attrPath[0]] == "apps" ? "app" : "derivation"; if (type != expected) diff --git a/src/nix/describe.cc b/src/nix/describe.cc new file mode 100644 index 00000000000..3d81474ee29 --- /dev/null +++ b/src/nix/describe.cc @@ -0,0 +1,85 @@ +#include "command.hh" +#include "eval-cache.hh" +#include "shared.hh" +#include "markdown.hh" + +#include + +using namespace nix; + +struct CmdDescribe : InstallableCommand +{ + std::optional filter; + + CmdDescribe() + { + addFlag({ + .longName = "filter", + .description = "Only show options that match this regular expression.", + .labels = {"regex"}, + .handler = {&filter}, + }); + } + + std::string description() override + { + return "show information about a configurable derivation"; + } + + Category category() override { return catSecondary; } + + // FIXME: add help + + void run(ref store) override + { + std::optional filterRegex; + if (filter) + filterRegex = std::regex(*filter, std::regex::extended | std::regex::icase); + + auto state = getEvalState(); + + auto cursor = installable->getCursor(*state); + + auto type = cursor->getAttr(state->sType)->getString(); + + if (type != "configurable") + throw Error("'%s' is not a configurable derivation", installable); + + std::ostringstream str; + + std::function &, std::string_view)> recurse; + recurse = [&](const ref & cursor, std::string_view attrPath) + { + auto type = cursor->getAttr(state->sType)->getString(); + + if (type == "optionSet") { + for (auto & attr : cursor->getAttrs()) { + if (attr == state->sType) continue; + recurse(cursor->getAttr(attr), + attrPath.empty() + ? state->symbols[attr] + : std::string(attrPath) + "." + state->symbols[attr]); + } + } + + else if (type == "option") { + if (filterRegex && !std::regex_search(std::string(attrPath), *filterRegex)) return; + auto typeId = trim(stripIndentation(cursor->getAttr("typeId")->getString())); + str << fmt("* `%s` (*%s*)\n", attrPath, typeId); + str << "\n"; + auto description = trim(stripIndentation(cursor->getAttr(state->sDescription)->getString())); + str << indent(" ", " ", description) << "\n\n"; + } + + else + throw Error("unexpected type '%s'", type); + }; + + recurse(cursor->getAttr("options"), ""); + + RunPager pager; + std::cout << renderMarkdownToTerminal(str.str()) << "\n"; + } +}; + +static auto rCmdDescribe = registerCommand("describe");