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

Changed files
+138 -42
lib
nixos
+1 -1
lib/modules.nix
···
};
};
-
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
# compatibility with the old module system. Not sure if this is
+3 -1
nixos/lib/eval-config.nix
···
baseModules ? import ../modules/module-list.nix
, # !!! See comment about args in lib/modules.nix
extraArgs ? {}
+
, # !!! See comment about args in lib/modules.nix
+
specialArgs ? {}
, modules
, # !!! See comment about check in lib/modules.nix
check ? true
···
inherit prefix check;
modules = modules ++ extraModules ++ baseModules ++ [ pkgsModule ];
args = extraArgs;
-
specialArgs = { modulesPath = ../modules; };
+
specialArgs = { modulesPath = ../modules; } // specialArgs;
}) config options;
# These are the extra arguments passed to every module. In
+134 -40
nixos/maintainers/option-usages.nix
···
{ 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.");
};
···
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;
};
}