lol

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

+138 -42
+1 -1
lib/modules.nix
··· 55 }; 56 }; 57 58 - closed = closeModules (modules ++ [ internalModule ]) (specialArgs // { inherit config options; lib = import ./.; }); 59 60 # Note: the list of modules is reversed to maintain backward 61 # compatibility with the old module system. Not sure if this is
··· 55 }; 56 }; 57 58 + closed = closeModules (modules ++ [ internalModule ]) ({ inherit config options; lib = import ./.; } // specialArgs); 59 60 # 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 ? {} 20 , 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; 52 53 # 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; 54 55 # 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> 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 - ] 14 }: 15 16 - # This file is used to generate a dot graph which contains all options and 17 - # there dependencies to track problems and their sources. 18 19 let 20 21 evalFun = { 22 - extraArgs ? {} 23 }: import ../lib/eval-config.nix { 24 modules = [ configuration ]; 25 - inherit extraArgs; 26 }; 27 28 eval = evalFun {}; 29 inherit (eval) pkgs; 30 31 - reportNewFailures = old: new: with pkgs.lib; 32 let 33 filterChanges = 34 filter ({fst, snd}: 35 - !(fst.config.success -> snd.config.success) 36 ); 37 38 keepNames = 39 map ({fst, snd}: 40 - assert fst.name == snd.name; snd.name 41 ); 42 in 43 keepNames ( 44 filterChanges ( 45 - zipLists (collect isOption old) (collect isOption new) 46 ) 47 ); 48 49 50 # 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); 62 63 overrideConfig = thrower: 64 - pkgs.lib.recursiveUpdateUntil (path: old: new: 65 path == thrower.path 66 ) eval.config thrower.config; 67 68 69 - 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; 77 }) introspectionModules; 78 79 - graphToDot = graph: with pkgs.lib; '' 80 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 ''; 91 92 in 93 94 - pkgs.texFunctions.dot2pdf { 95 - dotGraph = pkgs.writeTextFile { 96 name = "option_usages.dot"; 97 - text = graphToDot graph; 98 }; 99 }
··· 1 { configuration ? import ../lib/from-env.nix "NIXOS_CONFIG" <nixos-config> 2 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 ? [ ] 8 }: 9 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; 46 47 let 48 49 evalFun = { 50 + specialArgs ? {} 51 }: import ../lib/eval-config.nix { 52 modules = [ configuration ]; 53 + inherit specialArgs; 54 }; 55 56 eval = evalFun {}; 57 inherit (eval) pkgs; 58 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: 88 let 89 filterChanges = 90 filter ({fst, snd}: 91 + !(fst.success -> snd.success) 92 ); 93 94 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 ); 114 115 116 # 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); 128 129 overrideConfig = thrower: 130 + recursiveUpdateUntil (path: old: new: 131 path == thrower.path 132 ) eval.config thrower.config; 133 134 135 + 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; 145 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: '' 159 digraph "Option Usages" { 160 ${concatMapStrings ({option, usedBy}: 161 + concatMapStrings (user: '' 162 + "${option}" -> "${user}"'' 163 + ) usedBy 164 + ) displayOptionsGraph} 165 } 166 ''; 167 168 + graphToText = graph: 169 + concatMapStrings ({option, usedBy}: 170 + concatMapStrings (user: '' 171 + ${user} 172 + '') usedBy 173 + ) displayOptionsGraph; 174 + 175 in 176 177 + 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 }