summary refs log tree commit diff
path: root/nixos/maintainers/option-usages.nix
blob: 7413b9e18cec0e4a478b0fd97d516701d1942ca4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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;
  };
}