diff --git a/nixos/doc/manual/release-notes/rl-2411.section.md b/nixos/doc/manual/release-notes/rl-2411.section.md index d5ba9ca11fab3..c79b3db5f29d9 100644 --- a/nixos/doc/manual/release-notes/rl-2411.section.md +++ b/nixos/doc/manual/release-notes/rl-2411.section.md @@ -16,6 +16,8 @@ - `hardware.display` is a new module implementing workarounds for misbehaving monitors through setting up custom EDID files and forcing kernel/framebuffer modes. +- `nixos-option` has been rewritten to a Nix expression called by a simple bash script. This lowers our maintenance threshold, makes eval errors less verbose, adds support for flake-based configurations, descending into `attrsOf` and `listOf` submodule options, and `--show-trace`. + - NixOS now has support for *automatic boot assessment* (see [here](https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT/)) for detailed description of the feature) for `systemd-boot` users. Available as [boot.loader.systemd-boot.bootCounting](#opt-boot.loader.systemd-boot.bootCounting.enable). ## New Services {#sec-release-24.11-new-services} diff --git a/pkgs/tools/nix/nixos-option/default.nix b/pkgs/tools/nix/nixos-option/default.nix index b01ee138170f9..ceee9b8f1d788 100644 --- a/pkgs/tools/nix/nixos-option/default.nix +++ b/pkgs/tools/nix/nixos-option/default.nix @@ -1,39 +1,73 @@ -{ lib -, stdenv -, boost -, cmake -, pkg-config -, installShellFiles -, nix +{ + lib, + makeWrapper, + stdenvNoCC, + installShellFiles, + shellcheck, + nix, + jq, + man-db, + coreutils, }: -stdenv.mkDerivation { +stdenvNoCC.mkDerivation { name = "nixos-option"; - src = ./src; + src = ./nixos-option.sh; - postInstall = '' - installManPage ${./nixos-option.8} - ''; - - strictDeps = true; nativeBuildInputs = [ - cmake - pkg-config installShellFiles + makeWrapper + shellcheck ]; - buildInputs = [ - boost - nix - ]; - cmakeFlags = [ - "-DNIX_DEV_INCLUDEPATH=${nix.dev}/include/nix" - ]; - meta = with lib; { - license = licenses.lgpl2Plus; + env = { + nixosOptionNix = toString ./nixos-option.nix; + nixosOptionManpage = "${placeholder "out"}/share/man"; + }; + + dontUnpack = true; + dontConfigure = true; + dontPatch = true; + dontBuild = true; + + installPhase = '' + runHook preInstall + + install -Dm555 $src $out/bin/nixos-option + substituteAllInPlace $out/bin/nixos-option + installManPage ${./nixos-option.8} + + runHook postInstall + ''; + + doInstallCheck = true; + installCheckPhase = '' + runHook preInstallCheck + shellcheck $out/bin/nixos-option + runHook postInstallCheck + ''; + + postFixup = '' + wrapProgram $out/bin/nixos-option \ + --prefix PATH : ${ + lib.makeBinPath [ + nix + jq + man-db + coreutils + ] + } + ''; + + meta = { + description = "Evaluate NixOS configuration and return the properties of given option"; + license = lib.licenses.mit; mainProgram = "nixos-option"; - maintainers = [ ]; - inherit (nix.meta) platforms; + maintainers = with lib.maintainers; [ + FireyFly + azuwis + aleksana + ]; }; } diff --git a/pkgs/tools/nix/nixos-option/nixos-option.8 b/pkgs/tools/nix/nixos-option/nixos-option.8 index 28438b03580b1..58d51c6a4c75b 100644 --- a/pkgs/tools/nix/nixos-option/nixos-option.8 +++ b/pkgs/tools/nix/nixos-option/nixos-option.8 @@ -11,18 +11,35 @@ .Nm .Op Fl r | -recursive .Op Fl I Ar path +.Op Fl F | -flake Ar flake-uri +.br +.Op Fl -no-flake +.Op Fl -show-trace .Ar option.name . . . .Sh DESCRIPTION This command evaluates the configuration specified in -.Pa /etc/nixos/configuration.nix -and returns the properties of the option name given as argument. +.Ev NIXOS_CONFIG Ns +, +.Pa nixos-config +in +.Ev NIX_PATH +(which by default is +.Pa /etc/nixos/configuration.nix Ns +), +.Pa /etc/nixos/flake.nix +or the file and attribute specified by the +.Fl I +or +.Fl -flake +parameter, and returns the properties of the option name given as argument. . .Pp -When the option name is not an option, the command prints the list of attributes -contained in the attribute set. +When the option name is not an option but an attribute set, the command prints +the list of attributes contained in it. When no option name is given, the +command prints all top-level attributes in given NixOS configuration. . . . @@ -32,14 +49,38 @@ contained in the attribute set. Print all the values at or below the specified path recursively. . .It Fl I Ar path -This option is passed to the underlying +Add an entry to the Nix expression search path. This option is passed to the +underlying +.Xr nix-instantiate 1 +invocation. +. +.It Fl -show-trace +Print eval trace. This option is passed to the underlying .Xr nix-instantiate 1 invocation. +. +.It Fl F , -flake Ar flake-uri +Specify the flake containing NixOS configuration. It defaults to +.Pa /etc/nixos/flake.nix Ns +, if the flake exists, it must contain an output named +.Ql nixosConfigurations. Ns Va name Ns +\&. If +.Va name +is omitted, it defaults to the current host name. +. +.It Fl -no-flake +Do not imply +.Fl -flake +if +.Pa /etc/nixos/flake.nix +exists. With this option, it is possible to show options in non-flake NixOS +configurations even if the current NixOS systems uses flakes. +. .El . . . -.Sh ENVIRONMENT +.Sh Ev ENVIRONMENT .Bl -tag -width indent .It Ev NIXOS_CONFIG Path to the main NixOS configuration module. Defaults to @@ -60,19 +101,22 @@ initScript $ nixos-option boot.loader.grub.enable Value: -true + true Default: -true + true + +Type: + boolean Description: -Whether to enable the GNU GRUB boot loader. + Whether to enable the GNU GRUB boot loader. Declared by: - "/nix/var/nix/profiles/per-user/root/channels/nixos/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix" + /nix/var/nix/profiles/per-user/root/channels/nixos/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix Defined by: - "/nix/var/nix/profiles/per-user/root/channels/nixos/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix" + /nix/var/nix/profiles/per-user/root/channels/nixos/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix .Ed . . diff --git a/pkgs/tools/nix/nixos-option/nixos-option.nix b/pkgs/tools/nix/nixos-option/nixos-option.nix new file mode 100644 index 0000000000000..3d26fea6ec1ba --- /dev/null +++ b/pkgs/tools/nix/nixos-option/nixos-option.nix @@ -0,0 +1,167 @@ +{ + nixos ? import { }, + # list representing a nixos option path (e.g. ['console' 'enable']), or a + # prefix of such a path (e.g. ['console']), or a string representing the same + # (e.g. 'console.enable') + path, + # whether to recurse down the config attrset and show each set value instead + recursive ? false, +}: + +let + inherit (nixos.pkgs) lib; + + path' = if lib.isString path then (if path == "" then [ ] else readOption path) else path; + + # helper that maps `f` on subslices starting when `predStart x` and + # ending when `predEnd x` (no support for nested occurrences) + flatMapSlices = + predStart: predEnd: f: list: + let + empty = { + result = [ ]; + active = [ ]; + }; + op = + { result, active }: + x: + if predStart x && predEnd x then + { + result = result ++ active ++ f [ x ]; + active = [ ]; + } + else if predStart x then + { + result = result ++ active; + active = [ x ]; + } + else if predEnd x then + { + result = result ++ f (active ++ [ x ]); + active = [ ]; + } + else + { + inherit result; + active = active ++ [ x ]; + }; + in + (x: x.result ++ x.active) (lib.foldl op empty list); + + # tries to invert showOption, taking a written-out option name and splitting + # it into its parts + readOption = + str: + let + unescape = list: lib.replaceStrings (map (c: "\\${c}") list) list; + unescapeNixString = lib.flip lib.pipe [ + (lib.concatStringsSep ".") + (unescape [ "$" ]) + builtins.fromJSON + ]; + in + flatMapSlices (lib.hasPrefix "\"") (lib.hasSuffix "\"") (x: [ (unescapeNixString x) ]) ( + lib.splitString "." str + ); + + # like 'mapAttrsRecursiveCond' but handling errors in the attrset tree as leaf + # nodes (which means `f` is expected to handle shallow errors) + safeMapAttrsRecursiveCond = + cond: f: set: + let + recurse = + path: + lib.mapAttrs ( + name: value: + let + e = builtins.tryEval value; + path' = path ++ [ name ]; + in + if e.success && lib.isAttrs value && cond value then recurse path' value else f path' value + ); + in + recurse [ ] set; + + # traverse the option tree along `path` from `root`, returning the option or + # attrset at the given location + optionByPath = + path: root: + let + into = + opt: part: + if lib.isOption opt && opt.type.descriptionClass == "composite" then + opt.type.getSubOptions [ ] + else if lib.isOption opt then + throw "Trying to access '${part}' inside ${opt.type.name} option while traversing option path '${lib.showOption path}'" + else if lib.isAttrs opt && lib.hasAttr part opt then + opt.${part} + else + throw "Found neither an attrset nor supported option type near '${part}' while traversing option path '${lib.showOption path}'"; + in + lib.foldl into root path; + + toPretty = lib.generators.toPretty { multiline = true; }; + safeToPretty = + x: + let + e = builtins.tryEval (toPretty x); + in + if e.success then e.value else "«error»"; + + indent = str: lib.concatStringsSep "\n" (map (x: " " + x) (lib.splitString "\n" str)); + + optionAttrNames = attrs: lib.filter (x: x != "_module") (lib.attrNames attrs); + + ## full, non-recursive mode: print an option from `options` + renderAttrs = + attrs: "This attribute set contains:\n${lib.concatStringsSep "\n" (optionAttrNames attrs)}"; + + renderOption = + option: value: + let + entry = + cond: heading: value: + lib.optional cond "${heading}:\n${indent value}"; + in + lib.concatStringsSep "\n\n" ( + lib.concatLists [ + (entry true "Value" (toPretty value)) + (entry (option ? default) "Default" (toPretty option.default)) + (entry (option ? type) "Type" (option.type.description)) + (entry (option ? description) "Description" (lib.removeSuffix "\n" option.description)) + (entry (option ? example) "Example" (toPretty option.example)) + (entry (option ? declarations) "Declared by" (lib.concatStringsSep "\n" option.declarations)) + (entry (option ? files) "Defined by" (lib.concatStringsSep "\n" option.files)) + ] + ); + + renderFull = + entry: configEntry: + if lib.isOption entry then + renderOption entry configEntry + else if lib.isAttrs entry then + renderAttrs entry + else + throw "Found neither an attrset nor option at option path '${lib.showOption path'}'"; + + ## recursive mode: print paths and values from `config` + renderRecursive = + config: + let + renderShort = n: v: "${lib.showOption (path' ++ n)} = ${safeToPretty v};"; + mapAttrsRecursive' = safeMapAttrsRecursiveCond (x: !lib.isDerivation x); + in + if lib.isAttrs config then + lib.concatStringsSep "\n" (lib.collect lib.isString (mapAttrsRecursive' renderShort config)) + else + renderShort [ ] config; + +in +if !lib.hasAttrByPath path' nixos.config then + throw "Couldn't resolve config path '${lib.showOption path'}'" +else + let + optionEntry = optionByPath path' nixos.options; + configEntry = lib.attrByPath path' null nixos.config; + in + if recursive then renderRecursive configEntry else renderFull optionEntry configEntry diff --git a/pkgs/tools/nix/nixos-option/nixos-option.sh b/pkgs/tools/nix/nixos-option/nixos-option.sh new file mode 100644 index 0000000000000..9d7af1a0c97e3 --- /dev/null +++ b/pkgs/tools/nix/nixos-option/nixos-option.sh @@ -0,0 +1,102 @@ +#!/bin/bash +# shellcheck shell=bash +set -eou pipefail + +recursive=false +no_flake=false +positional_args=() +nix_args=() +flake="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --help) + exec man -M @nixosOptionManpage@ 8 nixos-option + ;; + + -r|--recursive) + recursive=true + shift + ;; + + -I) + nix_args+=("$1" "$2") + # Not breaking existing usages of it + no_flake=true + shift 2 + ;; + + -F|--flake) + flake=$2 + shift 2 + ;; + + --no-flake) + no_flake=true + shift + ;; + + --show-trace) + nix_args+=("$1") + shift + ;; + + -*) + echo >&2 "Unsupported option $1" + exit 1 + ;; + + *) + positional_args+=("$1") + shift + ;; + esac +done + +# Detection order, from high to low: +# `--flake` +# $NIXOS_CONFIG +# nixos-config= in NIX_PATH (normally /etc/nixos/configuration.nix) +# `-I` (implies `--no-flake`) +# `--no-flake` +# /etc/nixos/flake.nix (if exists) + +if [[ -z "$flake" ]] && [[ -e /etc/nixos/flake.nix ]] && [[ "$no_flake" == "false" ]]; then + flake="$(dirname "$(realpath /etc/nixos/flake.nix)")" +fi + +if [[ -n "$flake" ]]; then + if [[ $flake =~ ^(.*)\#([^\#\"]*)$ ]]; then + flake="${BASH_REMATCH[1]}" + flakeAttr="${BASH_REMATCH[2]}" + fi + # Unlike nix cli, builtins.getFlake infer path:// when a path is given + # See https://github.com/NixOS/nix/issues/5836 + if [[ -d "$flake" ]] && [[ -e "$flake/.git" ]] ; then + flake="git+file://$(realpath "$flake")" + fi + if [[ -z "${flakeAttr:-}" ]]; then + hostname=$(< /proc/sys/kernel/hostname) + if [[ -z "${hostname:-}" ]]; then + hostname=default + fi + flakeAttr="nixosConfigurations.\"$hostname\"" + else + flakeAttr="nixosConfigurations.\"$flakeAttr\"" + fi + nix_args+=(--arg nixos "(builtins.getFlake \"$flake\").$flakeAttr") +fi + +case ${#positional_args[@]} in + 0) path= ;; + # Remove trailing dot if exists, match the behavior of + # old nixos-option and make shell completions happy + 1) path="${positional_args[0]%.}" ;; + *) echo >&2 "Only one option path can be provided"; exit 1 ;; +esac + +nix-instantiate "${nix_args[@]}" --eval --json \ + --argstr path "$path" \ + --arg recursive "$recursive" \ + @nixosOptionNix@ \ +| jq -r diff --git a/pkgs/tools/nix/nixos-option/src/CMakeLists.txt b/pkgs/tools/nix/nixos-option/src/CMakeLists.txt deleted file mode 100644 index 9aee9e6ccadac..0000000000000 --- a/pkgs/tools/nix/nixos-option/src/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -cmake_minimum_required (VERSION 2.6) -project (nixos-option) - -set(NIX_DEV_INCLUDEPATH "" CACHE STRING "path to nix include directory") - -add_executable(nixos-option nixos-option.cc libnix-copy-paste.cc) -target_include_directories(nixos-option PUBLIC "${NIX_DEV_INCLUDEPATH}") -target_link_libraries(nixos-option PRIVATE -lnixmain -lnixexpr -lnixstore -lnixutil -lnixcmd) -target_compile_features(nixos-option PRIVATE cxx_std_20) - -install (TARGETS nixos-option DESTINATION bin) diff --git a/pkgs/tools/nix/nixos-option/src/libnix-copy-paste.cc b/pkgs/tools/nix/nixos-option/src/libnix-copy-paste.cc deleted file mode 100644 index ad6084844e57c..0000000000000 --- a/pkgs/tools/nix/nixos-option/src/libnix-copy-paste.cc +++ /dev/null @@ -1,80 +0,0 @@ -// 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 get_pointer -#include // for operator<<, basic_... -#include // for Strings -#include // for Error -#include // for string, basic_string - -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("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/pkgs/tools/nix/nixos-option/src/libnix-copy-paste.hh b/pkgs/tools/nix/nixos-option/src/libnix-copy-paste.hh deleted file mode 100644 index 2274e9a0f8532..0000000000000 --- a/pkgs/tools/nix/nixos-option/src/libnix-copy-paste.hh +++ /dev/null @@ -1,9 +0,0 @@ -#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/pkgs/tools/nix/nixos-option/src/nixos-option.cc b/pkgs/tools/nix/nixos-option/src/nixos-option.cc deleted file mode 100644 index eab727bc1db5a..0000000000000 --- a/pkgs/tools/nix/nixos-option/src/nixos-option.cc +++ /dev/null @@ -1,652 +0,0 @@ -#include // for nix/globals.hh's reference to SYSTEM - -#include // for exception_ptr, current_exception -#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 operator new -#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::EvalError; -using nix::EvalState; -using nix::Path; -using nix::PathSet; -using nix::Strings; -using nix::Symbol; -using nix::nAttrs; -using nix::ThrownError; -using nix::tLambda; -using nix::nString; -using nix::UsageError; -using nix::Value; - -struct Context -{ - Context(EvalState & state, Bindings & autoArgs, Value optionsRoot, Value configRoot) - : state(state), autoArgs(autoArgs), optionsRoot(optionsRoot), configRoot(configRoot), - underscoreType(state.symbols.create("_type")) - {} - EvalState & state; - Bindings & autoArgs; - Value optionsRoot; - Value configRoot; - Symbol underscoreType; -}; - -// 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), writeSinceSep(true) {} - Out(Out & o, const std::string & start, const std::string & end, LinePolicy policy); - Out(Out & o, const std::string & start, const std::string & end, int count) - : Out(o, start, end, count < 2 ? ONE_LINE : MULTI_LINE) - {} - Out(const Out &) = delete; - Out(Out &&) = default; - Out & operator=(const Out &) = delete; - Out & operator=(Out &&) = delete; - ~Out() { ostream << end; } - - private: - std::ostream & ostream; - std::string indentation; - std::string end; - LinePolicy policy; - bool writeSinceSep; - template friend Out & operator<<(Out & o, T thing); - - friend void printValue(Context & ctx, Out & out, std::variant maybeValue, const std::string & path); -}; - -template Out & operator<<(Out & o, T thing) -{ - if (!o.writeSinceSep && o.policy == Out::MULTI_LINE) { - o.ostream << o.indentation; - } - o.writeSinceSep = true; - o.ostream << thing; - return o; -} - -template <> Out & operator<<(Out & o, Out::Separator /* thing */) -{ - o.ostream << (o.policy == Out::ONE_LINE ? " " : "\n"); - o.writeSinceSep = false; - return o; -} - -Out::Out(Out & o, const std::string & start, const std::string & end, LinePolicy policy) - : ostream(o.ostream), indentation(policy == ONE_LINE ? o.indentation : o.indentation + " "), - end(policy == ONE_LINE ? end : o.indentation + end), policy(policy), writeSinceSep(true) -{ - o << start; - *this << Out::sep; -} - - -Value evaluateValue(Context & ctx, Value & v) -{ - ctx.state.forceValue(v, [&]() { return v.determinePos(nix::noPos); }); - if (ctx.autoArgs.empty()) { - return v; - } - Value called{}; - ctx.state.autoCallFunction(ctx.autoArgs, v, called); - return called; -} - -bool isOption(Context & ctx, const Value & v) -{ - if (v.type() != nAttrs) { - return false; - } - const auto & actualType = v.attrs->find(ctx.underscoreType); - if (actualType == v.attrs->end()) { - return false; - } - try { - Value evaluatedType = evaluateValue(ctx, *actualType->value); - if (evaluatedType.type() != nString) { - return false; - } - return static_cast(evaluatedType.string.s) == "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(const std::string & attribute) -{ - if (isVarName(attribute)) { - return attribute; - } - std::ostringstream buf; - printStringValue(buf, attribute.c_str()); - return buf.str(); -} - -const std::string appendPath(const std::string & prefix, const std::string & suffix) -{ - if (prefix.empty()) { - return quoteAttribute(suffix); - } - return prefix + "." + quoteAttribute(suffix); -} - -bool forbiddenRecursionName(const nix::Symbol symbol, const nix::SymbolTable & symbolTable) { - // note: this is created from a pointer - // According to standard, it may never point to null, and hence attempts to check against nullptr are not allowed. - // However, at the time of writing, I am not certain about the full implications of the omission of a nullptr check here. - const std::string & name = symbolTable[symbol]; - // TODO: figure out why haskellPackages is not recursed here - return (!name.empty() && name[0] == '_') || name == "haskellPackages"; -} - -void recurse(const std::function)> & f, - Context & ctx, Value v, const std::string & path) -{ - std::variant evaluated; - try { - evaluated = evaluateValue(ctx, v); - } catch (Error &) { - evaluated = std::current_exception(); - } - if (!f(path, evaluated)) { - return; - } - if (std::holds_alternative(evaluated)) { - return; - } - const Value & evaluated_value = std::get(evaluated); - if (evaluated_value.type() != nAttrs) { - return; - } - for (const auto & child : evaluated_value.attrs->lexicographicOrder(ctx.state.symbols)) { - if (forbiddenRecursionName(child->name, ctx.state.symbols)) { - continue; - } - recurse(f, ctx, *child->value, appendPath(path, ctx.state.symbols[child->name])); - } -} - -bool optionTypeIs(Context & ctx, Value & v, const std::string & soughtType) -{ - try { - const auto & typeLookup = v.attrs->find(ctx.state.sType); - if (typeLookup == v.attrs->end()) { - return false; - } - Value type = evaluateValue(ctx, *typeLookup->value); - if (type.type() != nAttrs) { - return false; - } - const auto & nameLookup = type.attrs->find(ctx.state.sName); - if (nameLookup == type.attrs->end()) { - return false; - } - Value name = evaluateValue(ctx, *nameLookup->value); - if (name.type() != nString) { - return false; - } - return name.string.s == soughtType; - } catch (Error &) { - return false; - } -} - -bool isAggregateOptionType(Context & ctx, Value & v) -{ - return optionTypeIs(ctx, v, "attrsOf") || optionTypeIs(ctx, v, "listOf"); -} - -MakeError(OptionPathError, EvalError); - -Value getSubOptions(Context & ctx, Value & option) -{ - Value getSubOptions = evaluateValue(ctx, *findAlongAttrPath(ctx.state, "type.getSubOptions", ctx.autoArgs, option).first); - if (getSubOptions.isLambda()) { - throw OptionPathError("Option's type.getSubOptions isn't a function"); - } - Value emptyString{}; - emptyString.mkString(""); - Value v; - ctx.state.callFunction(getSubOptions, emptyString, v, nix::PosIdx{}); - return v; -} - -// Carefully walk an option path, looking for sub-options when a path walks past -// an option value. -struct FindAlongOptionPathRet -{ - Value option; - std::string path; -}; -FindAlongOptionPathRet findAlongOptionPath(Context & ctx, const std::string & path) -{ - Strings tokens = parseAttrPath(path); - Value v = ctx.optionsRoot; - std::string processedPath; - for (auto i = tokens.begin(); i != tokens.end(); i++) { - const auto & attr = *i; - try { - bool lastAttribute = std::next(i) == tokens.end(); - v = evaluateValue(ctx, v); - if (attr.empty()) { - throw OptionPathError("empty attribute name"); - } - if (isOption(ctx, v) && optionTypeIs(ctx, v, "submodule")) { - v = getSubOptions(ctx, v); - } - if (isOption(ctx, v) && isAggregateOptionType(ctx, v)) { - auto subOptions = getSubOptions(ctx, v); - if (lastAttribute && subOptions.attrs->empty()) { - break; - } - v = subOptions; - // Note that we've consumed attr, but didn't actually use it. This is the path component that's looked - // up in the list or attribute set that doesn't name an option -- the "root" in "users.users.root.name". - } else if (v.type() != nAttrs) { - throw OptionPathError("Value is %s while a set was expected", showType(v)); - } else { - const auto & next = v.attrs->find(ctx.state.symbols.create(attr)); - if (next == v.attrs->end()) { - throw OptionPathError("Attribute not found", attr, path); - } - v = *next->value; - } - processedPath = appendPath(processedPath, attr); - } catch (OptionPathError & e) { - throw OptionPathError("At '%s' in path '%s': %s", attr, path, e.msg()); - } - } - return {v, processedPath}; -} - -// Calls f on all the option names at or below the option described by `path`. -// Note that "the option described by `path`" is not trivial -- if path describes a value inside an aggregate -// option (such as users.users.root), the *option* described by that path is one path component shorter -// (eg: users.users), which results in f being called on sibling-paths (eg: users.users.nixbld1). If f -// doesn't want these, it must do its own filtering. -void mapOptions(const std::function & f, Context & ctx, const std::string & path) -{ - auto root = findAlongOptionPath(ctx, path); - recurse( - [f, &ctx](const std::string & path, std::variant v) { - bool isOpt = std::holds_alternative(v) || isOption(ctx, std::get(v)); - if (isOpt) { - f(path); - } - return !isOpt; - }, - ctx, root.option, root.path); -} - -// Calls f on all the config values inside one option. -// Simple options have one config value inside, like services.foo.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, - const std::string & path, Context & ctx) -{ - Value * option; - try { - option = findAlongAttrPath(ctx.state, path, ctx.autoArgs, ctx.configRoot).first; - } catch (Error &) { - f(path, std::current_exception()); - return; - } - recurse( - [f, ctx](const std::string & path, std::variant v) { - bool leaf = std::holds_alternative(v) || std::get(v).type() != nAttrs || - ctx.state.isDerivation(std::get(v)); - if (!leaf) { - return true; // Keep digging - } - f(path, v); - return false; - }, - ctx, *option, path); -} - -std::string describeError(const Error & e) { return "«error: " + e.msg() + "»"; } - -void describeDerivation(Context & ctx, Out & out, Value v) -{ - // Copy-pasted from nix/src/nix/repl.cc :( - out << "«derivation "; - Bindings::iterator i = v.attrs->find(ctx.state.sDrvPath); - nix::NixStringContext strContext; - if (i != v.attrs->end()) - out << ctx.state.store->printStorePath(ctx.state.coerceToStorePath(i->pos, *i->value, strContext, "while evaluating the drvPath of a derivation")); - else - out << "???"; - out << "»"; -} - -Value parseAndEval(EvalState & state, const std::string & expression, const std::string & path) -{ - Value v{}; - state.eval(state.parseExprFromString(expression, nix::SourcePath(nix::CanonPath::fromCwd(path))), v); - return v; -} - -void printValue(Context & ctx, Out & out, std::variant maybeValue, const std::string & path); - -void printList(Context & ctx, Out & out, Value & v) -{ - Out listOut(out, "[", "]", v.listSize()); - for (unsigned int n = 0; n < v.listSize(); ++n) { - printValue(ctx, listOut, *v.listElems()[n], ""); - listOut << Out::sep; - } -} - -void printAttrs(Context & ctx, Out & out, Value & v, const std::string & path) -{ - Out attrsOut(out, "{", "}", v.attrs->size()); - for (const auto & a : v.attrs->lexicographicOrder(ctx.state.symbols)) { - if (!forbiddenRecursionName(a->name, ctx.state.symbols)) { - const std::string name = ctx.state.symbols[a->name]; - attrsOut << name << " = "; - printValue(ctx, attrsOut, *a->value, appendPath(path, name)); - attrsOut << ";" << Out::sep; - } - } -} - -void multiLineStringEscape(Out & out, const std::string & 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, const Value & v) -{ - std::string s = v.string.s; - Out strOut(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(strOut, s.substr(begin, s.size() - begin)); - break; - } - multiLineStringEscape(strOut, s.substr(begin, end - begin)); - strOut << Out::sep; - begin = end + 1; - } -} - -void printValue(Context & ctx, Out & out, std::variant maybeValue, const std::string & path) -{ - try { - if (auto ex = std::get_if(&maybeValue)) { - std::rethrow_exception(*ex); - } - Value v = evaluateValue(ctx, std::get(maybeValue)); - if (ctx.state.isDerivation(v)) { - describeDerivation(ctx, out, v); - } else if (v.isList()) { - printList(ctx, out, v); - } else if (v.type() == nAttrs) { - printAttrs(ctx, out, v, path); - } else if (v.type() == nString && std::string(v.string.s).find('\n') != std::string::npos) { - printMultiLineString(out, v); - } else { - ctx.state.forceValueDeep(v); - v.print(ctx.state.symbols, out.ostream); - } - } catch (ThrownError & 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-option, 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); - } - } catch (Error & e) { - out << describeError(e); - } -} - -void printConfigValue(Context & ctx, Out & out, const std::string & path, std::variant v) -{ - out << path << " = "; - printValue(ctx, out, std::move(v), path); - out << ";\n"; -} - -// Replace with std::starts_with when C++20 is available -bool starts_with(const std::string & s, const std::string & prefix) -{ - return s.size() >= prefix.size() && - std::equal(s.begin(), std::next(s.begin(), prefix.size()), prefix.begin(), prefix.end()); -} - -void printRecursive(Context & ctx, Out & out, const std::string & path) -{ - mapOptions( - [&ctx, &out, &path](const std::string & optionPath) { - mapConfigValuesInOption( - [&ctx, &out, &path](const std::string & configPath, std::variant v) { - if (starts_with(configPath, path)) { - printConfigValue(ctx, out, configPath, v); - } - }, - optionPath, ctx); - }, - ctx, path); -} - -void printAttr(Context & ctx, Out & out, const std::string & path, Value & root) -{ - try { - printValue(ctx, out, *findAlongAttrPath(ctx.state, path, ctx.autoArgs, root).first, path); - } catch (Error & e) { - out << describeError(e); - } -} - -bool hasExample(Context & ctx, Value & option) -{ - try { - findAlongAttrPath(ctx.state, "example", ctx.autoArgs, option); - return true; - } catch (Error &) { - return false; - } -} - -void printOption(Context & ctx, Out & out, const std::string & path, Value & option) -{ - out << "Value:\n"; - printAttr(ctx, out, path, ctx.configRoot); - - out << "\n\nDefault:\n"; - printAttr(ctx, out, "default", option); - - out << "\n\nType:\n"; - printAttr(ctx, out, "type.description", option); - - if (hasExample(ctx, 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(Context & ctx, Out & out, Value & v) -{ - out << "This attribute set contains:\n"; - for (const auto & a : v.attrs->lexicographicOrder(ctx.state.symbols)) { - const std::string & name = ctx.state.symbols[a->name]; - if (!name.empty() && name[0] != '_') { - out << name << "\n"; - } - } -} - -void printOne(Context & ctx, Out & out, const std::string & path) -{ - try { - auto result = findAlongOptionPath(ctx, path); - Value & option = result.option; - option = evaluateValue(ctx, option); - if (path != result.path) { - out << "Note: showing " << result.path << " instead of " << path << "\n"; - } - if (isOption(ctx, option)) { - printOption(ctx, out, result.path, option); - } else { - printListing(ctx, 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 recursive = false; - std::string path = "."; - std::string optionsExpr = "(import {}).options"; - std::string configExpr = "(import {}).config"; - std::vector args; - - struct MyArgs : nix::LegacyArgs, nix::MixEvalArgs - { - using nix::LegacyArgs::LegacyArgs; - }; - - MyArgs myArgs(std::string(nix::baseNameOf(argv[0])), [&](Strings::iterator & arg, const Strings::iterator & end) { - if (*arg == "--help") { - nix::showManPage("nixos-option"); - } else if (*arg == "--version") { - nix::printVersion("nixos-option"); - } else if (*arg == "-r" || *arg == "--recursive") { - recursive = true; - } else if (*arg == "--path") { - path = nix::getArg(*arg, arg, end); - } else if (*arg == "--options_expr") { - optionsExpr = nix::getArg(*arg, arg, end); - } else if (*arg == "--config_expr") { - configExpr = 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::initNix(); - nix::initGC(); - nix::settings.readOnlyMode = true; - auto store = nix::openStore(); - auto state = std::make_unique(myArgs.searchPath, store); - - Value optionsRoot = parseAndEval(*state, optionsExpr, path); - Value configRoot = parseAndEval(*state, configExpr, path); - - Context ctx{*state, *myArgs.getAutoArgs(*state), optionsRoot, configRoot}; - Out out(std::cout); - - auto print = recursive ? printRecursive : printOne; - if (args.empty()) { - print(ctx, out, ""); - } - for (const auto & arg : args) { - print(ctx, out, arg); - } - - ctx.state.printStats(); - - return 0; -} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index cc9e1a55b1ab4..b3f409131e40d 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -39037,9 +39037,7 @@ with pkgs; nix-melt = callPackage ../tools/nix/nix-melt { }; - nixos-option = callPackage ../tools/nix/nixos-option { - nix = nixVersions.nix_2_18; - }; + nixos-option = callPackage ../tools/nix/nixos-option { }; nix-pin = callPackage ../tools/package-management/nix-pin { };