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

This commit is contained in:
Nicolas B. Pierron 2015-08-09 14:40:01 +02:00
parent 2c9c135ee2
commit c47e89623b
3 changed files with 138 additions and 42 deletions

View File

@ -55,7 +55,7 @@ rec {
}; };
}; };
closed = closeModules (modules ++ [ internalModule ]) (specialArgs // { inherit config options; lib = import ./.; }); closed = closeModules (modules ++ [ internalModule ]) ({ inherit config options; lib = import ./.; } // specialArgs);
# Note: the list of modules is reversed to maintain backward # Note: the list of modules is reversed to maintain backward
# compatibility with the old module system. Not sure if this is # compatibility with the old module system. Not sure if this is

View File

@ -17,6 +17,8 @@
baseModules ? import ../modules/module-list.nix baseModules ? import ../modules/module-list.nix
, # !!! See comment about args in lib/modules.nix , # !!! See comment about args in lib/modules.nix
extraArgs ? {} extraArgs ? {}
, # !!! See comment about args in lib/modules.nix
specialArgs ? {}
, modules , modules
, # !!! See comment about check in lib/modules.nix , # !!! See comment about check in lib/modules.nix
check ? true check ? true
@ -47,7 +49,7 @@ in rec {
inherit prefix check; inherit prefix check;
modules = modules ++ extraModules ++ baseModules ++ [ pkgsModule ]; modules = modules ++ extraModules ++ baseModules ++ [ pkgsModule ];
args = extraArgs; args = extraArgs;
specialArgs = { modulesPath = ../modules; }; specialArgs = { modulesPath = ../modules; } // specialArgs;
}) config options; }) config options;
# These are the extra arguments passed to every module. In # These are the extra arguments passed to every module. In

View File

@ -1,59 +1,125 @@
{ configuration ? import ../lib/from-env.nix "NIXOS_CONFIG" <nixos-config> { configuration ? import ../lib/from-env.nix "NIXOS_CONFIG" <nixos-config>
# []: display all options # provide an option name, as a string literal.
# [<option names>]: display the selected options , testOption ? null
, displayOptions ? [
"hardware.pcmcia.enable" # provide a list of option names, as string literals.
"environment.systemPackages" , testOptions ? [ ]
"boot.kernelModules"
"services.udev.packages"
"jobs"
"environment.etc"
"system.activationScripts"
]
}: }:
# This file is used to generate a dot graph which contains all options and # This file is made to be used as follow:
# there dependencies to track problems and their sources. #
# $ 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 let
evalFun = { evalFun = {
extraArgs ? {} specialArgs ? {}
}: import ../lib/eval-config.nix { }: import ../lib/eval-config.nix {
modules = [ configuration ]; modules = [ configuration ];
inherit extraArgs; inherit specialArgs;
}; };
eval = evalFun {}; eval = evalFun {};
inherit (eval) pkgs; 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 let
filterChanges = filterChanges =
filter ({fst, snd}: filter ({fst, snd}:
!(fst.config.success -> snd.config.success) !(fst.success -> snd.success)
); );
keepNames = keepNames =
map ({fst, snd}: 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 in
keepNames ( keepNames (
filterChanges ( 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 # Create a list of modules where each module contains only one failling
# options. # options.
introspectionModules = with pkgs.lib; introspectionModules =
let let
setIntrospection = opt: rec { setIntrospection = opt: rec {
name = opt.name; name = showOption opt.loc;
path = splitString "." opt.name; path = opt.loc;
config = setAttrByPath path config = setAttrByPath path
(throw "Usage introspection of '${name}' by forced failure."); (throw "Usage introspection of '${name}' by forced failure.");
}; };
@ -61,39 +127,67 @@ let
map setIntrospection (collect isOption eval.options); map setIntrospection (collect isOption eval.options);
overrideConfig = thrower: overrideConfig = thrower:
pkgs.lib.recursiveUpdateUntil (path: old: new: recursiveUpdateUntil (path: old: new:
path == thrower.path path == thrower.path
) eval.config thrower.config; ) eval.config thrower.config;
graph = with pkgs.lib; graph =
map (thrower: { map (thrower: {
option = thrower.name; option = thrower.name;
usedBy = reportNewFailures eval.options (evalFun { usedBy = assert __trace "Investigate ${thrower.name}" true;
extraArgs = { reportNewFailures eval.options (evalFun {
config = overrideConfig thrower; specialArgs = {
}; config = overrideConfig thrower;
}).options; };
}).options;
}) introspectionModules; }) 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" { digraph "Option Usages" {
${concatMapStrings ({option, usedBy}: ${concatMapStrings ({option, usedBy}:
assert __trace option true; concatMapStrings (user: ''
if displayOptions == [] || elem option displayOptions then "${option}" -> "${user}"''
concatMapStrings (user: '' ) usedBy
"${option}" -> "${user}"'' ) displayOptionsGraph}
) usedBy
else ""
) graph}
} }
''; '';
graphToText = graph:
concatMapStrings ({option, usedBy}:
concatMapStrings (user: ''
${user}
'') usedBy
) displayOptionsGraph;
in in
pkgs.texFunctions.dot2pdf { rec {
dotGraph = pkgs.writeTextFile { dotContent = graphToDot graph;
dot = pkgs.writeTextFile {
name = "option_usages.dot"; 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;
}; };
} }