Skip to content
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

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 106 additions & 27 deletions src/libcmd/installables.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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") {
Copy link
Member

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 the 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) {
Copy link
Member

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, 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]);
Copy link
Member

Choose a reason for hiding this comment

The 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;
Copy link
Member

Choose a reason for hiding this comment

The 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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the pos be wherever we found attr?


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()
Expand Down
1 change: 1 addition & 0 deletions src/libcmd/installables.hh
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ struct InstallableValue : Installable

struct InstallableFlake : InstallableValue
{
SourceExprCommand * cmd;
FlakeRef flakeRef;
Strings attrPaths;
Strings prefixes;
Expand Down
7 changes: 7 additions & 0 deletions src/libexpr/eval-cache.cc
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,13 @@ ref<AttrCursor> AttrCursor::getAttr(std::string_view name)
return getAttr(root->state.symbols.create(name));
}

std::optional<std::string> AttrCursor::maybeGetStringAttr(Symbol name)
{
auto cursor = maybeGetAttr(name);
if (!cursor) return std::nullopt;
return cursor->getString();
}

OrSuggestions<ref<AttrCursor>> AttrCursor::findAlongAttrPath(const std::vector<Symbol> & attrPath, bool force)
{
auto res = shared_from_this();
Expand Down
2 changes: 2 additions & 0 deletions src/libexpr/eval-cache.hh
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ public:

ref<AttrCursor> getAttr(std::string_view name);

std::optional<std::string> maybeGetStringAttr(Symbol name);

/* Get an attribute along a chain of attrsets. Note that this does
not auto-call functors or functions. */
OrSuggestions<ref<AttrCursor>> findAlongAttrPath(const std::vector<Symbol> & attrPath, bool force = false);
Expand Down
9 changes: 4 additions & 5 deletions src/libexpr/get-drvs.cc
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,11 @@ std::string DrvInfo::querySystem() const
std::optional<StorePath> 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);
}
Expand Down
2 changes: 1 addition & 1 deletion src/libutil/error.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/libutil/error.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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<ErrPos> pos;
hintformat hint;
Expand Down
15 changes: 15 additions & 0 deletions src/libutil/util.hh
Original file line number Diff line number Diff line change
Expand Up @@ -700,4 +700,19 @@ template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
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;
}

}
2 changes: 1 addition & 1 deletion src/nix/app.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
85 changes: 85 additions & 0 deletions src/nix/describe.cc
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")
Copy link
Member

Choose a reason for hiding this comment

The 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 nix describe to also work on flakes.

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");