diff options
Diffstat (limited to 'nixos/maintainers')
-rw-r--r-- | nixos/maintainers/option-usages.nix | 174 |
1 files changed, 134 insertions, 40 deletions
diff --git a/nixos/maintainers/option-usages.nix b/nixos/maintainers/option-usages.nix index 7413b9e18cec0..854ecf7eac51b 100644 --- a/nixos/maintainers/option-usages.nix +++ b/nixos/maintainers/option-usages.nix @@ -1,59 +1,125 @@ { configuration ? import ../lib/from-env.nix "NIXOS_CONFIG" <nixos-config> -# []: display all options -# [<option names>]: display the selected options -, displayOptions ? [ - "hardware.pcmcia.enable" - "environment.systemPackages" - "boot.kernelModules" - "services.udev.packages" - "jobs" - "environment.etc" - "system.activationScripts" - ] +# provide an option name, as a string literal. +, testOption ? null + +# provide a list of option names, as string literals. +, testOptions ? [ ] }: -# This file is used to generate a dot graph which contains all options and -# there dependencies to track problems and their sources. +# This file is made to be used as follow: +# +# $ nix-instantiate ./option-usage.nix --argstr testOption service.xserver.enable -A txtContent --eval +# +# or +# +# $ nix-build ./option-usage.nix --argstr testOption service.xserver.enable -A txt -o service.xserver.enable._txt +# +# otther target exists such as, `dotContent`, `dot`, and `pdf`. If you are +# looking for the option usage of multiple options, you can provide a list +# as argument. +# +# $ nix-build ./option-usage.nix --arg testOptions \ +# '["boot.loader.gummiboot.enable" "boot.loader.gummiboot.timeout"]' \ +# -A txt -o gummiboot.list +# +# Note, this script is slow as it has to evaluate all options of the system +# once per queried option. +# +# This nix expression works by doing a first evaluation, which evaluates the +# result of every option. +# +# Then, for each queried option, we evaluate the NixOS modules a second +# time, except that we replace the `config` argument of all the modules with +# the result of the original evaluation, except for the tested option which +# value is replaced by a `throw` statement which is caught by the `tryEval` +# evaluation of each option value. +# +# We then compare the result of the evluation of the original module, with +# the result of the second evaluation, and consider that the new failures are +# caused by our mutation of the `config` argument. +# +# Doing so returns all option results which are directly using the +# tested option result. + +with import ../../lib; let evalFun = { - extraArgs ? {} + specialArgs ? {} }: import ../lib/eval-config.nix { modules = [ configuration ]; - inherit extraArgs; + inherit specialArgs; }; eval = evalFun {}; inherit (eval) pkgs; - reportNewFailures = old: new: with pkgs.lib; + excludedTestOptions = [ + # We cannot evluate _module.args, as it is used during the computation + # of the modules list. + "_module.args" + + # For some reasons which we yet have to investigate, some options cannot + # be replaced by a throw without cuasing a non-catchable failure. + "networking.bonds" + "networking.bridges" + "networking.interfaces" + "networking.macvlans" + "networking.sits" + "networking.vlans" + "services.openssh.startWhenNeeded" + ]; + + # for some reasons which we yet have to investigate, some options are + # time-consuming to compute, thus we filter them out at the moment. + excludedOptions = [ + "boot.systemd.services" + "systemd.services" + "environment.gnome3.packageSet" + "kde.extraPackages" + ]; + excludeOptions = list: + filter (opt: !(elem (showOption opt.loc) excludedOptions)) list; + + + reportNewFailures = old: new: let filterChanges = filter ({fst, snd}: - !(fst.config.success -> snd.config.success) + !(fst.success -> snd.success) ); keepNames = map ({fst, snd}: - assert fst.name == snd.name; snd.name + /* assert fst.name == snd.name; */ snd.name ); + + # Use tryEval (strict ...) to know if there is any failure while + # evaluating the option value. + # + # Note, the `strict` function is not strict enough, but using toXML + # builtins multiply by 4 the memory usage and the time used to compute + # each options. + tryCollectOptions = moduleResult: + flip map (excludeOptions (collect isOption moduleResult)) (opt: + { name = showOption opt.loc; } // builtins.tryEval (strict opt.value)); in keepNames ( filterChanges ( - zipLists (collect isOption old) (collect isOption new) + zipLists (tryCollectOptions old) (tryCollectOptions new) ) ); # Create a list of modules where each module contains only one failling # options. - introspectionModules = with pkgs.lib; + introspectionModules = let setIntrospection = opt: rec { - name = opt.name; - path = splitString "." opt.name; + name = showOption opt.loc; + path = opt.loc; config = setAttrByPath path (throw "Usage introspection of '${name}' by forced failure."); }; @@ -61,39 +127,67 @@ let map setIntrospection (collect isOption eval.options); overrideConfig = thrower: - pkgs.lib.recursiveUpdateUntil (path: old: new: + recursiveUpdateUntil (path: old: new: path == thrower.path ) eval.config thrower.config; - graph = with pkgs.lib; + graph = map (thrower: { option = thrower.name; - usedBy = reportNewFailures eval.options (evalFun { - extraArgs = { - config = overrideConfig thrower; - }; - }).options; + usedBy = assert __trace "Investigate ${thrower.name}" true; + reportNewFailures eval.options (evalFun { + specialArgs = { + config = overrideConfig thrower; + }; + }).options; }) introspectionModules; - graphToDot = graph: with pkgs.lib; '' + displayOptionsGraph = + let + checkList = + if !(isNull testOption) then [ testOption ] + else testOptions; + checkAll = checkList == []; + in + flip filter graph ({option, usedBy}: + (checkAll || elem option checkList) + && !(elem option excludedTestOptions) + ); + + graphToDot = graph: '' digraph "Option Usages" { ${concatMapStrings ({option, usedBy}: - assert __trace option true; - if displayOptions == [] || elem option displayOptions then - concatMapStrings (user: '' - "${option}" -> "${user}"'' - ) usedBy - else "" - ) graph} + concatMapStrings (user: '' + "${option}" -> "${user}"'' + ) usedBy + ) displayOptionsGraph} } ''; + graphToText = graph: + concatMapStrings ({option, usedBy}: + concatMapStrings (user: '' + ${user} + '') usedBy + ) displayOptionsGraph; + in -pkgs.texFunctions.dot2pdf { - dotGraph = pkgs.writeTextFile { +rec { + dotContent = graphToDot graph; + dot = pkgs.writeTextFile { name = "option_usages.dot"; - text = graphToDot graph; + text = dotContent; + }; + + pdf = pkgs.texFunctions.dot2pdf { + dotGraph = dot; + }; + + txtContent = graphToText graph; + txt = pkgs.writeTextFile { + name = "option_usages.txt"; + text = txtContent; }; } |