summary refs log tree commit diff
path: root/nixos/maintainers/option-usages.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/maintainers/option-usages.nix')
-rw-r--r--nixos/maintainers/option-usages.nix99
1 files changed, 99 insertions, 0 deletions
diff --git a/nixos/maintainers/option-usages.nix b/nixos/maintainers/option-usages.nix
new file mode 100644
index 00000000000..7413b9e18ce
--- /dev/null
+++ b/nixos/maintainers/option-usages.nix
@@ -0,0 +1,99 @@
+{ 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"
+  ]
+}:
+
+# This file is used to generate a dot graph which contains all options and
+# there dependencies to track problems and their sources.
+
+let
+
+  evalFun = {
+    extraArgs ? {}
+  }: import ../lib/eval-config.nix {
+       modules = [ configuration ];
+       inherit extraArgs;
+     };
+
+  eval = evalFun {};
+  inherit (eval) pkgs;
+
+  reportNewFailures = old: new: with pkgs.lib;
+    let
+      filterChanges =
+        filter ({fst, snd}:
+          !(fst.config.success -> snd.config.success)
+        );
+
+      keepNames =
+        map ({fst, snd}:
+          assert fst.name == snd.name; snd.name
+        );
+     in
+       keepNames (
+         filterChanges (
+           zipLists (collect isOption old) (collect isOption new)
+         )
+       );
+
+
+  # Create a list of modules where each module contains only one failling
+  # options.
+  introspectionModules = with pkgs.lib;
+    let
+      setIntrospection = opt: rec {
+        name = opt.name;
+        path = splitString "." opt.name;
+        config = setAttrByPath path
+          (throw "Usage introspection of '${name}' by forced failure.");
+      };
+    in
+      map setIntrospection (collect isOption eval.options);
+
+  overrideConfig = thrower:
+    pkgs.lib.recursiveUpdateUntil (path: old: new:
+      path == thrower.path
+    ) eval.config thrower.config;
+
+
+  graph = with pkgs.lib;
+    map (thrower: {
+      option = thrower.name;
+      usedBy = reportNewFailures eval.options (evalFun {
+        extraArgs = {
+          config = overrideConfig thrower;
+        };
+      }).options;
+    }) introspectionModules;
+
+  graphToDot = graph: with pkgs.lib; ''
+    digraph "Option Usages" {
+      ${concatMapStrings ({option, usedBy}:
+          assert __trace option true;
+          if displayOptions == [] || elem option displayOptions then
+            concatMapStrings (user: ''
+              "${option}" -> "${user}"''
+            ) usedBy
+          else ""
+        ) graph}
+    }
+  '';
+
+in
+
+pkgs.texFunctions.dot2pdf {
+  dotGraph = pkgs.writeTextFile {
+    name = "option_usages.dot";
+    text = graphToDot graph;
+  };
+}