Skip to content

Commit 5634f52

Browse files
authored
nixos-option: rewrite as a nix script, 2nd try (NixOS#369151)
2 parents 003867e + 8710aa0 commit 5634f52

File tree

13 files changed

+488
-845
lines changed

13 files changed

+488
-845
lines changed

nixos/doc/manual/release-notes/rl-2505.section.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
- `nixos-rebuild-ng`, a full rewrite of `nixos-rebuild` in Python, is available for testing. You can enable it by setting [system.rebuild.enableNg](options.html#opt-system.rebuild.enableNg) in your configuration (this will replace the old `nixos-rebuild`), or by adding `nixos-rebuild-ng` to your `environment.systemPackages` (in this case, it will live side-by-side with `nixos-rebuild` as `nixos-rebuild-ng`). It is expected that the next major version of NixOS (25.11) will enable `system.rebuild.enableNg` by default.
2828
- A `nixos-rebuild build-image` sub-command has been added.
2929

30+
- `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`.
31+
3032
It allows users to build platform-specific (disk) images from their NixOS configurations. `nixos-rebuild build-image` works similar to the popular [nix-community/nixos-generators](https://github.com/nix-community/nixos-generators) project. See new [section on image building in the nixpkgs manual](https://nixos.org/manual/nixpkgs/unstable/#sec-image-nixos-rebuild-build-image).
3133

3234
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
.Dd January 1, 1980
2+
.Dt nixos-option 8
3+
.Os
4+
.Sh NAME
5+
.Nm nixos-option
6+
.Nd inspect a NixOS configuration
7+
.
8+
.
9+
.
10+
.Sh SYNOPSIS
11+
.Nm
12+
.Op Fl r | -recursive
13+
.Op Fl I Ar path
14+
.Op Fl F | -flake Ar flake-uri
15+
.br
16+
.Op Fl -no-flake
17+
.Op Fl -show-trace
18+
.Ar option.name
19+
.
20+
.
21+
.
22+
.Sh DESCRIPTION
23+
This command evaluates the configuration specified in
24+
.Ev NIXOS_CONFIG Ns
25+
,
26+
.Pa nixos-config
27+
in
28+
.Ev NIX_PATH
29+
(which by default is
30+
.Pa /etc/nixos/configuration.nix Ns
31+
),
32+
.Pa /etc/nixos/flake.nix
33+
or the file and attribute specified by the
34+
.Fl I
35+
or
36+
.Fl -flake
37+
parameter, and returns the properties of the option name given as argument.
38+
.
39+
.Pp
40+
When the option name is not an option but an attribute set, the command prints
41+
the list of attributes contained in it. When no option name is given, the
42+
command prints all top-level attributes in given NixOS configuration.
43+
.
44+
.
45+
.
46+
.Sh OPTIONS
47+
.Bl -tag -width indent
48+
.It Fl r , -recursive
49+
Print all the values at or below the specified path recursively.
50+
.
51+
.It Fl I Ar path
52+
Add an entry to the Nix expression search path. This option is passed to the
53+
underlying
54+
.Xr nix-instantiate 1
55+
invocation.
56+
.
57+
.It Fl -show-trace
58+
Print eval trace. This option is passed to the underlying
59+
.Xr nix-instantiate 1
60+
invocation.
61+
.
62+
.It Fl F , -flake Ar flake-uri
63+
Specify the flake containing NixOS configuration. It defaults to
64+
.Pa /etc/nixos/flake.nix Ns
65+
, if the flake exists, it must contain an output named
66+
.Ql nixosConfigurations. Ns Va name Ns
67+
\&. If
68+
.Va name
69+
is omitted, it defaults to the current host name.
70+
.
71+
.It Fl -no-flake
72+
Do not imply
73+
.Fl -flake
74+
if
75+
.Pa /etc/nixos/flake.nix
76+
exists. With this option, it is possible to show options in non-flake NixOS
77+
configurations even if the current NixOS systems uses flakes.
78+
.
79+
.El
80+
.
81+
.
82+
.
83+
.Sh Ev ENVIRONMENT
84+
.Bl -tag -width indent
85+
.It Ev NIXOS_CONFIG
86+
Path to the main NixOS configuration module. Defaults to
87+
.Pa /etc/nixos/configuration.nix Ns
88+
\&.
89+
.El
90+
.
91+
.
92+
.
93+
.Sh EXAMPLES
94+
Investigate option values:
95+
.Bd -literal -offset indent
96+
$ nixos-option boot.loader
97+
This attribute set contains:
98+
generationsDir
99+
grub
100+
initScript
101+
102+
$ nixos-option boot.loader.grub.enable
103+
Value:
104+
true
105+
106+
Default:
107+
true
108+
109+
Type:
110+
boolean
111+
112+
Description:
113+
Whether to enable the GNU GRUB boot loader.
114+
115+
Declared by:
116+
/nix/var/nix/profiles/per-user/root/channels/nixos/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix
117+
118+
Defined by:
119+
/nix/var/nix/profiles/per-user/root/channels/nixos/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix
120+
.Ed
121+
.
122+
.
123+
.
124+
.Sh SEE ALSO
125+
.Xr configuration.nix 5
126+
.
127+
.
128+
.
129+
.Sh AUTHORS
130+
.An -nosplit
131+
.An Nicolas Pierron
132+
and
133+
.An the Nixpkgs/NixOS contributors
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
{
2+
nixos,
3+
# list representing a nixos option path (e.g. ['console' 'enable']), or a
4+
# prefix of such a path (e.g. ['console']), or a string representing the same
5+
# (e.g. 'console.enable')
6+
path,
7+
# whether to recurse down the config attrset and show each set value instead
8+
recursive,
9+
}:
10+
11+
let
12+
inherit (nixos.pkgs) lib;
13+
14+
path' = if lib.isString path then (if path == "" then [ ] else readOption path) else path;
15+
16+
# helper that maps `f` on subslices starting when `predStart x` and
17+
# ending when `predEnd x` (no support for nested occurrences)
18+
flatMapSlices =
19+
predStart: predEnd: f: list:
20+
let
21+
empty = {
22+
result = [ ];
23+
active = [ ];
24+
};
25+
op =
26+
{ result, active }:
27+
x:
28+
if predStart x && predEnd x then
29+
{
30+
result = result ++ active ++ f [ x ];
31+
active = [ ];
32+
}
33+
else if predStart x then
34+
{
35+
result = result ++ active;
36+
active = [ x ];
37+
}
38+
else if predEnd x then
39+
{
40+
result = result ++ f (active ++ [ x ]);
41+
active = [ ];
42+
}
43+
else
44+
{
45+
inherit result;
46+
active = active ++ [ x ];
47+
};
48+
in
49+
(x: x.result ++ x.active) (lib.foldl op empty list);
50+
51+
# tries to invert showOption, taking a written-out option name and splitting
52+
# it into its parts
53+
readOption =
54+
str:
55+
let
56+
unescape = list: lib.replaceStrings (map (c: "\\${c}") list) list;
57+
unescapeNixString = lib.flip lib.pipe [
58+
(lib.concatStringsSep ".")
59+
(unescape [ "$" ])
60+
builtins.fromJSON
61+
];
62+
in
63+
flatMapSlices (lib.hasPrefix "\"") (lib.hasSuffix "\"") (x: [ (unescapeNixString x) ]) (
64+
lib.splitString "." str
65+
);
66+
67+
# like 'mapAttrsRecursiveCond' but handling errors in the attrset tree as leaf
68+
# nodes (which means `f` is expected to handle shallow errors)
69+
safeMapAttrsRecursiveCond =
70+
cond: f: set:
71+
let
72+
recurse =
73+
path:
74+
lib.mapAttrs (
75+
name: value:
76+
let
77+
e = builtins.tryEval value;
78+
path' = path ++ [ name ];
79+
in
80+
if e.success && lib.isAttrs value && cond value then recurse path' value else f path' value
81+
);
82+
in
83+
recurse [ ] set;
84+
85+
# traverse the option tree along `path` from `root`, returning the option or
86+
# attrset at the given location
87+
optionByPath =
88+
path: root:
89+
let
90+
into =
91+
opt: part:
92+
if lib.isOption opt && opt.type.descriptionClass == "composite" then
93+
opt.type.getSubOptions [ ]
94+
else if lib.isOption opt then
95+
throw "Trying to access '${part}' inside ${opt.type.name} option while traversing option path '${lib.showOption path}'"
96+
else if lib.isAttrs opt && lib.hasAttr part opt then
97+
opt.${part}
98+
else
99+
throw "Found neither an attrset nor supported option type near '${part}' while traversing option path '${lib.showOption path}'";
100+
in
101+
lib.foldl into root path;
102+
103+
toPretty = lib.generators.toPretty { multiline = true; };
104+
safeToPretty =
105+
x:
106+
let
107+
e = builtins.tryEval (toPretty x);
108+
in
109+
if e.success then e.value else "«error»";
110+
111+
indent = str: lib.concatStringsSep "\n" (map (x: " " + x) (lib.splitString "\n" str));
112+
113+
optionAttrNames = attrs: lib.filter (x: x != "_module") (lib.attrNames attrs);
114+
115+
## full, non-recursive mode: print an option from `options`
116+
renderAttrs =
117+
attrs: "This attribute set contains:\n${lib.concatStringsSep "\n" (optionAttrNames attrs)}";
118+
119+
renderOption =
120+
option: value:
121+
let
122+
entry =
123+
cond: heading: value:
124+
lib.optional cond "${heading}:\n${indent value}";
125+
in
126+
lib.concatStringsSep "\n\n" (
127+
lib.concatLists [
128+
(entry true "Value" (toPretty value))
129+
(entry (option ? default) "Default" (toPretty option.default))
130+
(entry (option ? type) "Type" (option.type.description))
131+
(entry (option ? description) "Description" (lib.removeSuffix "\n" option.description))
132+
(entry (option ? example) "Example" (toPretty option.example))
133+
(entry (option ? declarations) "Declared by" (lib.concatStringsSep "\n" option.declarations))
134+
(entry (option ? files) "Defined by" (lib.concatStringsSep "\n" option.files))
135+
]
136+
);
137+
138+
renderFull =
139+
entry: configEntry:
140+
if lib.isOption entry then
141+
renderOption entry configEntry
142+
else if lib.isAttrs entry then
143+
renderAttrs entry
144+
else
145+
throw "Found neither an attrset nor option at option path '${lib.showOption path'}'";
146+
147+
## recursive mode: print paths and values from `config`
148+
renderRecursive =
149+
config:
150+
let
151+
renderShort = n: v: "${lib.showOption (path' ++ n)} = ${safeToPretty v};";
152+
mapAttrsRecursive' = safeMapAttrsRecursiveCond (x: !lib.isDerivation x);
153+
in
154+
if lib.isAttrs config then
155+
lib.concatStringsSep "\n" (lib.collect lib.isString (mapAttrsRecursive' renderShort config))
156+
else
157+
renderShort [ ] config;
158+
159+
in
160+
if !lib.hasAttrByPath path' nixos.config then
161+
throw "Couldn't resolve config path '${lib.showOption path'}'"
162+
else
163+
let
164+
optionEntry = optionByPath path' nixos.options;
165+
configEntry = lib.attrByPath path' null nixos.config;
166+
in
167+
if recursive then renderRecursive configEntry else renderFull optionEntry configEntry

0 commit comments

Comments
 (0)