-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Configurable derivations #6583
base: master
Are you sure you want to change the base?
Configurable derivations #6583
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableFlake::toDerivation() | ||
|
@@ -615,42 +618,118 @@ std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> 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<std::string> outputsToInstall; | ||
std::optional<NixInt> 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<std::string> outputsToInstall; | ||
std::optional<NixInt> 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<AllOutputs>(&outputsSpec)) { | ||
outputsToInstall.clear(); | ||
if (auto aOutputs = attr->maybeGetAttr(state->sOutputs)) | ||
for (auto & s : aOutputs->getListOfStrings()) | ||
outputsToInstall.insert(s); | ||
if (outputsToInstall.empty() || std::get_if<AllOutputs>(&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<OutputNames>(&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<OutputNames>(&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<std::string, std::variant<Value *, ValueTree>> children; | ||
}; | ||
|
||
ValueTree tree; | ||
|
||
if (cmd) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we want other things besides drvs/installables to be configurable, we should probably extract everything from here to line 713 into a separate function that can be used for other interfaces. |
||
auto autoArgs = cmd->getAutoArgs(*state); | ||
|
||
for (auto & attr : *autoArgs) { | ||
auto ss = tokenizeString<std::vector<std::string>>(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]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Many tools with non-repeatable flags have the convention that the later can be used to override the former. Is there a reason we went with this approach instead? |
||
pos->children.emplace(s, attr.value); | ||
} else { | ||
auto i = pos->children.emplace(s, ValueTree()).first; | ||
if (auto pos2 = std::get_if<ValueTree>(&i->second)) | ||
pos = pos2; | ||
else | ||
throw Error("definition of '%s' clashes with a previous definition", state->symbols[attr.name]); | ||
} | ||
} | ||
} | ||
} | ||
|
||
std::function<Value * (const ValueTree & tree)> buildAttrs; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be worth a comment that this declared here for recursive calling, it surprised me. |
||
buildAttrs = [&](const ValueTree & tree) | ||
{ | ||
auto attrs = state->buildBindings(tree.children.size()); | ||
for (auto & [name, child] : tree.children) { | ||
if (auto tree2 = std::get_if<ValueTree>(&child)) | ||
attrs.insert(state->symbols.create(name), buildAttrs(*tree2)); | ||
else if (auto v = std::get_if<Value *>(&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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't the |
||
|
||
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<InstallableValue::DerivationInfo> InstallableFlake::toDerivations() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
#include "command.hh" | ||
#include "eval-cache.hh" | ||
#include "shared.hh" | ||
#include "markdown.hh" | ||
|
||
#include <regex> | ||
|
||
using namespace nix; | ||
|
||
struct CmdDescribe : InstallableCommand | ||
{ | ||
std::optional<std::string> 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> store) override | ||
{ | ||
std::optional<std::regex> 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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe it's overloading the concept, but it seems like you might expect |
||
throw Error("'%s' is not a configurable derivation", installable); | ||
|
||
std::ostringstream str; | ||
|
||
std::function<void(const ref<eval_cache::AttrCursor> &, std::string_view)> recurse; | ||
recurse = [&](const ref<eval_cache::AttrCursor> & 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<CmdDescribe>("describe"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want other things besides drvs/installables to be configurable, should we allow
type
to be some kind of structured value? E.g.[ "configurable" "derivation" ]
or{ configurable = true; configured = "derivation"; }
or something?The alternative is to dynamically resolve this, i.e. check
type
after resolving theconfigurable
.