diff --git a/nixos/doc/manual/man-nixos-option.xml b/nixos/doc/manual/man-nixos-option.xml
index 81e3739b3be..beabf020c92 100644
--- a/nixos/doc/manual/man-nixos-option.xml
+++ b/nixos/doc/manual/man-nixos-option.xml
@@ -19,14 +19,10 @@
-
+
-
-
-
-
option.name
@@ -62,22 +58,11 @@
-
+
- This option enables verbose mode, which currently is just the Bash
- set debug mode.
-
-
-
-
-
-
-
-
-
- This option causes the output to be rendered as XML.
+ Print the values of all options.
diff --git a/nixos/modules/installer/tools/nixos-option.sh b/nixos/modules/installer/tools/nixos-option.sh
deleted file mode 100644
index 4560e9c7403..00000000000
--- a/nixos/modules/installer/tools/nixos-option.sh
+++ /dev/null
@@ -1,327 +0,0 @@
-#! @shell@ -e
-
-# FIXME: rewrite this in a more suitable language.
-
-usage () {
- exec man nixos-option
- exit 1
-}
-
-#####################
-# Process Arguments #
-#####################
-
-xml=false
-verbose=false
-nixPath=""
-
-option=""
-exit_code=0
-
-argfun=""
-for arg; do
- if test -z "$argfun"; then
- case $arg in
- -*)
- sarg="$arg"
- longarg=""
- while test "$sarg" != "-"; do
- case $sarg in
- --*) longarg=$arg; sarg="--";;
- -I) argfun="include_nixpath";;
- -*) usage;;
- esac
- # remove the first letter option
- sarg="-${sarg#??}"
- done
- ;;
- *) longarg=$arg;;
- esac
- for larg in $longarg; do
- case $larg in
- --xml) xml=true;;
- --verbose) verbose=true;;
- --help) usage;;
- -*) usage;;
- *) if test -z "$option"; then
- option="$larg"
- else
- usage
- fi;;
- esac
- done
- else
- case $argfun in
- set_*)
- var=$(echo $argfun | sed 's,^set_,,')
- eval $var=$arg
- ;;
- include_nixpath)
- nixPath="-I $arg $nixPath"
- ;;
- esac
- argfun=""
- fi
-done
-
-if $verbose; then
- set -x
-else
- set +x
-fi
-
-#############################
-# Process the configuration #
-#############################
-
-evalNix(){
- # disable `-e` flag, it's possible that the evaluation of `nix-instantiate` fails (e.g. due to broken pkgs)
- set +e
- result=$(nix-instantiate ${nixPath:+$nixPath} - --eval-only "$@" 2>&1)
- exit_code=$?
- set -e
-
- if test $exit_code -eq 0; then
- sed '/^warning: Nix search path/d' <&2 < {};
- nixpkgs = import {};
-in with nixpkgs.lib;
-"
-
-# This function is used for converting the option definition path given by
-# the user into accessors for reaching the definition and the declaration
-# corresponding to this option.
-generateAccessors(){
- if result=$(evalNix --strict --show-trace <"
- else if strict then
- if isAttrs x then mapAttrs (n: cleanOutput) x
- else if isList x then map cleanOutput x
- else x
- else x;
-in
- cleanOutput value
-EOF
-}
-
-evalOpt(){
- evalAttr "option" "" "$@"
-}
-
-evalCfg(){
- local strict="$1"
- evalAttr "config" "$strict"
-}
-
-findSources(){
- local suffix=$1
- evalNix --strict <,0,g; :inner; s/{[^\{\}]*};/0;/g; t inner;' | \
- evalNix --strict
-}
-
-# map a simple list which contains strings or paths.
-nixMap() {
- local fun="$1"
- local list="$2"
- local elem
- for elem in $list; do
- test $elem = '[' -o $elem = ']' && continue;
- $fun $elem
- done
-}
-
-# This duplicates the work made below, but it is useful for processing
-# the output of nixos-option with other tools such as nixos-gui.
-if $xml; then
- evalNix --xml --no-location < /dev/null)" = '"option"'; then
- echo "Value:"
- evalCfg 1
-
- echo
-
- echo "Default:"
- if default=$(evalOpt "default" - 2> /dev/null); then
- echo "$default"
- else
- echo ""
- fi
- echo
- if example=$(evalOpt "example" - 2> /dev/null); then
- echo "Example:"
- echo "$example"
- echo
- fi
- echo "Description:"
- echo
- echo $(evalOpt "description")
-
- echo $desc;
-
- printPath () { echo " $1"; }
-
- echo "Declared by:"
- nixMap printPath "$(findSources "declarations")"
- echo
- echo "Defined by:"
- nixMap printPath "$(findSources "files")"
- echo
-
-else
- # echo 1>&2 "Warning: This value is not an option."
-
- result=$(evalCfg "")
- if [ ! -z "$result" ]; then
- names=$(attrNames "$result" 2> /dev/null)
- echo 1>&2 "This attribute set contains:"
- escapeQuotes () { eval echo "$1"; }
- nixMap escapeQuotes "$names"
- else
- echo 1>&2 "An error occurred while looking for attribute names. Are you sure that '$option' exists?"
- fi
-fi
-
-exit $exit_code
diff --git a/nixos/modules/installer/tools/nixos-option/CMakeLists.txt b/nixos/modules/installer/tools/nixos-option/CMakeLists.txt
new file mode 100644
index 00000000000..e5834598c4f
--- /dev/null
+++ b/nixos/modules/installer/tools/nixos-option/CMakeLists.txt
@@ -0,0 +1,8 @@
+cmake_minimum_required (VERSION 2.6)
+project (nixos-option)
+
+add_executable(nixos-option nixos-option.cc libnix-copy-paste.cc)
+target_link_libraries(nixos-option PRIVATE -lnixmain -lnixexpr -lnixstore -lnixutil)
+target_compile_features(nixos-option PRIVATE cxx_std_17)
+
+install (TARGETS nixos-option DESTINATION bin)
diff --git a/nixos/modules/installer/tools/nixos-option/default.nix b/nixos/modules/installer/tools/nixos-option/default.nix
new file mode 100644
index 00000000000..6464a91052c
--- /dev/null
+++ b/nixos/modules/installer/tools/nixos-option/default.nix
@@ -0,0 +1,8 @@
+{stdenv, boost, cmake, pkgconfig, nix, ... }:
+stdenv.mkDerivation rec {
+ name = "nixos-option";
+ src = ./.;
+ nativeBuildInputs = [ cmake pkgconfig ];
+ buildInputs = [ boost nix ];
+ enableParallelBuilding = true;
+}
diff --git a/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.cc b/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.cc
new file mode 100644
index 00000000000..81de5ff8523
--- /dev/null
+++ b/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.cc
@@ -0,0 +1,81 @@
+// These are useful methods inside the nix library that ought to be exported.
+// Since they are not, copy/paste them here.
+// TODO: Delete these and use the ones in the library as they become available.
+
+#include // for nix/globals.hh's reference to SYSTEM
+
+#include "libnix-copy-paste.hh"
+#include // for basic_altstringbuf...
+#include // for basic_altstringbuf...
+#include // for basic_format
+#include // for format
+#include // for basic_format::basi...
+#include // for get_pointer
+#include // for operator<<, basic_...
+#include // for Strings, Error
+#include // for string, basic_string
+
+using boost::format;
+using nix::Error;
+using nix::Strings;
+using std::string;
+
+// From nix/src/libexpr/attr-path.cc
+Strings parseAttrPath(const string &s) {
+ Strings res;
+ string cur;
+ string::const_iterator i = s.begin();
+ while (i != s.end()) {
+ if (*i == '.') {
+ res.push_back(cur);
+ cur.clear();
+ } else if (*i == '"') {
+ ++i;
+ while (1) {
+ if (i == s.end())
+ throw Error(format("missing closing quote in selection path '%1%'") %
+ s);
+ if (*i == '"')
+ break;
+ cur.push_back(*i++);
+ }
+ } else
+ cur.push_back(*i);
+ ++i;
+ }
+ if (!cur.empty())
+ res.push_back(cur);
+ return res;
+}
+
+// From nix/src/nix/repl.cc
+bool isVarName(const string &s) {
+ if (s.size() == 0)
+ return false;
+ char c = s[0];
+ if ((c >= '0' && c <= '9') || c == '-' || c == '\'')
+ return false;
+ for (auto &i : s)
+ if (!((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') ||
+ (i >= '0' && i <= '9') || i == '_' || i == '-' || i == '\''))
+ return false;
+ return true;
+}
+
+// From nix/src/nix/repl.cc
+std::ostream &printStringValue(std::ostream &str, const char *string) {
+ str << "\"";
+ for (const char *i = string; *i; i++)
+ if (*i == '\"' || *i == '\\')
+ str << "\\" << *i;
+ else if (*i == '\n')
+ str << "\\n";
+ else if (*i == '\r')
+ str << "\\r";
+ else if (*i == '\t')
+ str << "\\t";
+ else
+ str << *i;
+ str << "\"";
+ return str;
+}
diff --git a/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.hh b/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.hh
new file mode 100644
index 00000000000..225e8b1b87e
--- /dev/null
+++ b/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.hh
@@ -0,0 +1,9 @@
+#pragma once
+
+#include
+#include
+#include
+
+nix::Strings parseAttrPath(const std::string &s);
+bool isVarName(const std::string &s);
+std::ostream &printStringValue(std::ostream &str, const char *string);
diff --git a/nixos/modules/installer/tools/nixos-option/nixos-option.cc b/nixos/modules/installer/tools/nixos-option/nixos-option.cc
new file mode 100644
index 00000000000..c778596d615
--- /dev/null
+++ b/nixos/modules/installer/tools/nixos-option/nixos-option.cc
@@ -0,0 +1,585 @@
+#include // for nix/globals.hh's reference to SYSTEM
+
+#include // for sort
+#include // for function
+#include // for operator<<, basic_ostream, ostrin...
+#include // for next
+#include // for _List_iterator
+#include // for allocator, unique_ptr, make_unique
+#include // for argvToStrings, UsageError
+#include // for findAlongAttrPath
+#include // for Attr, Bindings, Bindings::iterator
+#include // for MixEvalArgs
+#include // for EvalState::forceValue
+#include // for EvalState, initGC, operator<<
+#include // for initPlugins, Settings, settings
+#include // for Pos
+#include // for getArg, LegacyArgs, printVersion
+#include // for openStore
+#include // for Symbol, SymbolTable
+#include // for Error, Path, Strings, PathSet
+#include // for absPath, baseNameOf
+#include // for Value, Value::(anonymous), Value:...
+#include // for string, operator+, operator==
+#include // for move
+#include // for get, holds_alternative, variant
+#include // for vector<>::iterator, vector
+
+#include "libnix-copy-paste.hh"
+
+using nix::absPath;
+using nix::Bindings;
+using nix::Error;
+using nix::EvalState;
+using nix::Path;
+using nix::PathSet;
+using nix::Strings;
+using nix::Symbol;
+using nix::tAttrs;
+using nix::tLambda;
+using nix::tString;
+using nix::UsageError;
+using nix::Value;
+
+// An ostream wrapper to handle nested indentation
+class Out {
+public:
+ class Separator {};
+ const static Separator sep;
+ enum LinePolicy { ONE_LINE, MULTI_LINE };
+ explicit Out(std::ostream &ostream)
+ : ostream(ostream), policy(ONE_LINE), write_since_sep(true) {}
+ Out(Out &o, std::string const &start, std::string const &end,
+ LinePolicy policy);
+ Out(Out &o, std::string const &start, std::string const &end, int count)
+ : Out(o, start, end, count < 2 ? ONE_LINE : MULTI_LINE) {}
+ Out(Out const &) = delete;
+ Out(Out &&) = default;
+ Out &operator=(Out const &) = delete;
+ Out &operator=(Out &&) = delete;
+ ~Out() { ostream << end; }
+
+private:
+ std::ostream &ostream;
+ std::string indentation;
+ std::string end;
+ LinePolicy policy;
+ bool write_since_sep;
+ template friend Out &operator<<(Out &o, T thing);
+};
+
+template Out &operator<<(Out &o, T thing) {
+ if (!o.write_since_sep && o.policy == Out::MULTI_LINE) {
+ o.ostream << o.indentation;
+ }
+ o.write_since_sep = true;
+ o.ostream << thing;
+ return o;
+}
+
+template <>
+Out &operator<<(Out &o, Out::Separator /* thing */) {
+ o.ostream << (o.policy == Out::ONE_LINE ? " " : "\n");
+ o.write_since_sep = false;
+ return o;
+}
+
+Out::Out(Out &o, std::string const &start, std::string const &end,
+ LinePolicy policy)
+ : ostream(o.ostream),
+ indentation(policy == ONE_LINE ? o.indentation : o.indentation + " "),
+ end(policy == ONE_LINE ? end : o.indentation + end), policy(policy),
+ write_since_sep(true) {
+ o << start;
+ *this << Out::sep;
+}
+
+// Stuff needed for evaluation
+struct Context {
+ Context(EvalState *state, Bindings *autoArgs, Value options_root,
+ Value config_root)
+ : state(state), autoArgs(autoArgs), options_root(options_root),
+ config_root(config_root),
+ underscore_type(state->symbols.create("_type")) {}
+ EvalState *state;
+ Bindings *autoArgs;
+ Value options_root;
+ Value config_root;
+ Symbol underscore_type;
+};
+
+Value evaluateValue(Context *ctx, Value *v) {
+ ctx->state->forceValue(*v);
+ if (ctx->autoArgs->empty()) {
+ return *v;
+ }
+ Value called{};
+ ctx->state->autoCallFunction(*ctx->autoArgs, *v, called);
+ return called;
+}
+
+bool isOption(Context *ctx, Value const &v) {
+ if (v.type != tAttrs) {
+ return false;
+ }
+ auto const &actual_type = v.attrs->find(ctx->underscore_type);
+ if (actual_type == v.attrs->end()) {
+ return false;
+ }
+ try {
+ Value evaluated_type = evaluateValue(ctx, actual_type->value);
+ if (evaluated_type.type != tString) {
+ return false;
+ }
+ return evaluated_type.string.s == static_cast("option");
+ } catch (Error &) {
+ return false;
+ }
+}
+
+// Add quotes to a component of a path.
+// These are needed for paths like:
+// fileSystems."/".fsType
+// systemd.units."dbus.service".text
+std::string quoteAttribute(std::string const &attribute) {
+ if (isVarName(attribute)) {
+ return attribute;
+ }
+ std::ostringstream buf;
+ printStringValue(buf, attribute.c_str());
+ return buf.str();
+}
+
+std::string const appendPath(std::string const &prefix,
+ std::string const &suffix) {
+ if (prefix.empty()) {
+ return quoteAttribute(suffix);
+ }
+ return prefix + "." + quoteAttribute(suffix);
+}
+
+bool forbiddenRecursionName(std::string name) {
+ return (!name.empty() && name[0] == '_') || name == "haskellPackages";
+}
+
+void recurse(const std::function)> &f,
+ Context *ctx, Value v, std::string const &path) {
+ std::variant evaluated;
+ try {
+ evaluated = evaluateValue(ctx, &v);
+ } catch (Error &e) {
+ evaluated = e;
+ }
+ if (!f(path, evaluated)) {
+ return;
+ }
+ if (std::holds_alternative(evaluated)) {
+ return;
+ }
+ Value const &evaluated_value = std::get(evaluated);
+ if (evaluated_value.type != tAttrs) {
+ return;
+ }
+ for (auto const &child : evaluated_value.attrs->lexicographicOrder()) {
+ if (forbiddenRecursionName(child->name)) {
+ continue;
+ }
+ recurse(f, ctx, *child->value, appendPath(path, child->name));
+ }
+}
+
+// Calls f on all the option names
+void mapOptions(const std::function &f,
+ Context *ctx, Value root) {
+ recurse(
+ [f, ctx](std::string const &path, std::variant v) {
+ bool isOpt = std::holds_alternative(v) ||
+ isOption(ctx, std::get(v));
+ if (isOpt) {
+ f(path);
+ }
+ return !isOpt;
+ },
+ ctx, root, "");
+}
+
+// Calls f on all the config values inside one option.
+// Simple options have one config value inside, like sound.enable = true.
+// Compound options have multiple config values. For example, the option
+// "users.users" has about 1000 config values inside it:
+// users.users.avahi.createHome = false;
+// users.users.avahi.cryptHomeLuks = null;
+// users.users.avahi.description = "`avahi-daemon' privilege separation user";
+// ...
+// users.users.avahi.openssh.authorizedKeys.keyFiles = [ ];
+// users.users.avahi.openssh.authorizedKeys.keys = [ ];
+// ...
+// users.users.avahi.uid = 10;
+// users.users.avahi.useDefaultShell = false;
+// users.users.cups.createHome = false;
+// ...
+// users.users.cups.useDefaultShell = false;
+// users.users.gdm = ... ... ...
+// users.users.messagebus = ... .. ...
+// users.users.nixbld1 = ... .. ...
+// ...
+// users.users.systemd-timesync = ... .. ...
+void mapConfigValuesInOption(
+ const std::function v)> &f,
+ std::string const &path, Context *ctx) {
+ Value *option;
+ try {
+ option =
+ findAlongAttrPath(*ctx->state, path, *ctx->autoArgs, ctx->config_root);
+ } catch (Error &e) {
+ f(path, e);
+ return;
+ }
+ recurse(
+ [f, ctx](std::string const &path, std::variant v) {
+ bool leaf = std::holds_alternative(v) ||
+ std::get(v).type != tAttrs ||
+ ctx->state->isDerivation(std::get(v));
+ if (!leaf) {
+ return true; // Keep digging
+ }
+ f(path, v);
+ return false;
+ },
+ ctx, *option, path);
+}
+
+std::string describeError(Error const &e) { return "«error: " + e.msg() + "»"; }
+
+void describeDerivation(Context *ctx, Out &out, Value v) {
+ // Copy-pasted from nix/src/nix/repl.cc :(
+ Bindings::iterator i = v.attrs->find(ctx->state->sDrvPath);
+ PathSet pathset;
+ try {
+ Path drvPath = i != v.attrs->end()
+ ? ctx->state->coerceToPath(*i->pos, *i->value, pathset)
+ : "???";
+ out << "«derivation " << drvPath << "»";
+ } catch (Error &e) {
+ out << describeError(e);
+ }
+}
+
+Value parseAndEval(EvalState *state, std::string const &expression,
+ std::string const &path) {
+ Value v{};
+ state->eval(state->parseExprFromString(expression, absPath(path)), v);
+ return v;
+}
+
+void printValue(Context *ctx, Out &out, std::variant maybe_value,
+ std::string const &path);
+
+void printUnsortedList(Context *ctx, Out &out, Value &v) {
+ Out list_out(out, "[", "]", v.listSize());
+ for (unsigned int n = 0; n < v.listSize(); ++n) {
+ printValue(ctx, list_out, *v.listElems()[n], "");
+ list_out << Out::sep;
+ }
+}
+
+void printSortedList(Context *ctx, Out &out, Value &v) {
+ std::vector results;
+ for (unsigned int n = 0; n < v.listSize(); ++n) {
+ std::ostringstream buf;
+ Out buf_out(buf);
+ printValue(ctx, buf_out, *v.listElems()[n], "");
+ results.push_back(buf.str());
+ }
+ std::sort(results.begin(), results.end());
+ Out list_out(out, "[", "]", v.listSize());
+ for (auto const &v : results) {
+ list_out << v << Out::sep;
+ }
+}
+
+bool shouldSort(Context *ctx, Value &v) {
+ // Some lists should clearly be printed in sorted order, like
+ // environment.systemPackages. Some clearly should not, like
+ // services.xserver.multitouch.buttonsMap. As a conservative heuristic, sort
+ // lists of derivations.
+ return v.listSize() > 0 && ctx->state->isDerivation(*v.listElems()[0]);
+}
+
+void printList(Context *ctx, Out &out, Value &v) {
+ if (shouldSort(ctx, v)) {
+ printSortedList(ctx, out, v);
+ } else {
+ printUnsortedList(ctx, out, v);
+ }
+}
+
+void printAttrs(Context *ctx, Out &out, Value &v, std::string const &path) {
+ Out attrs_out(out, "{", "}", v.attrs->size());
+ for (const auto &a : v.attrs->lexicographicOrder()) {
+ std::string name = a->name;
+ attrs_out << name << " = ";
+ printValue(ctx, attrs_out, *a->value, appendPath(path, name));
+ attrs_out << ";" << Out::sep;
+ }
+}
+
+void multiLineStringEscape(Out &out, std::string const &s) {
+ int i;
+ for (i = 1; i < s.size(); i++) {
+ if (s[i - 1] == '$' && s[i] == '{') {
+ out << "''${";
+ i++;
+ } else if (s[i - 1] == '\'' && s[i] == '\'') {
+ out << "'''";
+ i++;
+ } else {
+ out << s[i - 1];
+ }
+ }
+ if (i == s.size()) {
+ out << s[i - 1];
+ }
+}
+
+void printMultiLineString(Out &out, Value const &v) {
+ std::string s = v.string.s;
+ Out str_out(out, "''", "''", Out::MULTI_LINE);
+ std::string::size_type begin = 0;
+ while (begin < s.size()) {
+ std::string::size_type end = s.find('\n', begin);
+ if (end == std::string::npos) {
+ multiLineStringEscape(str_out, s.substr(begin, s.size() - begin));
+ break;
+ }
+ multiLineStringEscape(str_out, s.substr(begin, end - begin));
+ str_out << Out::sep;
+ begin = end + 1;
+ }
+}
+
+void printValue(Context *ctx, Out &out, std::variant maybe_value,
+ std::string const &path) {
+ try {
+ if (std::holds_alternative(maybe_value)) {
+ throw Error{std::get(maybe_value)};
+ }
+ Value v = evaluateValue(ctx, &std::get(maybe_value));
+ if (ctx->state->isDerivation(v)) {
+ describeDerivation(ctx, out, v);
+ } else if (v.isList()) {
+ printList(ctx, out, v);
+ } else if (v.type == tAttrs) {
+ printAttrs(ctx, out, v, path);
+ } else if (v.type == tString &&
+ std::string(v.string.s).find('\n') != std::string::npos) {
+ printMultiLineString(out, v);
+ } else {
+ ctx->state->forceValueDeep(v);
+ out << v;
+ }
+ } catch (Error &e) {
+ if (e.msg() == "The option `" + path + "' is used but not defined.") {
+ // 93% of errors are this, and just letting this message through would be
+ // misleading. These values may or may not actually be "used" in the
+ // config. The thing throwing the error message assumes that if anything
+ // ever looks at this value, it is a "use" of this value. But here in
+ // nixos-options-summary, we are looking at this value only to print it.
+ // In order to avoid implying that this undefined value is actually
+ // referenced, eat the underlying error message and emit "«not defined»".
+ out << "«not defined»";
+ } else {
+ out << describeError(e);
+ }
+ }
+}
+
+void printConfigValue(Context *ctx, Out &out, std::string const &path,
+ std::variant v) {
+ out << path << " = ";
+ printValue(ctx, out, std::move(v), path);
+ out << ";\n";
+}
+
+void printAll(Context *ctx, Out &out) {
+ mapOptions(
+ [ctx, &out](std::string const &option_path) {
+ mapConfigValuesInOption(
+ [ctx, &out](std::string const &config_path,
+ std::variant v) {
+ printConfigValue(ctx, out, config_path, v);
+ },
+ option_path, ctx);
+ },
+ ctx, ctx->options_root);
+}
+
+void printAttr(Context *ctx, Out &out, std::string const &path, Value *root) {
+ try {
+ printValue(ctx, out,
+ *findAlongAttrPath(*ctx->state, path, *ctx->autoArgs, *root),
+ path);
+ } catch (Error &e) {
+ out << describeError(e);
+ }
+}
+
+void printOption(Context *ctx, Out &out, std::string const &path,
+ Value *option) {
+ out << "Value:\n";
+ printAttr(ctx, out, path, &ctx->config_root);
+
+ out << "\n\nDefault:\n";
+ printAttr(ctx, out, "default", option);
+
+ out << "\n\nExample:\n";
+ printAttr(ctx, out, "example", option);
+
+ out << "\n\nDescription:\n";
+ printAttr(ctx, out, "description", option);
+
+ out << "\n\nDeclared by:\n";
+ printAttr(ctx, out, "declarations", option);
+
+ out << "\n\nDefined by:\n";
+ printAttr(ctx, out, "files", option);
+ out << "\n";
+}
+
+void printListing(Out &out, Value *v) {
+ // Print this header on stderr rather than stdout because the old shell script
+ // implementation did. I don't know why.
+ std::cerr << "This attribute set contains:\n";
+ for (const auto &a : v->attrs->lexicographicOrder()) {
+ std::string name = a->name;
+ if (!name.empty() && name[0] != '_') {
+ out << name << "\n";
+ }
+ }
+}
+
+// Carefully walk an option path, looking for sub-options when a path walks past
+// an option value.
+Value findAlongOptionPath(Context *ctx, std::string const &path) {
+ Strings tokens = parseAttrPath(path);
+ Value v = ctx->options_root;
+ for (auto i = tokens.begin(); i != tokens.end(); i++) {
+ bool last_attribute = std::next(i) == tokens.end();
+ auto const &attr = *i;
+ v = evaluateValue(ctx, &v);
+ if (attr.empty()) {
+ throw Error("empty attribute name in selection path '" + path + "'");
+ }
+ if (isOption(ctx, v) && !last_attribute) {
+ Value getSubOptions = evaluateValue(
+ ctx, findAlongAttrPath(*ctx->state, "type.getSubOptions",
+ *ctx->autoArgs, v));
+ if (getSubOptions.type != tLambda) {
+ throw Error("Option's type.getSubOptions isn't a function at '" + attr +
+ "' in path '" + path + "'");
+ }
+ Value emptyString{};
+ nix::mkString(emptyString, "");
+ ctx->state->callFunction(getSubOptions, emptyString, v, nix::Pos{});
+ // Note that we've consumed attr, but didn't actually use it.
+ } else if (v.type != tAttrs) {
+ throw Error("attribute '" + attr + "' in path '" + path +
+ "' attempts to index a value that should be a set but is " +
+ showType(v));
+ } else {
+ auto const &next = v.attrs->find(ctx->state->symbols.create(attr));
+ if (next == v.attrs->end()) {
+ throw Error("attribute '" + attr + "' in path '" + path +
+ "' not found");
+ }
+ v = *next->value;
+ }
+ }
+ return v;
+}
+
+void printOne(Context *ctx, Out &out, std::string const &path) {
+ try {
+ Value option = findAlongOptionPath(ctx, path);
+ option = evaluateValue(ctx, &option);
+ if (isOption(ctx, option)) {
+ printOption(ctx, out, path, &option);
+ } else {
+ printListing(out, &option);
+ }
+ } catch (Error &e) {
+ std::cerr << "error: " << e.msg()
+ << "\nAn error occurred while looking for attribute names. Are "
+ "you sure that '"
+ << path << "' exists?\n";
+ }
+}
+
+int main(int argc, char **argv) {
+ bool all = false;
+ std::string path = ".";
+ std::string options_expr = "(import {}).options";
+ std::string config_expr = "(import {}).config";
+ std::vector args;
+
+ struct MyArgs : nix::LegacyArgs, nix::MixEvalArgs {
+ using nix::LegacyArgs::LegacyArgs;
+ };
+
+ MyArgs myArgs(nix::baseNameOf(argv[0]),
+ [&](Strings::iterator &arg, const Strings::iterator &end) {
+ if (*arg == "--help") {
+ nix::showManPage("nixos-options-summary");
+ } else if (*arg == "--version") {
+ nix::printVersion("nixos-options-summary");
+ } else if (*arg == "--all") {
+ all = true;
+ } else if (*arg == "--path") {
+ path = nix::getArg(*arg, arg, end);
+ } else if (*arg == "--options_expr") {
+ options_expr = nix::getArg(*arg, arg, end);
+ } else if (*arg == "--config_expr") {
+ config_expr = nix::getArg(*arg, arg, end);
+ } else if (!arg->empty() && arg->at(0) == '-') {
+ return false;
+ } else {
+ args.push_back(*arg);
+ }
+ return true;
+ });
+
+ myArgs.parseCmdline(nix::argvToStrings(argc, argv));
+
+ nix::initPlugins();
+ nix::initGC();
+ nix::settings.readOnlyMode = true;
+ auto store = nix::openStore();
+ auto state = std::make_unique(myArgs.searchPath, store);
+
+ Value options_root = parseAndEval(state.get(), options_expr, path);
+ Value config_root = parseAndEval(state.get(), config_expr, path);
+
+ Context ctx{state.get(), myArgs.getAutoArgs(*state), options_root,
+ config_root};
+ 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 (auto const &arg : args) {
+ printOne(&ctx, out, arg);
+ }
+ }
+
+ ctx.state->printStats();
+
+ return 0;
+}
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index 052e7fdd4fc..e4db39b5c81 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -41,10 +41,7 @@ let
inherit (config.system.nixos-generate-config) configuration;
};
- nixos-option = makeProg {
- name = "nixos-option";
- src = ./nixos-option.sh;
- };
+ nixos-option = pkgs.callPackage ./nixos-option { };
nixos-version = makeProg {
name = "nixos-version";