···55 };
56 };
5758- closed = closeModules (modules ++ [ internalModule ]) (specialArgs // { inherit config options; lib = import ./.; });
5960 # Note: the list of modules is reversed to maintain backward
61 # compatibility with the old module system. Not sure if this is
···55 };
56 };
5758+ closed = closeModules (modules ++ [ internalModule ]) ({ inherit config options; lib = import ./.; } // specialArgs);
5960 # Note: the list of modules is reversed to maintain backward
61 # compatibility with the old module system. Not sure if this is
+3-1
nixos/lib/eval-config.nix
···17 baseModules ? import ../modules/module-list.nix
18, # !!! See comment about args in lib/modules.nix
19 extraArgs ? {}
0020, modules
21, # !!! See comment about check in lib/modules.nix
22 check ? true
···47 inherit prefix check;
48 modules = modules ++ extraModules ++ baseModules ++ [ pkgsModule ];
49 args = extraArgs;
50- specialArgs = { modulesPath = ../modules; };
51 }) config options;
5253 # These are the extra arguments passed to every module. In
···17 baseModules ? import ../modules/module-list.nix
18, # !!! See comment about args in lib/modules.nix
19 extraArgs ? {}
20+, # !!! See comment about args in lib/modules.nix
21+ specialArgs ? {}
22, modules
23, # !!! See comment about check in lib/modules.nix
24 check ? true
···49 inherit prefix check;
50 modules = modules ++ extraModules ++ baseModules ++ [ pkgsModule ];
51 args = extraArgs;
52+ specialArgs = { modulesPath = ../modules; } // specialArgs;
53 }) config options;
5455 # These are the extra arguments passed to every module. In
+134-40
nixos/maintainers/option-usages.nix
···1{ configuration ? import ../lib/from-env.nix "NIXOS_CONFIG" <nixos-config>
23-# []: display all options
4-# [<option names>]: display the selected options
5-, displayOptions ? [
6- "hardware.pcmcia.enable"
7- "environment.systemPackages"
8- "boot.kernelModules"
9- "services.udev.packages"
10- "jobs"
11- "environment.etc"
12- "system.activationScripts"
13- ]
14}:
1516-# This file is used to generate a dot graph which contains all options and
17-# there dependencies to track problems and their sources.
00000000000000000000000000000000001819let
2021 evalFun = {
22- extraArgs ? {}
23 }: import ../lib/eval-config.nix {
24 modules = [ configuration ];
25- inherit extraArgs;
26 };
2728 eval = evalFun {};
29 inherit (eval) pkgs;
3031- reportNewFailures = old: new: with pkgs.lib;
000000000000000000000000000032 let
33 filterChanges =
34 filter ({fst, snd}:
35- !(fst.config.success -> snd.config.success)
36 );
3738 keepNames =
39 map ({fst, snd}:
40- assert fst.name == snd.name; snd.name
41 );
000000000042 in
43 keepNames (
44 filterChanges (
45- zipLists (collect isOption old) (collect isOption new)
46 )
47 );
484950 # Create a list of modules where each module contains only one failling
51 # options.
52- introspectionModules = with pkgs.lib;
53 let
54 setIntrospection = opt: rec {
55- name = opt.name;
56- path = splitString "." opt.name;
57 config = setAttrByPath path
58 (throw "Usage introspection of '${name}' by forced failure.");
59 };
···61 map setIntrospection (collect isOption eval.options);
6263 overrideConfig = thrower:
64- pkgs.lib.recursiveUpdateUntil (path: old: new:
65 path == thrower.path
66 ) eval.config thrower.config;
676869- graph = with pkgs.lib;
70 map (thrower: {
71 option = thrower.name;
72- usedBy = reportNewFailures eval.options (evalFun {
73- extraArgs = {
74- config = overrideConfig thrower;
75- };
76- }).options;
077 }) introspectionModules;
7879- graphToDot = graph: with pkgs.lib; ''
00000000000080 digraph "Option Usages" {
81 ${concatMapStrings ({option, usedBy}:
82- assert __trace option true;
83- if displayOptions == [] || elem option displayOptions then
84- concatMapStrings (user: ''
85- "${option}" -> "${user}"''
86- ) usedBy
87- else ""
88- ) graph}
89 }
90 '';
91000000092in
9394-pkgs.texFunctions.dot2pdf {
95- dotGraph = pkgs.writeTextFile {
096 name = "option_usages.dot";
97- text = graphToDot graph;
000000000098 };
99}
···1{ configuration ? import ../lib/from-env.nix "NIXOS_CONFIG" <nixos-config>
23+# provide an option name, as a string literal.
4+, testOption ? null
5+6+# provide a list of option names, as string literals.
7+, testOptions ? [ ]
0000008}:
910+# This file is made to be used as follow:
11+#
12+# $ nix-instantiate ./option-usage.nix --argstr testOption service.xserver.enable -A txtContent --eval
13+#
14+# or
15+#
16+# $ nix-build ./option-usage.nix --argstr testOption service.xserver.enable -A txt -o service.xserver.enable._txt
17+#
18+# otther target exists such as, `dotContent`, `dot`, and `pdf`. If you are
19+# looking for the option usage of multiple options, you can provide a list
20+# as argument.
21+#
22+# $ nix-build ./option-usage.nix --arg testOptions \
23+# '["boot.loader.gummiboot.enable" "boot.loader.gummiboot.timeout"]' \
24+# -A txt -o gummiboot.list
25+#
26+# Note, this script is slow as it has to evaluate all options of the system
27+# once per queried option.
28+#
29+# This nix expression works by doing a first evaluation, which evaluates the
30+# result of every option.
31+#
32+# Then, for each queried option, we evaluate the NixOS modules a second
33+# time, except that we replace the `config` argument of all the modules with
34+# the result of the original evaluation, except for the tested option which
35+# value is replaced by a `throw` statement which is caught by the `tryEval`
36+# evaluation of each option value.
37+#
38+# We then compare the result of the evluation of the original module, with
39+# the result of the second evaluation, and consider that the new failures are
40+# caused by our mutation of the `config` argument.
41+#
42+# Doing so returns all option results which are directly using the
43+# tested option result.
44+45+with import ../../lib;
4647let
4849 evalFun = {
50+ specialArgs ? {}
51 }: import ../lib/eval-config.nix {
52 modules = [ configuration ];
53+ inherit specialArgs;
54 };
5556 eval = evalFun {};
57 inherit (eval) pkgs;
5859+ excludedTestOptions = [
60+ # We cannot evluate _module.args, as it is used during the computation
61+ # of the modules list.
62+ "_module.args"
63+64+ # For some reasons which we yet have to investigate, some options cannot
65+ # be replaced by a throw without cuasing a non-catchable failure.
66+ "networking.bonds"
67+ "networking.bridges"
68+ "networking.interfaces"
69+ "networking.macvlans"
70+ "networking.sits"
71+ "networking.vlans"
72+ "services.openssh.startWhenNeeded"
73+ ];
74+75+ # for some reasons which we yet have to investigate, some options are
76+ # time-consuming to compute, thus we filter them out at the moment.
77+ excludedOptions = [
78+ "boot.systemd.services"
79+ "systemd.services"
80+ "environment.gnome3.packageSet"
81+ "kde.extraPackages"
82+ ];
83+ excludeOptions = list:
84+ filter (opt: !(elem (showOption opt.loc) excludedOptions)) list;
85+86+87+ reportNewFailures = old: new:
88 let
89 filterChanges =
90 filter ({fst, snd}:
91+ !(fst.success -> snd.success)
92 );
9394 keepNames =
95 map ({fst, snd}:
96+ /* assert fst.name == snd.name; */ snd.name
97 );
98+99+ # Use tryEval (strict ...) to know if there is any failure while
100+ # evaluating the option value.
101+ #
102+ # Note, the `strict` function is not strict enough, but using toXML
103+ # builtins multiply by 4 the memory usage and the time used to compute
104+ # each options.
105+ tryCollectOptions = moduleResult:
106+ flip map (excludeOptions (collect isOption moduleResult)) (opt:
107+ { name = showOption opt.loc; } // builtins.tryEval (strict opt.value));
108 in
109 keepNames (
110 filterChanges (
111+ zipLists (tryCollectOptions old) (tryCollectOptions new)
112 )
113 );
114115116 # Create a list of modules where each module contains only one failling
117 # options.
118+ introspectionModules =
119 let
120 setIntrospection = opt: rec {
121+ name = showOption opt.loc;
122+ path = opt.loc;
123 config = setAttrByPath path
124 (throw "Usage introspection of '${name}' by forced failure.");
125 };
···127 map setIntrospection (collect isOption eval.options);
128129 overrideConfig = thrower:
130+ recursiveUpdateUntil (path: old: new:
131 path == thrower.path
132 ) eval.config thrower.config;
133134135+ graph =
136 map (thrower: {
137 option = thrower.name;
138+ usedBy = assert __trace "Investigate ${thrower.name}" true;
139+ reportNewFailures eval.options (evalFun {
140+ specialArgs = {
141+ config = overrideConfig thrower;
142+ };
143+ }).options;
144 }) introspectionModules;
145146+ displayOptionsGraph =
147+ let
148+ checkList =
149+ if !(isNull testOption) then [ testOption ]
150+ else testOptions;
151+ checkAll = checkList == [];
152+ in
153+ flip filter graph ({option, usedBy}:
154+ (checkAll || elem option checkList)
155+ && !(elem option excludedTestOptions)
156+ );
157+158+ graphToDot = graph: ''
159 digraph "Option Usages" {
160 ${concatMapStrings ({option, usedBy}:
161+ concatMapStrings (user: ''
162+ "${option}" -> "${user}"''
163+ ) usedBy
164+ ) displayOptionsGraph}
000165 }
166 '';
167168+ graphToText = graph:
169+ concatMapStrings ({option, usedBy}:
170+ concatMapStrings (user: ''
171+ ${user}
172+ '') usedBy
173+ ) displayOptionsGraph;
174+175in
176177+rec {
178+ dotContent = graphToDot graph;
179+ dot = pkgs.writeTextFile {
180 name = "option_usages.dot";
181+ text = dotContent;
182+ };
183+184+ pdf = pkgs.texFunctions.dot2pdf {
185+ dotGraph = dot;
186+ };
187+188+ txtContent = graphToText graph;
189+ txt = pkgs.writeTextFile {
190+ name = "option_usages.txt";
191+ text = txtContent;
192 };
193}