Add a nix expression which generate a graph inside a pdf to display option
usages. svn path=/nixos/trunk/; revision=17483
This commit is contained in:
		
							parent
							
								
									373445c410
								
							
						
					
					
						commit
						bd41c01bfe
					
				
							
								
								
									
										131
									
								
								maintainers/option-usages.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								maintainers/option-usages.nix
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,131 @@
 | 
			
		||||
{ configuration ? import ../lib/from-env.nix "NIXOS_CONFIG" /etc/nixos/configuration.nix
 | 
			
		||||
 | 
			
		||||
# []: 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)
 | 
			
		||||
         )
 | 
			
		||||
       );
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  mkProtect = name: value: { _type = "protect"; inherit name value; };
 | 
			
		||||
  isProtected = attr: (pkgs.lib.typeOf attr) == "protect";
 | 
			
		||||
 | 
			
		||||
  getName = set: with pkgs.lib;
 | 
			
		||||
    if isProtected set then
 | 
			
		||||
      set.name
 | 
			
		||||
    else
 | 
			
		||||
      getName (head (attrValues set));
 | 
			
		||||
 | 
			
		||||
  rmProtect = set: with pkgs.lib;
 | 
			
		||||
    if isProtected set then
 | 
			
		||||
      set.value
 | 
			
		||||
    else
 | 
			
		||||
      mapAttrs (n: rmProtect) set;
 | 
			
		||||
 | 
			
		||||
  # Create a list of modules where each module contains only one failling
 | 
			
		||||
  # options.
 | 
			
		||||
  introspectionModules = with pkgs.lib;
 | 
			
		||||
    let
 | 
			
		||||
      genericFun = f: set:
 | 
			
		||||
        fold (name: rest:
 | 
			
		||||
          (map (v:
 | 
			
		||||
            listToAttrs [(nameValuePair name v)]
 | 
			
		||||
          ) (f (getAttr name set)))
 | 
			
		||||
          ++ rest
 | 
			
		||||
        ) [] (attrNames set);
 | 
			
		||||
 | 
			
		||||
      addIntrospection = opt: mkProtect opt.name (
 | 
			
		||||
        throw "Usage introspection of '${opt.name}' by forced failure."
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      f = v:
 | 
			
		||||
        if isOption v then
 | 
			
		||||
          [(addIntrospection v)]
 | 
			
		||||
        else
 | 
			
		||||
          genericFun f v;
 | 
			
		||||
    in
 | 
			
		||||
      genericFun f eval.options;
 | 
			
		||||
 | 
			
		||||
  overrideConfig = override: with pkgs.lib;
 | 
			
		||||
    let f = configs:
 | 
			
		||||
      zip (n: v:
 | 
			
		||||
        if tail v == [] || isProtected (head v) then
 | 
			
		||||
          head v
 | 
			
		||||
        else
 | 
			
		||||
          f v
 | 
			
		||||
      ) configs;
 | 
			
		||||
    in
 | 
			
		||||
      f [ override eval.config ];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  graph = with pkgs.lib;
 | 
			
		||||
    map (thrower: {
 | 
			
		||||
      option = getName thrower;
 | 
			
		||||
      usedBy = reportNewFailures eval.options (evalFun {
 | 
			
		||||
        extraArgs = {
 | 
			
		||||
          config = overrideConfig (rmProtect 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;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user