summary refs log tree commit diff
diff options
context:
space:
mode:
authorMaximilian Bosch <maximilian@mbosch.me>2020-02-01 10:00:59 +0100
committerGitHub <noreply@github.com>2020-02-01 10:00:59 +0100
commit7f49fa63caf2e61d71e4b69daba675bf7e00bcad (patch)
treed13684cc9d560abc4c22c652e70785e3ab55f35d
parent1e5cfc9cc88b3f11bca636069ea3aa1525a9c579 (diff)
parented51fd0033b5d5e2ad40cca02cc9864db845b586 (diff)
downloadnixpkgs-7f49fa63caf2e61d71e4b69daba675bf7e00bcad.tar
nixpkgs-7f49fa63caf2e61d71e4b69daba675bf7e00bcad.tar.gz
nixpkgs-7f49fa63caf2e61d71e4b69daba675bf7e00bcad.tar.bz2
nixpkgs-7f49fa63caf2e61d71e4b69daba675bf7e00bcad.tar.lz
nixpkgs-7f49fa63caf2e61d71e4b69daba675bf7e00bcad.tar.xz
nixpkgs-7f49fa63caf2e61d71e4b69daba675bf7e00bcad.tar.zst
nixpkgs-7f49fa63caf2e61d71e4b69daba675bf7e00bcad.zip
Merge pull request #75439 from Ma27/submodule-fixes-for-nixos-option
nixos/nixos-option: fix evaluator to render a full submodule entry
-rw-r--r--nixos/doc/manual/man-nixos-option.xml21
-rw-r--r--nixos/doc/manual/release-notes/rl-2003.xml2
-rw-r--r--nixos/modules/installer/tools/nixos-option/nixos-option.cc251
3 files changed, 151 insertions, 123 deletions
diff --git a/nixos/doc/manual/man-nixos-option.xml b/nixos/doc/manual/man-nixos-option.xml
index b82f3125609..b921386d0df 100644
--- a/nixos/doc/manual/man-nixos-option.xml
+++ b/nixos/doc/manual/man-nixos-option.xml
@@ -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>
@@ -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>
diff --git a/nixos/doc/manual/release-notes/rl-2003.xml b/nixos/doc/manual/release-notes/rl-2003.xml
index 4ee9ebd6a2b..8883c99d4b7 100644
--- a/nixos/doc/manual/release-notes/rl-2003.xml
+++ b/nixos/doc/manual/release-notes/rl-2003.xml
@@ -59,7 +59,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>
    <listitem>
diff --git a/nixos/modules/installer/tools/nixos-option/nixos-option.cc b/nixos/modules/installer/tools/nixos-option/nixos-option.cc
index 9b92dc829cd..1a7b07a74f8 100644
--- a/nixos/modules/installer/tools/nixos-option/nixos-option.cc
+++ b/nixos/modules/installer/tools/nixos-option/nixos-option.cc
@@ -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;
         }
@@ -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));
@@ -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.
@@ -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;
+        }
     }
 }
 
@@ -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)
@@ -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);
         }
@@ -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";
@@ -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") {
@@ -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();