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

nixos/nixos-option: fix evaluator to render a full submodule entry #75439

Merged
merged 4 commits into from Feb 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 12 additions & 9 deletions nixos/doc/manual/man-nixos-option.xml
Expand Up @@ -14,12 +14,16 @@
<refsynopsisdiv>
<cmdsynopsis>
<command>nixos-option</command>

<arg>
<option>-I</option> <replaceable>path</replaceable>
<group choice='req'>
<arg choice='plain'><option>-r</option></arg>
<arg choice='plain'><option>--recursive</option></arg>
</group>
</arg>

<arg>
<option>--all</option>
<option>-I</option> <replaceable>path</replaceable>
</arg>

<arg>
Expand All @@ -46,23 +50,22 @@
</para>
<variablelist>
<varlistentry>
<term>
<option>-I</option> <replaceable>path</replaceable>
</term>
<term><option>-r</option></term>
<term><option>--recursive</option></term>
<listitem>
<para>
This option is passed to the underlying
<command>nix-instantiate</command> invocation.
Print all the values at or below the specified path recursively.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>--all</option>
<option>-I</option> <replaceable>path</replaceable>
</term>
<listitem>
<para>
Print the values of all options.
This option is passed to the underlying
<command>nix-instantiate</command> invocation.
</para>
</listitem>
</varlistentry>
Expand Down
2 changes: 1 addition & 1 deletion nixos/doc/manual/release-notes/rl-2003.xml
Expand Up @@ -52,7 +52,7 @@
<listitem>
<para>
<command>nixos-option</command> has been rewritten in C++, speeding it up, improving correctness,
and adding a <option>--all</option> option which prints all options and their values.
and adding a <option>-r</option> option which prints all options and their values recursively.
</para>
</listitem>
</itemizedlist>
Expand Down
251 changes: 138 additions & 113 deletions nixos/modules/installer/tools/nixos-option/nixos-option.cc
Expand Up @@ -131,12 +131,12 @@ bool isOption(Context & ctx, const Value & v)
if (v.type != tAttrs) {
return false;
}
const auto & atualType = v.attrs->find(ctx.underscoreType);
if (atualType == v.attrs->end()) {
const auto & actualType = v.attrs->find(ctx.underscoreType);
if (actualType == v.attrs->end()) {
return false;
}
try {
Value evaluatedType = evaluateValue(ctx, *atualType->value);
Value evaluatedType = evaluateValue(ctx, *actualType->value);
if (evaluatedType.type != tString) {
return false;
}
Expand Down Expand Up @@ -197,9 +197,107 @@ void recurse(const std::function<bool(const std::string & path, std::variant<Val
}
}

// Calls f on all the option names
void mapOptions(const std::function<void(const std::string & path)> & f, Context & ctx, Value root)
bool optionTypeIs(Context & ctx, Value & v, const std::string & soughtType)
{
try {
const auto & typeLookup = v.attrs->find(ctx.state.sType);
if (typeLookup == v.attrs->end()) {
return false;
}
Value type = evaluateValue(ctx, *typeLookup->value);
if (type.type != tAttrs) {
return false;
}
const auto & nameLookup = type.attrs->find(ctx.state.sName);
if (nameLookup == type.attrs->end()) {
return false;
}
Value name = evaluateValue(ctx, *nameLookup->value);
if (name.type != tString) {
return false;
}
return name.string.s == soughtType;
} catch (Error &) {
return false;
}
}

bool isAggregateOptionType(Context & ctx, Value & v)
{
return optionTypeIs(ctx, v, "attrsOf") || optionTypeIs(ctx, v, "listOf") || optionTypeIs(ctx, v, "loaOf");
}

MakeError(OptionPathError, EvalError);

Value getSubOptions(Context & ctx, Value & option)
{
Value getSubOptions = evaluateValue(ctx, *findAlongAttrPath(ctx.state, "type.getSubOptions", ctx.autoArgs, option));
if (getSubOptions.type != tLambda) {
throw OptionPathError("Option's type.getSubOptions isn't a function");
}
Value emptyString{};
nix::mkString(emptyString, "");
Value v;
ctx.state.callFunction(getSubOptions, emptyString, v, nix::Pos{});
return v;
}

// Carefully walk an option path, looking for sub-options when a path walks past
// an option value.
struct FindAlongOptionPathRet
{
Value option;
std::string path;
};
FindAlongOptionPathRet findAlongOptionPath(Context & ctx, const std::string & path)
{
Strings tokens = parseAttrPath(path);
Value v = ctx.optionsRoot;
std::string processedPath;
for (auto i = tokens.begin(); i != tokens.end(); i++) {
const auto & attr = *i;
try {
bool lastAttribute = std::next(i) == tokens.end();
v = evaluateValue(ctx, v);
if (attr.empty()) {
throw OptionPathError("empty attribute name");
}
if (isOption(ctx, v) && optionTypeIs(ctx, v, "submodule")) {
v = getSubOptions(ctx, v);
}
if (isOption(ctx, v) && isAggregateOptionType(ctx, v)) {
auto subOptions = getSubOptions(ctx, v);
if (lastAttribute && subOptions.attrs->empty()) {
break;
}
v = subOptions;
// Note that we've consumed attr, but didn't actually use it. This is the path component that's looked
// up in the list or attribute set that doesn't name an option -- the "root" in "users.users.root.name".
} else if (v.type != tAttrs) {
throw OptionPathError("Value is %s while a set was expected", showType(v));
} else {
const auto & next = v.attrs->find(ctx.state.symbols.create(attr));
if (next == v.attrs->end()) {
throw OptionPathError("Attribute not found", attr, path);
}
v = *next->value;
}
processedPath = appendPath(processedPath, attr);
} catch (OptionPathError & e) {
throw OptionPathError("At '%s' in path '%s': %s", attr, path, e.msg());
}
}
return {v, processedPath};
}

// Calls f on all the option names at or below the option described by `path`.
// Note that "the option described by `path`" is not trivial -- if path describes a value inside an aggregate
// option (such as users.users.root), the *option* described by that path is one path component shorter
// (eg: users.users), which results in f being called on sibling-paths (eg: users.users.nixbld1). If f
// doesn't want these, it must do its own filtering.
void mapOptions(const std::function<void(const std::string & path)> & f, Context & ctx, const std::string & path)
{
auto root = findAlongOptionPath(ctx, path);
recurse(
[f, &ctx](const std::string & path, std::variant<Value, std::exception_ptr> v) {
bool isOpt = std::holds_alternative<std::exception_ptr>(v) || isOption(ctx, std::get<Value>(v));
Expand All @@ -208,7 +306,7 @@ void mapOptions(const std::function<void(const std::string & path)> & f, Context
}
return !isOpt;
},
ctx, root, "");
ctx, root.option, root.path);
}

// Calls f on all the config values inside one option.
Expand Down Expand Up @@ -294,9 +392,11 @@ void printAttrs(Context & ctx, Out & out, Value & v, const std::string & path)
Out attrsOut(out, "{", "}", v.attrs->size());
for (const auto & a : v.attrs->lexicographicOrder()) {
std::string name = a->name;
attrsOut << name << " = ";
printValue(ctx, attrsOut, *a->value, appendPath(path, name));
attrsOut << ";" << Out::sep;
if (!forbiddenRecursionName(name)) {
attrsOut << name << " = ";
printValue(ctx, attrsOut, *a->value, appendPath(path, name));
attrsOut << ";" << Out::sep;
}
}
}

Expand Down Expand Up @@ -380,17 +480,26 @@ void printConfigValue(Context & ctx, Out & out, const std::string & path, std::v
out << ";\n";
}

void printAll(Context & ctx, Out & out)
// Replace with std::starts_with when C++20 is available
bool starts_with(const std::string & s, const std::string & prefix)
{
return s.size() >= prefix.size() &&
std::equal(s.begin(), std::next(s.begin(), prefix.size()), prefix.begin(), prefix.end());
}

void printRecursive(Context & ctx, Out & out, const std::string & path)
{
mapOptions(
[&ctx, &out](const std::string & optionPath) {
[&ctx, &out, &path](const std::string & optionPath) {
mapConfigValuesInOption(
[&ctx, &out](const std::string & configPath, std::variant<Value, std::exception_ptr> v) {
printConfigValue(ctx, out, configPath, v);
[&ctx, &out, &path](const std::string & configPath, std::variant<Value, std::exception_ptr> v) {
if (starts_with(configPath, path)) {
printConfigValue(ctx, out, configPath, v);
}
},
optionPath, ctx);
},
ctx, ctx.optionsRoot);
ctx, path);
}

void printAttr(Context & ctx, Out & out, const std::string & path, Value & root)
Expand Down Expand Up @@ -450,95 +559,17 @@ void printListing(Out & out, Value & v)
}
}

bool optionTypeIs(Context & ctx, Value & v, const std::string & soughtType)
{
try {
const auto & typeLookup = v.attrs->find(ctx.state.sType);
if (typeLookup == v.attrs->end()) {
return false;
}
Value type = evaluateValue(ctx, *typeLookup->value);
if (type.type != tAttrs) {
return false;
}
const auto & nameLookup = type.attrs->find(ctx.state.sName);
if (nameLookup == type.attrs->end()) {
return false;
}
Value name = evaluateValue(ctx, *nameLookup->value);
if (name.type != tString) {
return false;
}
return name.string.s == soughtType;
} catch (Error &) {
return false;
}
}

bool isAggregateOptionType(Context & ctx, Value & v)
{
return optionTypeIs(ctx, v, "attrsOf") || optionTypeIs(ctx, v, "listOf") || optionTypeIs(ctx, v, "loaOf");
}

MakeError(OptionPathError, EvalError);

Value getSubOptions(Context & ctx, Value & option)
{
Value getSubOptions = evaluateValue(ctx, *findAlongAttrPath(ctx.state, "type.getSubOptions", ctx.autoArgs, option));
if (getSubOptions.type != tLambda) {
throw OptionPathError("Option's type.getSubOptions isn't a function");
}
Value emptyString{};
nix::mkString(emptyString, "");
Value v;
ctx.state.callFunction(getSubOptions, emptyString, v, nix::Pos{});
return v;
}

// Carefully walk an option path, looking for sub-options when a path walks past
// an option value.
Value findAlongOptionPath(Context & ctx, const std::string & path)
{
Strings tokens = parseAttrPath(path);
Value v = ctx.optionsRoot;
for (auto i = tokens.begin(); i != tokens.end(); i++) {
const auto & attr = *i;
try {
bool lastAttribute = std::next(i) == tokens.end();
v = evaluateValue(ctx, v);
if (attr.empty()) {
throw OptionPathError("empty attribute name");
}
if (isOption(ctx, v) && optionTypeIs(ctx, v, "submodule")) {
v = getSubOptions(ctx, v);
}
if (isOption(ctx, v) && isAggregateOptionType(ctx, v) && !lastAttribute) {
v = getSubOptions(ctx, v);
// Note that we've consumed attr, but didn't actually use it. This is the path component that's looked
// up in the list or attribute set that doesn't name an option -- the "root" in "users.users.root.name".
} else if (v.type != tAttrs) {
throw OptionPathError("Value is %s while a set was expected", showType(v));
} else {
const auto & next = v.attrs->find(ctx.state.symbols.create(attr));
if (next == v.attrs->end()) {
throw OptionPathError("Attribute not found", attr, path);
}
v = *next->value;
}
} catch (OptionPathError & e) {
throw OptionPathError("At '%s' in path '%s': %s", attr, path, e.msg());
}
}
return v;
}

void printOne(Context & ctx, Out & out, const std::string & path)
{
try {
Value option = findAlongOptionPath(ctx, path);
auto result = findAlongOptionPath(ctx, path);
Value & option = result.option;
option = evaluateValue(ctx, option);
if (path != result.path) {
out << "Note: showing " << result.path << " instead of " << path << "\n";
}
if (isOption(ctx, option)) {
printOption(ctx, out, path, option);
printOption(ctx, out, result.path, option);
} else {
printListing(out, option);
}
Expand All @@ -552,7 +583,7 @@ void printOne(Context & ctx, Out & out, const std::string & path)

int main(int argc, char ** argv)
{
bool all = false;
bool recursive = false;
std::string path = ".";
std::string optionsExpr = "(import <nixpkgs/nixos> {}).options";
std::string configExpr = "(import <nixpkgs/nixos> {}).config";
Expand All @@ -568,8 +599,8 @@ int main(int argc, char ** argv)
nix::showManPage("nixos-option");
} else if (*arg == "--version") {
nix::printVersion("nixos-option");
} else if (*arg == "--all") {
all = true;
} else if (*arg == "-r" || *arg == "--recursive") {
recursive = true;
} else if (*arg == "--path") {
path = nix::getArg(*arg, arg, end);
} else if (*arg == "--options_expr") {
Expand Down Expand Up @@ -598,18 +629,12 @@ int main(int argc, char ** argv)
Context ctx{*state, *myArgs.getAutoArgs(*state), optionsRoot, configRoot};
Out out(std::cout);

if (all) {
if (!args.empty()) {
throw UsageError("--all cannot be used with arguments");
}
printAll(ctx, out);
} else {
if (args.empty()) {
printOne(ctx, out, "");
}
for (const auto & arg : args) {
printOne(ctx, out, arg);
}
auto print = recursive ? printRecursive : printOne;
if (args.empty()) {
print(ctx, out, "");
}
for (const auto & arg : args) {
print(ctx, out, arg);
}

ctx.state.printStats();
Expand Down