biome: 1.9.2 -> 1.9.3
[NixPkgs.git] / pkgs / tools / nix / nixos-option / src / nixos-option.cc
blob658b836d491a83e4f5413c7dc8772ef76be83eaf
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"
31 using nix::absPath;
32 using nix::Bindings;
33 using nix::Error;
34 using nix::EvalError;
35 using nix::EvalState;
36 using nix::Path;
37 using nix::PathSet;
38 using nix::Strings;
39 using nix::Symbol;
40 using nix::nAttrs;
41 using nix::ThrownError;
42 using nix::tLambda;
43 using nix::nString;
44 using nix::UsageError;
45 using nix::Value;
47 struct Context
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"))
53 EvalState & state;
54 Bindings & autoArgs;
55 Value optionsRoot;
56 Value configRoot;
57 Symbol underscoreType;
60 // An ostream wrapper to handle nested indentation
61 class Out
63 public:
64 class Separator
65 {};
66 const static Separator sep;
67 enum LinePolicy
69 ONE_LINE,
70 MULTI_LINE
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; }
83 private:
84 std::ostream & ostream;
85 std::string indentation;
86 std::string end;
87 LinePolicy policy;
88 bool writeSinceSep;
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;
100 o.ostream << thing;
101 return o;
104 template <> Out & operator<<<Out::Separator>(Out & o, Out::Separator /* thing */)
106 o.ostream << (o.policy == Out::ONE_LINE ? " " : "\n");
107 o.writeSinceSep = false;
108 return o;
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)
115 o << start;
116 *this << Out::sep;
120 Value evaluateValue(Context & ctx, Value & v)
122 ctx.state.forceValue(v, [&]() { return v.determinePos(nix::noPos); });
123 if (ctx.autoArgs.empty()) {
124 return v;
126 Value called{};
127 ctx.state.autoCallFunction(ctx.autoArgs, v, called);
128 return called;
131 bool isOption(Context & ctx, const Value & v)
133 if (v.type() != nAttrs) {
134 return false;
136 const auto & actualType = v.attrs->find(ctx.underscoreType);
137 if (actualType == v.attrs->end()) {
138 return false;
140 try {
141 Value evaluatedType = evaluateValue(ctx, *actualType->value);
142 if (evaluatedType.type() != nString) {
143 return false;
145 return static_cast<std::string>(evaluatedType.string.s) == "option";
146 } catch (Error &) {
147 return false;
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)) {
158 return attribute;
160 std::ostringstream buf;
161 printStringValue(buf, attribute.c_str());
162 return buf.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;
186 try {
187 evaluated = evaluateValue(ctx, v);
188 } catch (Error &) {
189 evaluated = std::current_exception();
191 if (!f(path, evaluated)) {
192 return;
194 if (std::holds_alternative<std::exception_ptr>(evaluated)) {
195 return;
197 const Value & evaluated_value = std::get<Value>(evaluated);
198 if (evaluated_value.type() != nAttrs) {
199 return;
201 for (const auto & child : evaluated_value.attrs->lexicographicOrder(ctx.state.symbols)) {
202 if (forbiddenRecursionName(child->name, ctx.state.symbols)) {
203 continue;
205 recurse(f, ctx, *child->value, appendPath(path, ctx.state.symbols[child->name]));
209 bool optionTypeIs(Context & ctx, Value & v, const std::string & soughtType)
211 try {
212 const auto & typeLookup = v.attrs->find(ctx.state.sType);
213 if (typeLookup == v.attrs->end()) {
214 return false;
216 Value type = evaluateValue(ctx, *typeLookup->value);
217 if (type.type() != nAttrs) {
218 return false;
220 const auto & nameLookup = type.attrs->find(ctx.state.sName);
221 if (nameLookup == type.attrs->end()) {
222 return false;
224 Value name = evaluateValue(ctx, *nameLookup->value);
225 if (name.type() != nString) {
226 return false;
228 return name.string.s == soughtType;
229 } catch (Error &) {
230 return false;
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");
247 Value emptyString{};
248 emptyString.mkString("");
249 Value v;
250 ctx.state.callFunction(getSubOptions, emptyString, v, nix::PosIdx{});
251 return v;
254 // Carefully walk an option path, looking for sub-options when a path walks past
255 // an option value.
256 struct FindAlongOptionPathRet
258 Value option;
259 std::string path;
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;
268 try {
269 bool lastAttribute = std::next(i) == tokens.end();
270 v = evaluateValue(ctx, v);
271 if (attr.empty()) {
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()) {
280 break;
282 v = subOptions;
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));
287 } else {
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);
292 v = *next->value;
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);
310 recurse(
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));
313 if (isOpt) {
314 f(path);
316 return !isOpt;
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";
328 // ...
329 // users.users.avahi.openssh.authorizedKeys.keyFiles = [ ];
330 // users.users.avahi.openssh.authorizedKeys.keys = [ ];
331 // ...
332 // users.users.avahi.uid = 10;
333 // users.users.avahi.useDefaultShell = false;
334 // users.users.cups.createHome = false;
335 // ...
336 // users.users.cups.useDefaultShell = false;
337 // users.users.gdm = ... ... ...
338 // users.users.messagebus = ... .. ...
339 // users.users.nixbld1 = ... .. ...
340 // ...
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)
346 Value * option;
347 try {
348 option = findAlongAttrPath(ctx.state, path, ctx.autoArgs, ctx.configRoot).first;
349 } catch (Error &) {
350 f(path, std::current_exception());
351 return;
353 recurse(
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));
357 if (!leaf) {
358 return true; // Keep digging
360 f(path, v);
361 return false;
363 ctx, *option, path);
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"));
376 else
377 out << "???";
378 out << "»";
381 Value parseAndEval(EvalState & state, const std::string & expression, const std::string & path)
383 Value v{};
384 state.eval(state.parseExprFromString(expression, nix::SourcePath(nix::CanonPath::fromCwd(path))), v);
385 return 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], "");
395 listOut << Out::sep;
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)
414 int i;
415 for (i = 1; i < s.size(); i++) {
416 if (s[i - 1] == '$' && s[i] == '{') {
417 out << "''${";
418 i++;
419 } else if (s[i - 1] == '\'' && s[i] == '\'') {
420 out << "'''";
421 i++;
422 } else {
423 out << s[i - 1];
426 if (i == s.size()) {
427 out << s[i - 1];
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));
440 break;
442 multiLineStringEscape(strOut, s.substr(begin, end - begin));
443 strOut << Out::sep;
444 begin = end + 1;
448 void printValue(Context & ctx, Out & out, std::variant<Value, std::exception_ptr> maybeValue, const std::string & path)
450 try {
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);
463 } else {
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»";
477 } else {
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);
489 out << ";\n";
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)
501 mapOptions(
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);
509 optionPath, ctx);
511 ctx, path);
514 void printAttr(Context & ctx, Out & out, const std::string & path, Value & root)
516 try {
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)
525 try {
526 findAlongAttrPath(ctx.state, "example", ctx.autoArgs, option);
527 return true;
528 } catch (Error &) {
529 return false;
533 void printOption(Context & ctx, Out & out, const std::string & path, Value & option)
535 out << "Value:\n";
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);
557 out << "\n";
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] != '_') {
566 out << name << "\n";
571 void printOne(Context & ctx, Out & out, const std::string & path)
573 try {
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);
582 } else {
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 "
588 "you sure that '"
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") {
612 recursive = true;
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) == '-') {
620 return false;
621 } else {
622 args.push_back(*arg);
624 return true;
627 myArgs.parseCmdline(nix::argvToStrings(argc, argv));
629 nix::initNix();
630 nix::initGC();
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};
639 Out out(std::cout);
641 auto print = recursive ? printRecursive : printOne;
642 if (args.empty()) {
643 print(ctx, out, "");
645 for (const auto & arg : args) {
646 print(ctx, out, arg);
649 ctx.state.printStats();
651 return 0;