From 3366ca10942525a1bab9ef4ad1595be7fec8a644 Mon Sep 17 00:00:00 2001 From: Chuck Date: Thu, 12 Dec 2019 09:39:09 -0800 Subject: [PATCH 1/3] nixos/nixos-option: Show values inside aggregate options uniformly 1. This makes aggregates of submodules (including the very important "nixos-option users.users." case) behave the same way as any other you-need-to-keep-typing-to-get-to-an-option-leaf (eg: "nixos-option environment"). Before e0780c5: $ nixos-option users.users.root error: At 'root' in path 'users.users.root': Attribute not found An error occurred while looking for attribute names. Are you sure that 'users.users.root' exists? After e0780c5 but before this change, this query just printed out a raw thing, which is behavior that belongs in "nix eval", "nix-instantiate --eval", or "nix repl <<<": $ nixos-option users.users.root { _module = { args = { name = "root"; }; check = true; }; createHome = false; cryptHomeLuks = null; description = "System administrator"; ... After this change: $ nixos-option users.users.root This attribute set contains: createHome cryptHomeLuks description extraGroups group hashedPassword ... 2. For aggregates of other types (not submodules), print out the option that contains them rather than printing an error message. Before: $ nixos-option environment.shellAliases.l error: At 'l' in path 'environment.shellAliases.l': Attribute not found An error occurred while looking for attribute names. Are you sure that 'environment.shellAliases.l' exists? After: $ nixos-option environment.shellAliases.l Note: showing environment.shellAliases instead of environment.shellAliases.l Value: { l = "ls -alh"; ll = "ls -l"; ls = "ls --color=tty"; } ... --- .../tools/nixos-option/nixos-option.cc | 68 +++++++++---------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/nixos/modules/installer/tools/nixos-option/nixos-option.cc b/nixos/modules/installer/tools/nixos-option/nixos-option.cc index 89f9c88be9646..5aa3c766d1036 100644 --- a/nixos/modules/installer/tools/nixos-option/nixos-option.cc +++ b/nixos/modules/installer/tools/nixos-option/nixos-option.cc @@ -106,14 +106,13 @@ struct Context { Context(EvalState & state, Bindings & autoArgs, Value optionsRoot, Value configRoot) : state(state), autoArgs(autoArgs), optionsRoot(optionsRoot), configRoot(configRoot), - underscoreType(state.symbols.create("_type")), submoduleAttr(state.symbols.create("__nixos-option-submodule-attr")) + underscoreType(state.symbols.create("_type")) {} EvalState & state; Bindings & autoArgs; Value optionsRoot; Value configRoot; Symbol underscoreType; - Symbol submoduleAttr; }; Value evaluateValue(Context & ctx, Value & v) @@ -127,7 +126,7 @@ Value evaluateValue(Context & ctx, Value & v) return called; } -bool isType(Context & ctx, const Value & v, const std::string & type) +bool isOption(Context & ctx, const Value & v) { if (v.type != tAttrs) { return false; @@ -141,17 +140,12 @@ bool isType(Context & ctx, const Value & v, const std::string & type) if (evaluatedType.type != tString) { return false; } - return static_cast(evaluatedType.string.s) == type; + return static_cast(evaluatedType.string.s) == "option"; } catch (Error &) { return false; } } -bool isOption(Context & ctx, const Value & v) -{ - return isType(ctx, v, "option"); -} - // Add quotes to a component of a path. // These are needed for paths like: // fileSystems."/".fsType @@ -300,9 +294,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; + } } } @@ -503,10 +499,16 @@ Value getSubOptions(Context & ctx, Value & option) // 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) +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 { @@ -518,48 +520,42 @@ Value findAlongOptionPath(Context & ctx, const std::string & path) if (isOption(ctx, v) && optionTypeIs(ctx, v, "submodule")) { v = getSubOptions(ctx, v); } - if (isOption(ctx, v) && isAggregateOptionType(ctx, v) && !lastAttribute) { - 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 { - auto symbol = ctx.state.symbols.create(attr); - const auto & next = v.attrs->find(symbol); + const auto & next = v.attrs->find(ctx.state.symbols.create(attr)); if (next == v.attrs->end()) { - try { - const auto & value = findAlongAttrPath(ctx.state, path, ctx.autoArgs, ctx.configRoot); - Value &dummyOpt = *ctx.state.allocValue(); - ctx.state.mkAttrs(dummyOpt, 1); - Value *type = ctx.state.allocAttr(dummyOpt, ctx.state.symbols.create("_type")); - nix::mkString(*type, ctx.submoduleAttr); - v = dummyOpt; - break; - } catch (Error & e) { - // do nothing - } throw OptionPathError("Attribute not found", attr, path); - } else { - v = *next->value; } + v = *next->value; } + processedPath = appendPath(processedPath, attr); } catch (OptionPathError & e) { throw OptionPathError("At '%s' in path '%s': %s", attr, path, e.msg()); } } - return v; + return {v, processedPath}; } 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 (isType(ctx, option, ctx.submoduleAttr)) { - printAttr(ctx, out, path, ctx.configRoot); - } else if (isOption(ctx, option)) { - printOption(ctx, out, path, option); + if (path != result.path) { + out << "Note: showing " << result.path << " instead of " << path << "\n"; + } + if (isOption(ctx, option)) { + printOption(ctx, out, result.path, option); } else { printListing(out, option); } From 55e1db3fa04e3ca68af7ef0c4eaafd65f7e02e17 Mon Sep 17 00:00:00 2001 From: Chuck Date: Tue, 17 Dec 2019 11:08:24 -0800 Subject: [PATCH 2/3] nixos/nixos-option: Refactor: Move functions around --- .../tools/nixos-option/nixos-option.cc | 186 +++++++++--------- 1 file changed, 93 insertions(+), 93 deletions(-) diff --git a/nixos/modules/installer/tools/nixos-option/nixos-option.cc b/nixos/modules/installer/tools/nixos-option/nixos-option.cc index 5aa3c766d1036..a5c0b65baeb80 100644 --- a/nixos/modules/installer/tools/nixos-option/nixos-option.cc +++ b/nixos/modules/installer/tools/nixos-option/nixos-option.cc @@ -197,6 +197,99 @@ void recurse(const std::functionfind(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 void mapOptions(const std::function & f, Context & ctx, Value root) { @@ -452,99 +545,6 @@ 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. -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}; -} - void printOne(Context & ctx, Out & out, const std::string & path) { try { From 16d6c6d2b584e5ddafeb189475cadca64ecd4d54 Mon Sep 17 00:00:00 2001 From: Chuck Date: Tue, 17 Dec 2019 12:08:07 -0800 Subject: [PATCH 3/3] nixos/nixos-option: Convert --all into -r --- nixos/doc/manual/man-nixos-option.xml | 21 ++++--- nixos/doc/manual/release-notes/rl-2003.xml | 2 +- .../tools/nixos-option/nixos-option.cc | 56 +++++++++++-------- 3 files changed, 45 insertions(+), 34 deletions(-) diff --git a/nixos/doc/manual/man-nixos-option.xml b/nixos/doc/manual/man-nixos-option.xml index beabf020c92ae..0c6a6950a0561 100644 --- a/nixos/doc/manual/man-nixos-option.xml +++ b/nixos/doc/manual/man-nixos-option.xml @@ -14,12 +14,16 @@ nixos-option + - path + + + + - + path @@ -46,23 +50,22 @@ - - path - + + - This option is passed to the underlying - nix-instantiate invocation. + Print all the values at or below the specified path recursively. - + path - Print the values of all options. + This option is passed to the underlying + nix-instantiate invocation. diff --git a/nixos/doc/manual/release-notes/rl-2003.xml b/nixos/doc/manual/release-notes/rl-2003.xml index b8af15f59c953..417b8524ab826 100644 --- a/nixos/doc/manual/release-notes/rl-2003.xml +++ b/nixos/doc/manual/release-notes/rl-2003.xml @@ -52,7 +52,7 @@ nixos-option has been rewritten in C++, speeding it up, improving correctness, - and adding a option which prints all options and their values. + and adding a option which prints all options and their values recursively. diff --git a/nixos/modules/installer/tools/nixos-option/nixos-option.cc b/nixos/modules/installer/tools/nixos-option/nixos-option.cc index a5c0b65baeb80..1a7b07a74f8ac 100644 --- a/nixos/modules/installer/tools/nixos-option/nixos-option.cc +++ b/nixos/modules/installer/tools/nixos-option/nixos-option.cc @@ -290,9 +290,14 @@ FindAlongOptionPathRet findAlongOptionPath(Context & ctx, const std::string & pa return {v, processedPath}; } -// Calls f on all the option names -void mapOptions(const std::function & f, Context & ctx, Value root) -{ +// 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 & f, Context & ctx, const std::string & path) +{ + auto root = findAlongOptionPath(ctx, path); recurse( [f, &ctx](const std::string & path, std::variant v) { bool isOpt = std::holds_alternative(v) || isOption(ctx, std::get(v)); @@ -301,7 +306,7 @@ void mapOptions(const std::function & f, Context } return !isOpt; }, - ctx, root, ""); + ctx, root.option, root.path); } // Calls f on all the config values inside one option. @@ -475,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 v) { - printConfigValue(ctx, out, configPath, v); + [&ctx, &out, &path](const std::string & configPath, std::variant 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) @@ -569,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 {}).options"; std::string configExpr = "(import {}).config"; @@ -585,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") { @@ -615,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();