1 #include <nix/config.h> // for nix/globals.hh's reference to SYSTEM
3 #include <exception> // for exception_ptr, current_exception
4 #include <functional> // for function
5 #include <iostream> // for operator<<, basic_ostream, ostrin...
6 #include <iterator> // for next
7 #include <list> // for _List_iterator
8 #include <memory> // for allocator, unique_ptr, make_unique
9 #include <new> // for operator new
10 #include <nix/args.hh> // for argvToStrings, UsageError
11 #include <nix/attr-path.hh> // for findAlongAttrPath
12 #include <nix/attr-set.hh> // for Attr, Bindings, Bindings::iterator
13 #include <nix/common-eval-args.hh> // for MixEvalArgs
14 #include <nix/eval-inline.hh> // for EvalState::forceValue
15 #include <nix/eval.hh> // for EvalState, initGC, operator<<
16 #include <nix/globals.hh> // for initPlugins, Settings, settings
17 #include <nix/nixexpr.hh> // for Pos
18 #include <nix/shared.hh> // for getArg, LegacyArgs, printVersion
19 #include <nix/store-api.hh> // for openStore
20 #include <nix/symbol-table.hh> // for Symbol, SymbolTable
21 #include <nix/types.hh> // for Error, Path, Strings, PathSet
22 #include <nix/util.hh> // for absPath, baseNameOf
23 #include <nix/value.hh> // for Value, Value::(anonymous), Value:...
24 #include <string> // for string, operator+, operator==
25 #include <utility> // for move
26 #include <variant> // for get, holds_alternative, variant
27 #include <vector> // for vector<>::iterator, vector
29 #include "libnix-copy-paste.hh"
41 using nix::ThrownError
;
44 using nix::UsageError
;
47 // An ostream wrapper to handle nested indentation
53 const static Separator sep
;
59 explicit Out(std::ostream
& ostream
) : ostream(ostream
), policy(ONE_LINE
), writeSinceSep(true) {}
60 Out(Out
& o
, const std::string
& start
, const std::string
& end
, LinePolicy policy
);
61 Out(Out
& o
, const std::string
& start
, const std::string
& end
, int count
)
62 : Out(o
, start
, end
, count
< 2 ? ONE_LINE
: MULTI_LINE
)
64 Out(const Out
&) = delete;
65 Out(Out
&&) = default;
66 Out
& operator=(const Out
&) = delete;
67 Out
& operator=(Out
&&) = delete;
68 ~Out() { ostream
<< end
; }
71 std::ostream
& ostream
;
72 std::string indentation
;
76 template <typename T
> friend Out
& operator<<(Out
& o
, T thing
);
79 template <typename T
> Out
& operator<<(Out
& o
, T thing
)
81 if (!o
.writeSinceSep
&& o
.policy
== Out::MULTI_LINE
) {
82 o
.ostream
<< o
.indentation
;
84 o
.writeSinceSep
= true;
89 template <> Out
& operator<<<Out::Separator
>(Out
& o
, Out::Separator
/* thing */)
91 o
.ostream
<< (o
.policy
== Out::ONE_LINE
? " " : "\n");
92 o
.writeSinceSep
= false;
96 Out::Out(Out
& o
, const std::string
& start
, const std::string
& end
, LinePolicy policy
)
97 : ostream(o
.ostream
), indentation(policy
== ONE_LINE
? o
.indentation
: o
.indentation
+ " "),
98 end(policy
== ONE_LINE
? end
: o
.indentation
+ end
), policy(policy
), writeSinceSep(true)
104 // Stuff needed for evaluation
107 Context(EvalState
& state
, Bindings
& autoArgs
, Value optionsRoot
, Value configRoot
)
108 : state(state
), autoArgs(autoArgs
), optionsRoot(optionsRoot
), configRoot(configRoot
),
109 underscoreType(state
.symbols
.create("_type"))
115 Symbol underscoreType
;
118 Value
evaluateValue(Context
& ctx
, Value
& v
)
120 ctx
.state
.forceValue(v
);
121 if (ctx
.autoArgs
.empty()) {
125 ctx
.state
.autoCallFunction(ctx
.autoArgs
, v
, called
);
129 bool isOption(Context
& ctx
, const Value
& v
)
131 if (v
.type
!= tAttrs
) {
134 const auto & actualType
= v
.attrs
->find(ctx
.underscoreType
);
135 if (actualType
== v
.attrs
->end()) {
139 Value evaluatedType
= evaluateValue(ctx
, *actualType
->value
);
140 if (evaluatedType
.type
!= tString
) {
143 return static_cast<std::string
>(evaluatedType
.string
.s
) == "option";
149 // Add quotes to a component of a path.
150 // These are needed for paths like:
151 // fileSystems."/".fsType
152 // systemd.units."dbus.service".text
153 std::string
quoteAttribute(const std::string
& attribute
)
155 if (isVarName(attribute
)) {
158 std::ostringstream buf
;
159 printStringValue(buf
, attribute
.c_str());
163 const std::string
appendPath(const std::string
& prefix
, const std::string
& suffix
)
165 if (prefix
.empty()) {
166 return quoteAttribute(suffix
);
168 return prefix
+ "." + quoteAttribute(suffix
);
171 bool forbiddenRecursionName(std::string name
) { return (!name
.empty() && name
[0] == '_') || name
== "haskellPackages"; }
173 void recurse(const std::function
<bool(const std::string
& path
, std::variant
<Value
, std::exception_ptr
>)> & f
,
174 Context
& ctx
, Value v
, const std::string
& path
)
176 std::variant
<Value
, std::exception_ptr
> evaluated
;
178 evaluated
= evaluateValue(ctx
, v
);
180 evaluated
= std::current_exception();
182 if (!f(path
, evaluated
)) {
185 if (std::holds_alternative
<std::exception_ptr
>(evaluated
)) {
188 const Value
& evaluated_value
= std::get
<Value
>(evaluated
);
189 if (evaluated_value
.type
!= tAttrs
) {
192 for (const auto & child
: evaluated_value
.attrs
->lexicographicOrder()) {
193 if (forbiddenRecursionName(child
->name
)) {
196 recurse(f
, ctx
, *child
->value
, appendPath(path
, child
->name
));
200 bool optionTypeIs(Context
& ctx
, Value
& v
, const std::string
& soughtType
)
203 const auto & typeLookup
= v
.attrs
->find(ctx
.state
.sType
);
204 if (typeLookup
== v
.attrs
->end()) {
207 Value type
= evaluateValue(ctx
, *typeLookup
->value
);
208 if (type
.type
!= tAttrs
) {
211 const auto & nameLookup
= type
.attrs
->find(ctx
.state
.sName
);
212 if (nameLookup
== type
.attrs
->end()) {
215 Value name
= evaluateValue(ctx
, *nameLookup
->value
);
216 if (name
.type
!= tString
) {
219 return name
.string
.s
== soughtType
;
225 bool isAggregateOptionType(Context
& ctx
, Value
& v
)
227 return optionTypeIs(ctx
, v
, "attrsOf") || optionTypeIs(ctx
, v
, "listOf");
230 MakeError(OptionPathError
, EvalError
);
232 Value
getSubOptions(Context
& ctx
, Value
& option
)
234 Value getSubOptions
= evaluateValue(ctx
, *findAlongAttrPath(ctx
.state
, "type.getSubOptions", ctx
.autoArgs
, option
));
235 if (getSubOptions
.type
!= tLambda
) {
236 throw OptionPathError("Option's type.getSubOptions isn't a function");
239 nix::mkString(emptyString
, "");
241 ctx
.state
.callFunction(getSubOptions
, emptyString
, v
, nix::Pos
{});
245 // Carefully walk an option path, looking for sub-options when a path walks past
247 struct FindAlongOptionPathRet
252 FindAlongOptionPathRet
findAlongOptionPath(Context
& ctx
, const std::string
& path
)
254 Strings tokens
= parseAttrPath(path
);
255 Value v
= ctx
.optionsRoot
;
256 std::string processedPath
;
257 for (auto i
= tokens
.begin(); i
!= tokens
.end(); i
++) {
258 const auto & attr
= *i
;
260 bool lastAttribute
= std::next(i
) == tokens
.end();
261 v
= evaluateValue(ctx
, v
);
263 throw OptionPathError("empty attribute name");
265 if (isOption(ctx
, v
) && optionTypeIs(ctx
, v
, "submodule")) {
266 v
= getSubOptions(ctx
, v
);
268 if (isOption(ctx
, v
) && isAggregateOptionType(ctx
, v
)) {
269 auto subOptions
= getSubOptions(ctx
, v
);
270 if (lastAttribute
&& subOptions
.attrs
->empty()) {
274 // Note that we've consumed attr, but didn't actually use it. This is the path component that's looked
275 // up in the list or attribute set that doesn't name an option -- the "root" in "users.users.root.name".
276 } else if (v
.type
!= tAttrs
) {
277 throw OptionPathError("Value is %s while a set was expected", showType(v
));
279 const auto & next
= v
.attrs
->find(ctx
.state
.symbols
.create(attr
));
280 if (next
== v
.attrs
->end()) {
281 throw OptionPathError("Attribute not found", attr
, path
);
285 processedPath
= appendPath(processedPath
, attr
);
286 } catch (OptionPathError
& e
) {
287 throw OptionPathError("At '%s' in path '%s': %s", attr
, path
, e
.msg());
290 return {v
, processedPath
};
293 // Calls f on all the option names at or below the option described by `path`.
294 // Note that "the option described by `path`" is not trivial -- if path describes a value inside an aggregate
295 // option (such as users.users.root), the *option* described by that path is one path component shorter
296 // (eg: users.users), which results in f being called on sibling-paths (eg: users.users.nixbld1). If f
297 // doesn't want these, it must do its own filtering.
298 void mapOptions(const std::function
<void(const std::string
& path
)> & f
, Context
& ctx
, const std::string
& path
)
300 auto root
= findAlongOptionPath(ctx
, path
);
302 [f
, &ctx
](const std::string
& path
, std::variant
<Value
, std::exception_ptr
> v
) {
303 bool isOpt
= std::holds_alternative
<std::exception_ptr
>(v
) || isOption(ctx
, std::get
<Value
>(v
));
309 ctx
, root
.option
, root
.path
);
312 // Calls f on all the config values inside one option.
313 // Simple options have one config value inside, like sound.enable = true.
314 // Compound options have multiple config values. For example, the option
315 // "users.users" has about 1000 config values inside it:
316 // users.users.avahi.createHome = false;
317 // users.users.avahi.cryptHomeLuks = null;
318 // users.users.avahi.description = "`avahi-daemon' privilege separation user";
320 // users.users.avahi.openssh.authorizedKeys.keyFiles = [ ];
321 // users.users.avahi.openssh.authorizedKeys.keys = [ ];
323 // users.users.avahi.uid = 10;
324 // users.users.avahi.useDefaultShell = false;
325 // users.users.cups.createHome = false;
327 // users.users.cups.useDefaultShell = false;
328 // users.users.gdm = ... ... ...
329 // users.users.messagebus = ... .. ...
330 // users.users.nixbld1 = ... .. ...
332 // users.users.systemd-timesync = ... .. ...
333 void mapConfigValuesInOption(
334 const std::function
<void(const std::string
& path
, std::variant
<Value
, std::exception_ptr
> v
)> & f
,
335 const std::string
& path
, Context
& ctx
)
339 option
= findAlongAttrPath(ctx
.state
, path
, ctx
.autoArgs
, ctx
.configRoot
);
341 f(path
, std::current_exception());
345 [f
, ctx
](const std::string
& path
, std::variant
<Value
, std::exception_ptr
> v
) {
346 bool leaf
= std::holds_alternative
<std::exception_ptr
>(v
) || std::get
<Value
>(v
).type
!= tAttrs
||
347 ctx
.state
.isDerivation(std::get
<Value
>(v
));
349 return true; // Keep digging
357 std::string
describeError(const Error
& e
) { return "«error: " + e
.msg() + "»"; }
359 void describeDerivation(Context
& ctx
, Out
& out
, Value v
)
361 // Copy-pasted from nix/src/nix/repl.cc :(
362 Bindings::iterator i
= v
.attrs
->find(ctx
.state
.sDrvPath
);
365 Path drvPath
= i
!= v
.attrs
->end() ? ctx
.state
.coerceToPath(*i
->pos
, *i
->value
, pathset
) : "???";
366 out
<< "«derivation " << drvPath
<< "»";
367 } catch (Error
& e
) {
368 out
<< describeError(e
);
372 Value
parseAndEval(EvalState
& state
, const std::string
& expression
, const std::string
& path
)
375 state
.eval(state
.parseExprFromString(expression
, absPath(path
)), v
);
379 void printValue(Context
& ctx
, Out
& out
, std::variant
<Value
, std::exception_ptr
> maybeValue
, const std::string
& path
);
381 void printList(Context
& ctx
, Out
& out
, Value
& v
)
383 Out
listOut(out
, "[", "]", v
.listSize());
384 for (unsigned int n
= 0; n
< v
.listSize(); ++n
) {
385 printValue(ctx
, listOut
, *v
.listElems()[n
], "");
390 void printAttrs(Context
& ctx
, Out
& out
, Value
& v
, const std::string
& path
)
392 Out
attrsOut(out
, "{", "}", v
.attrs
->size());
393 for (const auto & a
: v
.attrs
->lexicographicOrder()) {
394 std::string name
= a
->name
;
395 if (!forbiddenRecursionName(name
)) {
396 attrsOut
<< name
<< " = ";
397 printValue(ctx
, attrsOut
, *a
->value
, appendPath(path
, name
));
398 attrsOut
<< ";" << Out::sep
;
403 void multiLineStringEscape(Out
& out
, const std::string
& s
)
406 for (i
= 1; i
< s
.size(); i
++) {
407 if (s
[i
- 1] == '$' && s
[i
] == '{') {
410 } else if (s
[i
- 1] == '\'' && s
[i
] == '\'') {
422 void printMultiLineString(Out
& out
, const Value
& v
)
424 std::string s
= v
.string
.s
;
425 Out
strOut(out
, "''", "''", Out::MULTI_LINE
);
426 std::string::size_type begin
= 0;
427 while (begin
< s
.size()) {
428 std::string::size_type end
= s
.find('\n', begin
);
429 if (end
== std::string::npos
) {
430 multiLineStringEscape(strOut
, s
.substr(begin
, s
.size() - begin
));
433 multiLineStringEscape(strOut
, s
.substr(begin
, end
- begin
));
439 void printValue(Context
& ctx
, Out
& out
, std::variant
<Value
, std::exception_ptr
> maybeValue
, const std::string
& path
)
442 if (auto ex
= std::get_if
<std::exception_ptr
>(&maybeValue
)) {
443 std::rethrow_exception(*ex
);
445 Value v
= evaluateValue(ctx
, std::get
<Value
>(maybeValue
));
446 if (ctx
.state
.isDerivation(v
)) {
447 describeDerivation(ctx
, out
, v
);
448 } else if (v
.isList()) {
449 printList(ctx
, out
, v
);
450 } else if (v
.type
== tAttrs
) {
451 printAttrs(ctx
, out
, v
, path
);
452 } else if (v
.type
== tString
&& std::string(v
.string
.s
).find('\n') != std::string::npos
) {
453 printMultiLineString(out
, v
);
455 ctx
.state
.forceValueDeep(v
);
458 } catch (ThrownError
& e
) {
459 if (e
.msg() == "The option `" + path
+ "' is used but not defined.") {
460 // 93% of errors are this, and just letting this message through would be
461 // misleading. These values may or may not actually be "used" in the
462 // config. The thing throwing the error message assumes that if anything
463 // ever looks at this value, it is a "use" of this value. But here in
464 // nixos-option, we are looking at this value only to print it.
465 // In order to avoid implying that this undefined value is actually
466 // referenced, eat the underlying error message and emit "«not defined»".
467 out
<< "«not defined»";
469 out
<< describeError(e
);
471 } catch (Error
& e
) {
472 out
<< describeError(e
);
476 void printConfigValue(Context
& ctx
, Out
& out
, const std::string
& path
, std::variant
<Value
, std::exception_ptr
> v
)
478 out
<< path
<< " = ";
479 printValue(ctx
, out
, std::move(v
), path
);
483 // Replace with std::starts_with when C++20 is available
484 bool starts_with(const std::string
& s
, const std::string
& prefix
)
486 return s
.size() >= prefix
.size() &&
487 std::equal(s
.begin(), std::next(s
.begin(), prefix
.size()), prefix
.begin(), prefix
.end());
490 void printRecursive(Context
& ctx
, Out
& out
, const std::string
& path
)
493 [&ctx
, &out
, &path
](const std::string
& optionPath
) {
494 mapConfigValuesInOption(
495 [&ctx
, &out
, &path
](const std::string
& configPath
, std::variant
<Value
, std::exception_ptr
> v
) {
496 if (starts_with(configPath
, path
)) {
497 printConfigValue(ctx
, out
, configPath
, v
);
505 void printAttr(Context
& ctx
, Out
& out
, const std::string
& path
, Value
& root
)
508 printValue(ctx
, out
, *findAlongAttrPath(ctx
.state
, path
, ctx
.autoArgs
, root
), path
);
509 } catch (Error
& e
) {
510 out
<< describeError(e
);
514 bool hasExample(Context
& ctx
, Value
& option
)
517 findAlongAttrPath(ctx
.state
, "example", ctx
.autoArgs
, option
);
524 void printOption(Context
& ctx
, Out
& out
, const std::string
& path
, Value
& option
)
527 printAttr(ctx
, out
, path
, ctx
.configRoot
);
529 out
<< "\n\nDefault:\n";
530 printAttr(ctx
, out
, "default", option
);
532 out
<< "\n\nType:\n";
533 printAttr(ctx
, out
, "type.description", option
);
535 if (hasExample(ctx
, option
)) {
536 out
<< "\n\nExample:\n";
537 printAttr(ctx
, out
, "example", option
);
540 out
<< "\n\nDescription:\n";
541 printAttr(ctx
, out
, "description", option
);
543 out
<< "\n\nDeclared by:\n";
544 printAttr(ctx
, out
, "declarations", option
);
546 out
<< "\n\nDefined by:\n";
547 printAttr(ctx
, out
, "files", option
);
551 void printListing(Out
& out
, Value
& v
)
553 out
<< "This attribute set contains:\n";
554 for (const auto & a
: v
.attrs
->lexicographicOrder()) {
555 std::string name
= a
->name
;
556 if (!name
.empty() && name
[0] != '_') {
562 void printOne(Context
& ctx
, Out
& out
, const std::string
& path
)
565 auto result
= findAlongOptionPath(ctx
, path
);
566 Value
& option
= result
.option
;
567 option
= evaluateValue(ctx
, option
);
568 if (path
!= result
.path
) {
569 out
<< "Note: showing " << result
.path
<< " instead of " << path
<< "\n";
571 if (isOption(ctx
, option
)) {
572 printOption(ctx
, out
, result
.path
, option
);
574 printListing(out
, option
);
576 } catch (Error
& e
) {
577 std::cerr
<< "error: " << e
.msg()
578 << "\nAn error occurred while looking for attribute names. Are "
580 << path
<< "' exists?\n";
584 int main(int argc
, char ** argv
)
586 bool recursive
= false;
587 std::string path
= ".";
588 std::string optionsExpr
= "(import <nixpkgs/nixos> {}).options";
589 std::string configExpr
= "(import <nixpkgs/nixos> {}).config";
590 std::vector
<std::string
> args
;
592 struct MyArgs
: nix::LegacyArgs
, nix::MixEvalArgs
594 using nix::LegacyArgs::LegacyArgs
;
597 MyArgs
myArgs(nix::baseNameOf(argv
[0]), [&](Strings::iterator
& arg
, const Strings::iterator
& end
) {
598 if (*arg
== "--help") {
599 nix::showManPage("nixos-option");
600 } else if (*arg
== "--version") {
601 nix::printVersion("nixos-option");
602 } else if (*arg
== "-r" || *arg
== "--recursive") {
604 } else if (*arg
== "--path") {
605 path
= nix::getArg(*arg
, arg
, end
);
606 } else if (*arg
== "--options_expr") {
607 optionsExpr
= nix::getArg(*arg
, arg
, end
);
608 } else if (*arg
== "--config_expr") {
609 configExpr
= nix::getArg(*arg
, arg
, end
);
610 } else if (!arg
->empty() && arg
->at(0) == '-') {
613 args
.push_back(*arg
);
618 myArgs
.parseCmdline(nix::argvToStrings(argc
, argv
));
622 nix::settings
.readOnlyMode
= true;
623 auto store
= nix::openStore();
624 auto state
= std::make_unique
<EvalState
>(myArgs
.searchPath
, store
);
626 Value optionsRoot
= parseAndEval(*state
, optionsExpr
, path
);
627 Value configRoot
= parseAndEval(*state
, configExpr
, path
);
629 Context ctx
{*state
, *myArgs
.getAutoArgs(*state
), optionsRoot
, configRoot
};
632 auto print
= recursive
? printRecursive
: printOne
;
636 for (const auto & arg
: args
) {
637 print(ctx
, out
, arg
);
640 ctx
.state
.printStats();