at 21.11-pre 22 kB view raw
1#include <nix/config.h> // for nix/globals.hh's reference to SYSTEM 2 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 28 29#include "libnix-copy-paste.hh" 30 31using nix::absPath; 32using nix::Bindings; 33using nix::Error; 34using nix::EvalError; 35using nix::EvalState; 36using nix::Path; 37using nix::PathSet; 38using nix::Strings; 39using nix::Symbol; 40using nix::tAttrs; 41using nix::ThrownError; 42using nix::tLambda; 43using nix::tString; 44using nix::UsageError; 45using nix::Value; 46 47// An ostream wrapper to handle nested indentation 48class Out 49{ 50 public: 51 class Separator 52 {}; 53 const static Separator sep; 54 enum LinePolicy 55 { 56 ONE_LINE, 57 MULTI_LINE 58 }; 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) 63 {} 64 Out(const Out &) = delete; 65 Out(Out &&) = default; 66 Out & operator=(const Out &) = delete; 67 Out & operator=(Out &&) = delete; 68 ~Out() { ostream << end; } 69 70 private: 71 std::ostream & ostream; 72 std::string indentation; 73 std::string end; 74 LinePolicy policy; 75 bool writeSinceSep; 76 template <typename T> friend Out & operator<<(Out & o, T thing); 77}; 78 79template <typename T> Out & operator<<(Out & o, T thing) 80{ 81 if (!o.writeSinceSep && o.policy == Out::MULTI_LINE) { 82 o.ostream << o.indentation; 83 } 84 o.writeSinceSep = true; 85 o.ostream << thing; 86 return o; 87} 88 89template <> Out & operator<<<Out::Separator>(Out & o, Out::Separator /* thing */) 90{ 91 o.ostream << (o.policy == Out::ONE_LINE ? " " : "\n"); 92 o.writeSinceSep = false; 93 return o; 94} 95 96Out::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) 99{ 100 o << start; 101 *this << Out::sep; 102} 103 104// Stuff needed for evaluation 105struct Context 106{ 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")) 110 {} 111 EvalState & state; 112 Bindings & autoArgs; 113 Value optionsRoot; 114 Value configRoot; 115 Symbol underscoreType; 116}; 117 118Value evaluateValue(Context & ctx, Value & v) 119{ 120 ctx.state.forceValue(v); 121 if (ctx.autoArgs.empty()) { 122 return v; 123 } 124 Value called{}; 125 ctx.state.autoCallFunction(ctx.autoArgs, v, called); 126 return called; 127} 128 129bool isOption(Context & ctx, const Value & v) 130{ 131 if (v.type != tAttrs) { 132 return false; 133 } 134 const auto & actualType = v.attrs->find(ctx.underscoreType); 135 if (actualType == v.attrs->end()) { 136 return false; 137 } 138 try { 139 Value evaluatedType = evaluateValue(ctx, *actualType->value); 140 if (evaluatedType.type != tString) { 141 return false; 142 } 143 return static_cast<std::string>(evaluatedType.string.s) == "option"; 144 } catch (Error &) { 145 return false; 146 } 147} 148 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 153std::string quoteAttribute(const std::string & attribute) 154{ 155 if (isVarName(attribute)) { 156 return attribute; 157 } 158 std::ostringstream buf; 159 printStringValue(buf, attribute.c_str()); 160 return buf.str(); 161} 162 163const std::string appendPath(const std::string & prefix, const std::string & suffix) 164{ 165 if (prefix.empty()) { 166 return quoteAttribute(suffix); 167 } 168 return prefix + "." + quoteAttribute(suffix); 169} 170 171bool forbiddenRecursionName(std::string name) { return (!name.empty() && name[0] == '_') || name == "haskellPackages"; } 172 173void 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) 175{ 176 std::variant<Value, std::exception_ptr> evaluated; 177 try { 178 evaluated = evaluateValue(ctx, v); 179 } catch (Error &) { 180 evaluated = std::current_exception(); 181 } 182 if (!f(path, evaluated)) { 183 return; 184 } 185 if (std::holds_alternative<std::exception_ptr>(evaluated)) { 186 return; 187 } 188 const Value & evaluated_value = std::get<Value>(evaluated); 189 if (evaluated_value.type != tAttrs) { 190 return; 191 } 192 for (const auto & child : evaluated_value.attrs->lexicographicOrder()) { 193 if (forbiddenRecursionName(child->name)) { 194 continue; 195 } 196 recurse(f, ctx, *child->value, appendPath(path, child->name)); 197 } 198} 199 200bool optionTypeIs(Context & ctx, Value & v, const std::string & soughtType) 201{ 202 try { 203 const auto & typeLookup = v.attrs->find(ctx.state.sType); 204 if (typeLookup == v.attrs->end()) { 205 return false; 206 } 207 Value type = evaluateValue(ctx, *typeLookup->value); 208 if (type.type != tAttrs) { 209 return false; 210 } 211 const auto & nameLookup = type.attrs->find(ctx.state.sName); 212 if (nameLookup == type.attrs->end()) { 213 return false; 214 } 215 Value name = evaluateValue(ctx, *nameLookup->value); 216 if (name.type != tString) { 217 return false; 218 } 219 return name.string.s == soughtType; 220 } catch (Error &) { 221 return false; 222 } 223} 224 225bool isAggregateOptionType(Context & ctx, Value & v) 226{ 227 return optionTypeIs(ctx, v, "attrsOf") || optionTypeIs(ctx, v, "listOf"); 228} 229 230MakeError(OptionPathError, EvalError); 231 232Value getSubOptions(Context & ctx, Value & option) 233{ 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"); 237 } 238 Value emptyString{}; 239 nix::mkString(emptyString, ""); 240 Value v; 241 ctx.state.callFunction(getSubOptions, emptyString, v, nix::Pos{}); 242 return v; 243} 244 245// Carefully walk an option path, looking for sub-options when a path walks past 246// an option value. 247struct FindAlongOptionPathRet 248{ 249 Value option; 250 std::string path; 251}; 252FindAlongOptionPathRet findAlongOptionPath(Context & ctx, const std::string & path) 253{ 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; 259 try { 260 bool lastAttribute = std::next(i) == tokens.end(); 261 v = evaluateValue(ctx, v); 262 if (attr.empty()) { 263 throw OptionPathError("empty attribute name"); 264 } 265 if (isOption(ctx, v) && optionTypeIs(ctx, v, "submodule")) { 266 v = getSubOptions(ctx, v); 267 } 268 if (isOption(ctx, v) && isAggregateOptionType(ctx, v)) { 269 auto subOptions = getSubOptions(ctx, v); 270 if (lastAttribute && subOptions.attrs->empty()) { 271 break; 272 } 273 v = subOptions; 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)); 278 } else { 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); 282 } 283 v = *next->value; 284 } 285 processedPath = appendPath(processedPath, attr); 286 } catch (OptionPathError & e) { 287 throw OptionPathError("At '%s' in path '%s': %s", attr, path, e.msg()); 288 } 289 } 290 return {v, processedPath}; 291} 292 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. 298void mapOptions(const std::function<void(const std::string & path)> & f, Context & ctx, const std::string & path) 299{ 300 auto root = findAlongOptionPath(ctx, path); 301 recurse( 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)); 304 if (isOpt) { 305 f(path); 306 } 307 return !isOpt; 308 }, 309 ctx, root.option, root.path); 310} 311 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"; 319// ... 320// users.users.avahi.openssh.authorizedKeys.keyFiles = [ ]; 321// users.users.avahi.openssh.authorizedKeys.keys = [ ]; 322// ... 323// users.users.avahi.uid = 10; 324// users.users.avahi.useDefaultShell = false; 325// users.users.cups.createHome = false; 326// ... 327// users.users.cups.useDefaultShell = false; 328// users.users.gdm = ... ... ... 329// users.users.messagebus = ... .. ... 330// users.users.nixbld1 = ... .. ... 331// ... 332// users.users.systemd-timesync = ... .. ... 333void 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) 336{ 337 Value * option; 338 try { 339 option = findAlongAttrPath(ctx.state, path, ctx.autoArgs, ctx.configRoot); 340 } catch (Error &) { 341 f(path, std::current_exception()); 342 return; 343 } 344 recurse( 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)); 348 if (!leaf) { 349 return true; // Keep digging 350 } 351 f(path, v); 352 return false; 353 }, 354 ctx, *option, path); 355} 356 357std::string describeError(const Error & e) { return "«error: " + e.msg() + "»"; } 358 359void describeDerivation(Context & ctx, Out & out, Value v) 360{ 361 // Copy-pasted from nix/src/nix/repl.cc :( 362 Bindings::iterator i = v.attrs->find(ctx.state.sDrvPath); 363 PathSet pathset; 364 try { 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); 369 } 370} 371 372Value parseAndEval(EvalState & state, const std::string & expression, const std::string & path) 373{ 374 Value v{}; 375 state.eval(state.parseExprFromString(expression, absPath(path)), v); 376 return v; 377} 378 379void printValue(Context & ctx, Out & out, std::variant<Value, std::exception_ptr> maybeValue, const std::string & path); 380 381void printList(Context & ctx, Out & out, Value & v) 382{ 383 Out listOut(out, "[", "]", v.listSize()); 384 for (unsigned int n = 0; n < v.listSize(); ++n) { 385 printValue(ctx, listOut, *v.listElems()[n], ""); 386 listOut << Out::sep; 387 } 388} 389 390void printAttrs(Context & ctx, Out & out, Value & v, const std::string & path) 391{ 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; 399 } 400 } 401} 402 403void multiLineStringEscape(Out & out, const std::string & s) 404{ 405 int i; 406 for (i = 1; i < s.size(); i++) { 407 if (s[i - 1] == '$' && s[i] == '{') { 408 out << "''${"; 409 i++; 410 } else if (s[i - 1] == '\'' && s[i] == '\'') { 411 out << "'''"; 412 i++; 413 } else { 414 out << s[i - 1]; 415 } 416 } 417 if (i == s.size()) { 418 out << s[i - 1]; 419 } 420} 421 422void printMultiLineString(Out & out, const Value & v) 423{ 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)); 431 break; 432 } 433 multiLineStringEscape(strOut, s.substr(begin, end - begin)); 434 strOut << Out::sep; 435 begin = end + 1; 436 } 437} 438 439void printValue(Context & ctx, Out & out, std::variant<Value, std::exception_ptr> maybeValue, const std::string & path) 440{ 441 try { 442 if (auto ex = std::get_if<std::exception_ptr>(&maybeValue)) { 443 std::rethrow_exception(*ex); 444 } 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); 454 } else { 455 ctx.state.forceValueDeep(v); 456 out << v; 457 } 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»"; 468 } else { 469 out << describeError(e); 470 } 471 } catch (Error & e) { 472 out << describeError(e); 473 } 474} 475 476void printConfigValue(Context & ctx, Out & out, const std::string & path, std::variant<Value, std::exception_ptr> v) 477{ 478 out << path << " = "; 479 printValue(ctx, out, std::move(v), path); 480 out << ";\n"; 481} 482 483// Replace with std::starts_with when C++20 is available 484bool starts_with(const std::string & s, const std::string & prefix) 485{ 486 return s.size() >= prefix.size() && 487 std::equal(s.begin(), std::next(s.begin(), prefix.size()), prefix.begin(), prefix.end()); 488} 489 490void printRecursive(Context & ctx, Out & out, const std::string & path) 491{ 492 mapOptions( 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); 498 } 499 }, 500 optionPath, ctx); 501 }, 502 ctx, path); 503} 504 505void printAttr(Context & ctx, Out & out, const std::string & path, Value & root) 506{ 507 try { 508 printValue(ctx, out, *findAlongAttrPath(ctx.state, path, ctx.autoArgs, root), path); 509 } catch (Error & e) { 510 out << describeError(e); 511 } 512} 513 514bool hasExample(Context & ctx, Value & option) 515{ 516 try { 517 findAlongAttrPath(ctx.state, "example", ctx.autoArgs, option); 518 return true; 519 } catch (Error &) { 520 return false; 521 } 522} 523 524void printOption(Context & ctx, Out & out, const std::string & path, Value & option) 525{ 526 out << "Value:\n"; 527 printAttr(ctx, out, path, ctx.configRoot); 528 529 out << "\n\nDefault:\n"; 530 printAttr(ctx, out, "default", option); 531 532 out << "\n\nType:\n"; 533 printAttr(ctx, out, "type.description", option); 534 535 if (hasExample(ctx, option)) { 536 out << "\n\nExample:\n"; 537 printAttr(ctx, out, "example", option); 538 } 539 540 out << "\n\nDescription:\n"; 541 printAttr(ctx, out, "description", option); 542 543 out << "\n\nDeclared by:\n"; 544 printAttr(ctx, out, "declarations", option); 545 546 out << "\n\nDefined by:\n"; 547 printAttr(ctx, out, "files", option); 548 out << "\n"; 549} 550 551void printListing(Out & out, Value & v) 552{ 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] != '_') { 557 out << name << "\n"; 558 } 559 } 560} 561 562void printOne(Context & ctx, Out & out, const std::string & path) 563{ 564 try { 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"; 570 } 571 if (isOption(ctx, option)) { 572 printOption(ctx, out, result.path, option); 573 } else { 574 printListing(out, option); 575 } 576 } catch (Error & e) { 577 std::cerr << "error: " << e.msg() 578 << "\nAn error occurred while looking for attribute names. Are " 579 "you sure that '" 580 << path << "' exists?\n"; 581 } 582} 583 584int main(int argc, char ** argv) 585{ 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; 591 592 struct MyArgs : nix::LegacyArgs, nix::MixEvalArgs 593 { 594 using nix::LegacyArgs::LegacyArgs; 595 }; 596 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") { 603 recursive = true; 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) == '-') { 611 return false; 612 } else { 613 args.push_back(*arg); 614 } 615 return true; 616 }); 617 618 myArgs.parseCmdline(nix::argvToStrings(argc, argv)); 619 620 nix::initPlugins(); 621 nix::initGC(); 622 nix::settings.readOnlyMode = true; 623 auto store = nix::openStore(); 624 auto state = std::make_unique<EvalState>(myArgs.searchPath, store); 625 626 Value optionsRoot = parseAndEval(*state, optionsExpr, path); 627 Value configRoot = parseAndEval(*state, configExpr, path); 628 629 Context ctx{*state, *myArgs.getAutoArgs(*state), optionsRoot, configRoot}; 630 Out out(std::cout); 631 632 auto print = recursive ? printRecursive : printOne; 633 if (args.empty()) { 634 print(ctx, out, ""); 635 } 636 for (const auto & arg : args) { 637 print(ctx, out, arg); 638 } 639 640 ctx.state.printStats(); 641 642 return 0; 643}