Update option-usages.nix expression to work with newer version of the module system.

+138 -42
+1 -1
lib/modules.nix
··· 55 55 }; 56 56 }; 57 57 58 - closed = closeModules (modules ++ [ internalModule ]) (specialArgs // { inherit config options; lib = import ./.; }); 58 + closed = closeModules (modules ++ [ internalModule ]) ({ inherit config options; lib = import ./.; } // specialArgs); 59 59 60 60 # Note: the list of modules is reversed to maintain backward 61 61 # compatibility with the old module system. Not sure if this is
+3 -1
nixos/lib/eval-config.nix
··· 17 17 baseModules ? import ../modules/module-list.nix 18 18 , # !!! See comment about args in lib/modules.nix 19 19 extraArgs ? {} 20 + , # !!! See comment about args in lib/modules.nix 21 + specialArgs ? {} 20 22 , modules 21 23 , # !!! See comment about check in lib/modules.nix 22 24 check ? true ··· 47 49 inherit prefix check; 48 50 modules = modules ++ extraModules ++ baseModules ++ [ pkgsModule ]; 49 51 args = extraArgs; 50 - specialArgs = { modulesPath = ../modules; }; 52 + specialArgs = { modulesPath = ../modules; } // specialArgs; 51 53 }) config options; 52 54 53 55 # These are the extra arguments passed to every module. In
+134 -40
nixos/maintainers/option-usages.nix
··· 1 1 { configuration ? import ../lib/from-env.nix "NIXOS_CONFIG" <nixos-config> 2 2 3 - # []: 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 - ] 3 + # provide an option name, as a string literal. 4 + , testOption ? null 5 + 6 + # provide a list of option names, as string literals. 7 + , testOptions ? [ ] 14 8 }: 15 9 16 - # This file is used to generate a dot graph which contains all options and 17 - # there dependencies to track problems and their sources. 10 + # 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; 18 46 19 47 let 20 48 21 49 evalFun = { 22 - extraArgs ? {} 50 + specialArgs ? {} 23 51 }: import ../lib/eval-config.nix { 24 52 modules = [ configuration ]; 25 - inherit extraArgs; 53 + inherit specialArgs; 26 54 }; 27 55 28 56 eval = evalFun {}; 29 57 inherit (eval) pkgs; 30 58 31 - reportNewFailures = old: new: with pkgs.lib; 59 + 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: 32 88 let 33 89 filterChanges = 34 90 filter ({fst, snd}: 35 - !(fst.config.success -> snd.config.success) 91 + !(fst.success -> snd.success) 36 92 ); 37 93 38 94 keepNames = 39 95 map ({fst, snd}: 40 - assert fst.name == snd.name; snd.name 96 + /* assert fst.name == snd.name; */ snd.name 41 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)); 42 108 in 43 109 keepNames ( 44 110 filterChanges ( 45 - zipLists (collect isOption old) (collect isOption new) 111 + zipLists (tryCollectOptions old) (tryCollectOptions new) 46 112 ) 47 113 ); 48 114 49 115 50 116 # Create a list of modules where each module contains only one failling 51 117 # options. 52 - introspectionModules = with pkgs.lib; 118 + introspectionModules = 53 119 let 54 120 setIntrospection = opt: rec { 55 - name = opt.name; 56 - path = splitString "." opt.name; 121 + name = showOption opt.loc; 122 + path = opt.loc; 57 123 config = setAttrByPath path 58 124 (throw "Usage introspection of '${name}' by forced failure."); 59 125 }; ··· 61 127 map setIntrospection (collect isOption eval.options); 62 128 63 129 overrideConfig = thrower: 64 - pkgs.lib.recursiveUpdateUntil (path: old: new: 130 + recursiveUpdateUntil (path: old: new: 65 131 path == thrower.path 66 132 ) eval.config thrower.config; 67 133 68 134 69 - graph = with pkgs.lib; 135 + graph = 70 136 map (thrower: { 71 137 option = thrower.name; 72 - usedBy = reportNewFailures eval.options (evalFun { 73 - extraArgs = { 74 - config = overrideConfig thrower; 75 - }; 76 - }).options; 138 + usedBy = assert __trace "Investigate ${thrower.name}" true; 139 + reportNewFailures eval.options (evalFun { 140 + specialArgs = { 141 + config = overrideConfig thrower; 142 + }; 143 + }).options; 77 144 }) introspectionModules; 78 145 79 - graphToDot = graph: with pkgs.lib; '' 146 + 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: '' 80 159 digraph "Option Usages" { 81 160 ${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} 161 + concatMapStrings (user: '' 162 + "${option}" -> "${user}"'' 163 + ) usedBy 164 + ) displayOptionsGraph} 89 165 } 90 166 ''; 91 167 168 + graphToText = graph: 169 + concatMapStrings ({option, usedBy}: 170 + concatMapStrings (user: '' 171 + ${user} 172 + '') usedBy 173 + ) displayOptionsGraph; 174 + 92 175 in 93 176 94 - pkgs.texFunctions.dot2pdf { 95 - dotGraph = pkgs.writeTextFile { 177 + rec { 178 + dotContent = graphToDot graph; 179 + dot = pkgs.writeTextFile { 96 180 name = "option_usages.dot"; 97 - text = graphToDot graph; 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; 98 192 }; 99 193 }