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
;
49 Context(EvalState
& state
, Bindings
& autoArgs
, Value optionsRoot
, Value configRoot
)
50 : state(state
), autoArgs(autoArgs
), optionsRoot(optionsRoot
), configRoot(configRoot
),
51 underscoreType(state
.symbols
.create("_type"))
57 Symbol underscoreType
;
60 // An ostream wrapper to handle nested indentation
66 const static Separator sep
;
72 explicit Out(std::ostream
& ostream
) : ostream(ostream
), policy(ONE_LINE
), writeSinceSep(true) {}
73 Out(Out
& o
, const std::string
& start
, const std::string
& end
, LinePolicy policy
);
74 Out(Out
& o
, const std::string
& start
, const std::string
& end
, int count
)
75 : Out(o
, start
, end
, count
< 2 ? ONE_LINE
: MULTI_LINE
)
77 Out(const Out
&) = delete;
78 Out(Out
&&) = default;
79 Out
& operator=(const Out
&) = delete;
80 Out
& operator=(Out
&&) = delete;
81 ~Out() { ostream
<< end
; }
84 std::ostream
& ostream
;
85 std::string indentation
;
89 template <typename T
> friend Out
& operator<<(Out
& o
, T thing
);
91 friend void printValue(Context
& ctx
, Out
& out
, std::variant
<Value
, std::exception_ptr
> maybeValue
, const std::string
& path
);
94 template <typename T
> Out
& operator<<(Out
& o
, T thing
)
96 if (!o
.writeSinceSep
&& o
.policy
== Out::MULTI_LINE
) {
97 o
.ostream
<< o
.indentation
;
99 o
.writeSinceSep
= true;
104 template <> Out
& operator<<<Out::Separator
>(Out
& o
, Out::Separator
/* thing */)
106 o
.ostream
<< (o
.policy
== Out::ONE_LINE
? " " : "\n");
107 o
.writeSinceSep
= false;
111 Out::Out(Out
& o
, const std::string
& start
, const std::string
& end
, LinePolicy policy
)
112 : ostream(o
.ostream
), indentation(policy
== ONE_LINE
? o
.indentation
: o
.indentation
+ " "),
113 end(policy
== ONE_LINE
? end
: o
.indentation
+ end
), policy(policy
), writeSinceSep(true)
120 Value
evaluateValue(Context
& ctx
, Value
& v
)
122 ctx
.state
.forceValue(v
, [&]() { return v
.determinePos(nix::noPos
); });
123 if (ctx
.autoArgs
.empty()) {
127 ctx
.state
.autoCallFunction(ctx
.autoArgs
, v
, called
);
131 bool isOption(Context
& ctx
, const Value
& v
)
133 if (v
.type() != nAttrs
) {
136 const auto & actualType
= v
.attrs
->find(ctx
.underscoreType
);
137 if (actualType
== v
.attrs
->end()) {
141 Value evaluatedType
= evaluateValue(ctx
, *actualType
->value
);
142 if (evaluatedType
.type() != nString
) {
145 return static_cast<std::string
>(evaluatedType
.string
.s
) == "option";
151 // Add quotes to a component of a path.
152 // These are needed for paths like:
153 // fileSystems."/".fsType
154 // systemd.units."dbus.service".text
155 std::string
quoteAttribute(const std::string
& attribute
)
157 if (isVarName(attribute
)) {
160 std::ostringstream buf
;
161 printStringValue(buf
, attribute
.c_str());
165 const std::string
appendPath(const std::string
& prefix
, const std::string
& suffix
)
167 if (prefix
.empty()) {
168 return quoteAttribute(suffix
);
170 return prefix
+ "." + quoteAttribute(suffix
);
173 bool forbiddenRecursionName(const nix::Symbol symbol
, const nix::SymbolTable
& symbolTable
) {
174 // note: this is created from a pointer
175 // According to standard, it may never point to null, and hence attempts to check against nullptr are not allowed.
176 // However, at the time of writing, I am not certain about the full implications of the omission of a nullptr check here.
177 const std::string
& name
= symbolTable
[symbol
];
178 // TODO: figure out why haskellPackages is not recursed here
179 return (!name
.empty() && name
[0] == '_') || name
== "haskellPackages";
182 void recurse(const std::function
<bool(const std::string
& path
, std::variant
<Value
, std::exception_ptr
>)> & f
,
183 Context
& ctx
, Value v
, const std::string
& path
)
185 std::variant
<Value
, std::exception_ptr
> evaluated
;
187 evaluated
= evaluateValue(ctx
, v
);
189 evaluated
= std::current_exception();
191 if (!f(path
, evaluated
)) {
194 if (std::holds_alternative
<std::exception_ptr
>(evaluated
)) {
197 const Value
& evaluated_value
= std::get
<Value
>(evaluated
);
198 if (evaluated_value
.type() != nAttrs
) {
201 for (const auto & child
: evaluated_value
.attrs
->lexicographicOrder(ctx
.state
.symbols
)) {
202 if (forbiddenRecursionName(child
->name
, ctx
.state
.symbols
)) {
205 recurse(f
, ctx
, *child
->value
, appendPath(path
, ctx
.state
.symbols
[child
->name
]));
209 bool optionTypeIs(Context
& ctx
, Value
& v
, const std::string
& soughtType
)
212 const auto & typeLookup
= v
.attrs
->find(ctx
.state
.sType
);
213 if (typeLookup
== v
.attrs
->end()) {
216 Value type
= evaluateValue(ctx
, *typeLookup
->value
);
217 if (type
.type() != nAttrs
) {
220 const auto & nameLookup
= type
.attrs
->find(ctx
.state
.sName
);
221 if (nameLookup
== type
.attrs
->end()) {
224 Value name
= evaluateValue(ctx
, *nameLookup
->value
);
225 if (name
.type() != nString
) {
228 return name
.string
.s
== soughtType
;
234 bool isAggregateOptionType(Context
& ctx
, Value
& v
)
236 return optionTypeIs(ctx
, v
, "attrsOf") || optionTypeIs(ctx
, v
, "listOf");
239 MakeError(OptionPathError
, EvalError
);
241 Value
getSubOptions(Context
& ctx
, Value
& option
)
243 Value getSubOptions
= evaluateValue(ctx
, *findAlongAttrPath(ctx
.state
, "type.getSubOptions", ctx
.autoArgs
, option
).first
);
244 if (getSubOptions
.isLambda()) {
245 throw OptionPathError("Option's type.getSubOptions isn't a function");
248 emptyString
.mkString("");
250 ctx
.state
.callFunction(getSubOptions
, emptyString
, v
, nix::PosIdx
{});
254 // Carefully walk an option path, looking for sub-options when a path walks past
256 struct FindAlongOptionPathRet
261 FindAlongOptionPathRet
findAlongOptionPath(Context
& ctx
, const std::string
& path
)
263 Strings tokens
= parseAttrPath(path
);
264 Value v
= ctx
.optionsRoot
;
265 std::string processedPath
;
266 for (auto i
= tokens
.begin(); i
!= tokens
.end(); i
++) {
267 const auto & attr
= *i
;
269 bool lastAttribute
= std::next(i
) == tokens
.end();
270 v
= evaluateValue(ctx
, v
);
272 throw OptionPathError("empty attribute name");
274 if (isOption(ctx
, v
) && optionTypeIs(ctx
, v
, "submodule")) {
275 v
= getSubOptions(ctx
, v
);
277 if (isOption(ctx
, v
) && isAggregateOptionType(ctx
, v
)) {
278 auto subOptions
= getSubOptions(ctx
, v
);
279 if (lastAttribute
&& subOptions
.attrs
->empty()) {
283 // Note that we've consumed attr, but didn't actually use it. This is the path component that's looked
284 // up in the list or attribute set that doesn't name an option -- the "root" in "users.users.root.name".
285 } else if (v
.type() != nAttrs
) {
286 throw OptionPathError("Value is %s while a set was expected", showType(v
));
288 const auto & next
= v
.attrs
->find(ctx
.state
.symbols
.create(attr
));
289 if (next
== v
.attrs
->end()) {
290 throw OptionPathError("Attribute not found", attr
, path
);
294 processedPath
= appendPath(processedPath
, attr
);
295 } catch (OptionPathError
& e
) {
296 throw OptionPathError("At '%s' in path '%s': %s", attr
, path
, e
.msg());
299 return {v
, processedPath
};
302 // Calls f on all the option names at or below the option described by `path`.
303 // Note that "the option described by `path`" is not trivial -- if path describes a value inside an aggregate
304 // option (such as users.users.root), the *option* described by that path is one path component shorter
305 // (eg: users.users), which results in f being called on sibling-paths (eg: users.users.nixbld1). If f
306 // doesn't want these, it must do its own filtering.
307 void mapOptions(const std::function
<void(const std::string
& path
)> & f
, Context
& ctx
, const std::string
& path
)
309 auto root
= findAlongOptionPath(ctx
, path
);
311 [f
, &ctx
](const std::string
& path
, std::variant
<Value
, std::exception_ptr
> v
) {
312 bool isOpt
= std::holds_alternative
<std::exception_ptr
>(v
) || isOption(ctx
, std::get
<Value
>(v
));
318 ctx
, root
.option
, root
.path
);
321 // Calls f on all the config values inside one option.
322 // Simple options have one config value inside, like services.foo.enable = true.
323 // Compound options have multiple config values. For example, the option
324 // "users.users" has about 1000 config values inside it:
325 // users.users.avahi.createHome = false;
326 // users.users.avahi.cryptHomeLuks = null;
327 // users.users.avahi.description = "`avahi-daemon' privilege separation user";
329 // users.users.avahi.openssh.authorizedKeys.keyFiles = [ ];
330 // users.users.avahi.openssh.authorizedKeys.keys = [ ];
332 // users.users.avahi.uid = 10;
333 // users.users.avahi.useDefaultShell = false;
334 // users.users.cups.createHome = false;
336 // users.users.cups.useDefaultShell = false;
337 // users.users.gdm = ... ... ...
338 // users.users.messagebus = ... .. ...
339 // users.users.nixbld1 = ... .. ...
341 // users.users.systemd-timesync = ... .. ...
342 void mapConfigValuesInOption(
343 const std::function
<void(const std::string
& path
, std::variant
<Value
, std::exception_ptr
> v
)> & f
,
344 const std::string
& path
, Context
& ctx
)
348 option
= findAlongAttrPath(ctx
.state
, path
, ctx
.autoArgs
, ctx
.configRoot
).first
;
350 f(path
, std::current_exception());
354 [f
, ctx
](const std::string
& path
, std::variant
<Value
, std::exception_ptr
> v
) {
355 bool leaf
= std::holds_alternative
<std::exception_ptr
>(v
) || std::get
<Value
>(v
).type() != nAttrs
||
356 ctx
.state
.isDerivation(std::get
<Value
>(v
));
358 return true; // Keep digging
366 std::string
describeError(const Error
& e
) { return "«error: " + e
.msg() + "»"; }
368 void describeDerivation(Context
& ctx
, Out
& out
, Value v
)
370 // Copy-pasted from nix/src/nix/repl.cc :(
371 out
<< "«derivation ";
372 Bindings::iterator i
= v
.attrs
->find(ctx
.state
.sDrvPath
);
373 nix::NixStringContext strContext
;
374 if (i
!= v
.attrs
->end())
375 out
<< ctx
.state
.store
->printStorePath(ctx
.state
.coerceToStorePath(i
->pos
, *i
->value
, strContext
, "while evaluating the drvPath of a derivation"));
381 Value
parseAndEval(EvalState
& state
, const std::string
& expression
, const std::string
& path
)
384 state
.eval(state
.parseExprFromString(expression
, nix::SourcePath(nix::CanonPath::fromCwd(path
))), v
);
388 void printValue(Context
& ctx
, Out
& out
, std::variant
<Value
, std::exception_ptr
> maybeValue
, const std::string
& path
);
390 void printList(Context
& ctx
, Out
& out
, Value
& v
)
392 Out
listOut(out
, "[", "]", v
.listSize());
393 for (unsigned int n
= 0; n
< v
.listSize(); ++n
) {
394 printValue(ctx
, listOut
, *v
.listElems()[n
], "");
399 void printAttrs(Context
& ctx
, Out
& out
, Value
& v
, const std::string
& path
)
401 Out
attrsOut(out
, "{", "}", v
.attrs
->size());
402 for (const auto & a
: v
.attrs
->lexicographicOrder(ctx
.state
.symbols
)) {
403 if (!forbiddenRecursionName(a
->name
, ctx
.state
.symbols
)) {
404 const std::string name
= ctx
.state
.symbols
[a
->name
];
405 attrsOut
<< name
<< " = ";
406 printValue(ctx
, attrsOut
, *a
->value
, appendPath(path
, name
));
407 attrsOut
<< ";" << Out::sep
;
412 void multiLineStringEscape(Out
& out
, const std::string
& s
)
415 for (i
= 1; i
< s
.size(); i
++) {
416 if (s
[i
- 1] == '$' && s
[i
] == '{') {
419 } else if (s
[i
- 1] == '\'' && s
[i
] == '\'') {
431 void printMultiLineString(Out
& out
, const Value
& v
)
433 std::string s
= v
.string
.s
;
434 Out
strOut(out
, "''", "''", Out::MULTI_LINE
);
435 std::string::size_type begin
= 0;
436 while (begin
< s
.size()) {
437 std::string::size_type end
= s
.find('\n', begin
);
438 if (end
== std::string::npos
) {
439 multiLineStringEscape(strOut
, s
.substr(begin
, s
.size() - begin
));
442 multiLineStringEscape(strOut
, s
.substr(begin
, end
- begin
));
448 void printValue(Context
& ctx
, Out
& out
, std::variant
<Value
, std::exception_ptr
> maybeValue
, const std::string
& path
)
451 if (auto ex
= std::get_if
<std::exception_ptr
>(&maybeValue
)) {
452 std::rethrow_exception(*ex
);
454 Value v
= evaluateValue(ctx
, std::get
<Value
>(maybeValue
));
455 if (ctx
.state
.isDerivation(v
)) {
456 describeDerivation(ctx
, out
, v
);
457 } else if (v
.isList()) {
458 printList(ctx
, out
, v
);
459 } else if (v
.type() == nAttrs
) {
460 printAttrs(ctx
, out
, v
, path
);
461 } else if (v
.type() == nString
&& std::string(v
.string
.s
).find('\n') != std::string::npos
) {
462 printMultiLineString(out
, v
);
464 ctx
.state
.forceValueDeep(v
);
465 v
.print(ctx
.state
.symbols
, out
.ostream
);
467 } catch (ThrownError
& e
) {
468 if (e
.msg() == "The option `" + path
+ "' was accessed but has no value defined. Try setting the option.") {
469 // 93% of errors are this, and just letting this message through would be
470 // misleading. These values may or may not actually be "used" in the
471 // config. The thing throwing the error message assumes that if anything
472 // ever looks at this value, it is a "use" of this value. But here in
473 // nixos-option, we are looking at this value only to print it.
474 // In order to avoid implying that this undefined value is actually
475 // referenced, eat the underlying error message and emit "«not defined»".
476 out
<< "«not defined»";
478 out
<< describeError(e
);
480 } catch (Error
& e
) {
481 out
<< describeError(e
);
485 void printConfigValue(Context
& ctx
, Out
& out
, const std::string
& path
, std::variant
<Value
, std::exception_ptr
> v
)
487 out
<< path
<< " = ";
488 printValue(ctx
, out
, std::move(v
), path
);
492 // Replace with std::starts_with when C++20 is available
493 bool starts_with(const std::string
& s
, const std::string
& prefix
)
495 return s
.size() >= prefix
.size() &&
496 std::equal(s
.begin(), std::next(s
.begin(), prefix
.size()), prefix
.begin(), prefix
.end());
499 void printRecursive(Context
& ctx
, Out
& out
, const std::string
& path
)
502 [&ctx
, &out
, &path
](const std::string
& optionPath
) {
503 mapConfigValuesInOption(
504 [&ctx
, &out
, &path
](const std::string
& configPath
, std::variant
<Value
, std::exception_ptr
> v
) {
505 if (starts_with(configPath
, path
)) {
506 printConfigValue(ctx
, out
, configPath
, v
);
514 void printAttr(Context
& ctx
, Out
& out
, const std::string
& path
, Value
& root
)
517 printValue(ctx
, out
, *findAlongAttrPath(ctx
.state
, path
, ctx
.autoArgs
, root
).first
, path
);
518 } catch (Error
& e
) {
519 out
<< describeError(e
);
523 bool hasExample(Context
& ctx
, Value
& option
)
526 findAlongAttrPath(ctx
.state
, "example", ctx
.autoArgs
, option
);
533 void printOption(Context
& ctx
, Out
& out
, const std::string
& path
, Value
& option
)
536 printAttr(ctx
, out
, path
, ctx
.configRoot
);
538 out
<< "\n\nDefault:\n";
539 printAttr(ctx
, out
, "default", option
);
541 out
<< "\n\nType:\n";
542 printAttr(ctx
, out
, "type.description", option
);
544 if (hasExample(ctx
, option
)) {
545 out
<< "\n\nExample:\n";
546 printAttr(ctx
, out
, "example", option
);
549 out
<< "\n\nDescription:\n";
550 printAttr(ctx
, out
, "description", option
);
552 out
<< "\n\nDeclared by:\n";
553 printAttr(ctx
, out
, "declarations", option
);
555 out
<< "\n\nDefined by:\n";
556 printAttr(ctx
, out
, "files", option
);
560 void printListing(Context
& ctx
, Out
& out
, Value
& v
)
562 out
<< "This attribute set contains:\n";
563 for (const auto & a
: v
.attrs
->lexicographicOrder(ctx
.state
.symbols
)) {
564 const std::string
& name
= ctx
.state
.symbols
[a
->name
];
565 if (!name
.empty() && name
[0] != '_') {
571 void printOne(Context
& ctx
, Out
& out
, const std::string
& path
)
574 auto result
= findAlongOptionPath(ctx
, path
);
575 Value
& option
= result
.option
;
576 option
= evaluateValue(ctx
, option
);
577 if (path
!= result
.path
) {
578 out
<< "Note: showing " << result
.path
<< " instead of " << path
<< "\n";
580 if (isOption(ctx
, option
)) {
581 printOption(ctx
, out
, result
.path
, option
);
583 printListing(ctx
, out
, option
);
585 } catch (Error
& e
) {
586 std::cerr
<< "error: " << e
.msg()
587 << "\nAn error occurred while looking for attribute names. Are "
589 << path
<< "' exists?\n";
593 int main(int argc
, char ** argv
)
595 bool recursive
= false;
596 std::string path
= ".";
597 std::string optionsExpr
= "(import <nixpkgs/nixos> {}).options";
598 std::string configExpr
= "(import <nixpkgs/nixos> {}).config";
599 std::vector
<std::string
> args
;
601 struct MyArgs
: nix::LegacyArgs
, nix::MixEvalArgs
603 using nix::LegacyArgs::LegacyArgs
;
606 MyArgs
myArgs(std::string(nix::baseNameOf(argv
[0])), [&](Strings::iterator
& arg
, const Strings::iterator
& end
) {
607 if (*arg
== "--help") {
608 nix::showManPage("nixos-option");
609 } else if (*arg
== "--version") {
610 nix::printVersion("nixos-option");
611 } else if (*arg
== "-r" || *arg
== "--recursive") {
613 } else if (*arg
== "--path") {
614 path
= nix::getArg(*arg
, arg
, end
);
615 } else if (*arg
== "--options_expr") {
616 optionsExpr
= nix::getArg(*arg
, arg
, end
);
617 } else if (*arg
== "--config_expr") {
618 configExpr
= nix::getArg(*arg
, arg
, end
);
619 } else if (!arg
->empty() && arg
->at(0) == '-') {
622 args
.push_back(*arg
);
627 myArgs
.parseCmdline(nix::argvToStrings(argc
, argv
));
631 nix::settings
.readOnlyMode
= true;
632 auto store
= nix::openStore();
633 auto state
= std::make_unique
<EvalState
>(myArgs
.searchPath
, store
);
635 Value optionsRoot
= parseAndEval(*state
, optionsExpr
, path
);
636 Value configRoot
= parseAndEval(*state
, configExpr
, path
);
638 Context ctx
{*state
, *myArgs
.getAutoArgs(*state
), optionsRoot
, configRoot
};
641 auto print
= recursive
? printRecursive
: printOne
;
645 for (const auto & arg
: args
) {
646 print(ctx
, out
, arg
);
649 ctx
.state
.printStats();