Replace the traversal of modules:
- Remove handleOptionSets which used option declarations & definitions
in the same set.
- Add a traversal of modules where "config" and "options" are traverse at
the same time.
This allow to have accruate error messages with the incriminated files
playing a role in the error.
This system add a new restriction compare to the previous system:
- A module with no structure (option definitions & option declarations
& require) should not contain any option declarations.  If such module
exists you must convert it to the following form:
{ imports = <content of the require attribute>;
  options = <set of option declarations>;
  config = <set of option definitions>;
}
svn path=/nixpkgs/trunk/; revision=17163
			
			
This commit is contained in:
		
							parent
							
								
									07ed9e4611
								
							
						
					
					
						commit
						0c16b00cbd
					
				@ -23,4 +23,4 @@ in
 | 
			
		||||
  # !!! don't include everything at top-level; perhaps only the most
 | 
			
		||||
  # commonly used functions.
 | 
			
		||||
  // trivial // lists // strings // stringsWithDeps // attrsets // sources
 | 
			
		||||
  // properties // options // types // meta // debug // misc
 | 
			
		||||
  // properties // options // types // meta // debug // misc // modules
 | 
			
		||||
 | 
			
		||||
@ -200,14 +200,16 @@ rec {
 | 
			
		||||
  # Merge sets of attributes and use the function f to merge
 | 
			
		||||
  # attributes values.
 | 
			
		||||
  zip = f: sets:
 | 
			
		||||
    zipWithNames (concatMap builtins.attrNames sets) f sets;
 | 
			
		||||
 | 
			
		||||
  zipWithNames = names: f: sets:
 | 
			
		||||
    builtins.listToAttrs (map (name: {
 | 
			
		||||
      inherit name;
 | 
			
		||||
      value =
 | 
			
		||||
        f name
 | 
			
		||||
          (map (__getAttr name)
 | 
			
		||||
            (filter (__hasAttr name) sets));
 | 
			
		||||
    }) (concatMap builtins.attrNames sets));
 | 
			
		||||
 | 
			
		||||
    }) names);
 | 
			
		||||
 | 
			
		||||
  lazyGenericClosure = {startSet, operator}:
 | 
			
		||||
    let
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ with import ./trivial.nix;
 | 
			
		||||
with import ./lists.nix;
 | 
			
		||||
with import ./misc.nix;
 | 
			
		||||
with import ./attrsets.nix;
 | 
			
		||||
with import ./options.nix;
 | 
			
		||||
with import ./properties.nix;
 | 
			
		||||
 | 
			
		||||
rec {
 | 
			
		||||
@ -32,6 +33,10 @@ rec {
 | 
			
		||||
    else
 | 
			
		||||
      f;
 | 
			
		||||
 | 
			
		||||
  isModule = m:
 | 
			
		||||
       (m ? config && isAttrs m.config && ! isOption m.config)
 | 
			
		||||
    || (m ? options && isAttrs m.options && ! isOption m.options);
 | 
			
		||||
 | 
			
		||||
  # Convert module to a set which has imports / options and config
 | 
			
		||||
  # attributes.
 | 
			
		||||
  unifyModuleSyntax = m:
 | 
			
		||||
@ -48,7 +53,7 @@ rec {
 | 
			
		||||
      getConfig = m:
 | 
			
		||||
        removeAttrs (delayProperties m) ["require"];
 | 
			
		||||
    in
 | 
			
		||||
      if m ? config || m ? options then
 | 
			
		||||
      if isModule m then
 | 
			
		||||
        m
 | 
			
		||||
      else
 | 
			
		||||
        {
 | 
			
		||||
@ -88,4 +93,165 @@ rec {
 | 
			
		||||
        [ m ]
 | 
			
		||||
    ) modules;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  moduleApply = funs: module:
 | 
			
		||||
    lib.mapAttrs (name: value:
 | 
			
		||||
      if builtins.hasAttr name funs then
 | 
			
		||||
        let fun = lib.getAttr name funs; in
 | 
			
		||||
        fun value
 | 
			
		||||
      else
 | 
			
		||||
        value
 | 
			
		||||
    ) module;
 | 
			
		||||
 | 
			
		||||
  delayModule = module:
 | 
			
		||||
    moduleApply { config = delayProperties; } module;
 | 
			
		||||
 | 
			
		||||
  selectModule = name: m:
 | 
			
		||||
    { inherit (m) key;
 | 
			
		||||
    } // (
 | 
			
		||||
      if m ? options && builtins.hasAttr name m.options then
 | 
			
		||||
        { options = lib.getAttr name m.options; }
 | 
			
		||||
      else {}
 | 
			
		||||
    ) // (
 | 
			
		||||
      if m ? config && builtins.hasAttr name m.config then
 | 
			
		||||
        { config = lib.getAttr name m.config; }
 | 
			
		||||
      else {}
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  filterModules = name: modules:
 | 
			
		||||
    filter (m: m ? config || m ? options) (
 | 
			
		||||
      map (selectModule name) modules
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  modulesNames = modules:
 | 
			
		||||
    lib.concatMap (m: []
 | 
			
		||||
    ++ optionals (m ? options) (lib.attrNames m.options)
 | 
			
		||||
    ++ optionals (m ? config) (lib.attrNames m.config)
 | 
			
		||||
    ) modules;
 | 
			
		||||
 | 
			
		||||
  moduleZip = funs: modules:
 | 
			
		||||
    lib.mapAttrs (name: fun:
 | 
			
		||||
      fun (
 | 
			
		||||
        map (lib.getAttr name) (
 | 
			
		||||
          filter (builtins.hasAttr name) modules
 | 
			
		||||
        )
 | 
			
		||||
      )
 | 
			
		||||
    ) funs;
 | 
			
		||||
 | 
			
		||||
  moduleMerge = path: modules:
 | 
			
		||||
    let modules_ = modules; in
 | 
			
		||||
    let
 | 
			
		||||
      addName = name:
 | 
			
		||||
        if path == "" then name else path + "." + name;
 | 
			
		||||
 | 
			
		||||
      modules = map delayModule modules_;
 | 
			
		||||
 | 
			
		||||
      modulesOf = name: filterModules name modules;
 | 
			
		||||
      declarationsOf = name: filter (m: m ? options) (modulesOf name);
 | 
			
		||||
      definitionsOf  = name: filter (m: m ? config ) (modulesOf name);
 | 
			
		||||
 | 
			
		||||
      recurseInto = name: modules:
 | 
			
		||||
        moduleMerge (addName name) (modulesOf name);
 | 
			
		||||
 | 
			
		||||
      recurseForOption = name: modules:
 | 
			
		||||
        moduleMerge name (
 | 
			
		||||
          map unifyModuleSyntax modules
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
      errorSource = modules:
 | 
			
		||||
        "The error may comes from the following files:\n" + (
 | 
			
		||||
          lib.concatStringsSep "\n" (
 | 
			
		||||
            map (m:
 | 
			
		||||
              if m ? key then toString m.key else "<unknow location>"
 | 
			
		||||
            ) modules
 | 
			
		||||
          )
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
      eol = "\n";
 | 
			
		||||
 | 
			
		||||
      errDefinedWithoutDeclaration = name:
 | 
			
		||||
        let
 | 
			
		||||
          badModules =
 | 
			
		||||
            filter (m: ! isAttrs m.config)
 | 
			
		||||
              (definitionsOf name);
 | 
			
		||||
        in
 | 
			
		||||
          "${eol
 | 
			
		||||
          }Option '${addName name}' defined without option declaration.${eol
 | 
			
		||||
          }${errorSource badModules}${eol
 | 
			
		||||
          }";
 | 
			
		||||
 | 
			
		||||
      endRecursion =  { options = {}; config = {}; };
 | 
			
		||||
 | 
			
		||||
    in if modules == [] then endRecursion else
 | 
			
		||||
 | 
			
		||||
      lib.fix (result:
 | 
			
		||||
        moduleZip {
 | 
			
		||||
          options = lib.zip (name: values:
 | 
			
		||||
            if any isOption values then
 | 
			
		||||
              addOptionMakeUp
 | 
			
		||||
                { name = addName name; recurseInto = recurseForOption; }
 | 
			
		||||
                (mergeOptionDecls values)
 | 
			
		||||
            else if all isAttrs values then
 | 
			
		||||
              (recurseInto name modules).options
 | 
			
		||||
            else
 | 
			
		||||
              throw "${eol
 | 
			
		||||
                }Unexpected type where option declarations are expected.${eol
 | 
			
		||||
                }${errorSource (declarationsOf name)}${eol
 | 
			
		||||
              }"
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
          config = lib.zipWithNames (modulesNames modules) (name: values:
 | 
			
		||||
            let
 | 
			
		||||
              hasOpt = builtins.hasAttr name result.options;
 | 
			
		||||
              opt = lib.getAttr name result.options;
 | 
			
		||||
 | 
			
		||||
            in if hasOpt && isOption opt then
 | 
			
		||||
              let defs = evalProperties values; in
 | 
			
		||||
              lib.addErrorContext "${eol
 | 
			
		||||
                }while evaluating the option '${addName name}'.${eol
 | 
			
		||||
                }${errorSource (modulesOf name)}${eol
 | 
			
		||||
              }" (
 | 
			
		||||
                opt.apply (
 | 
			
		||||
                  if defs == [] then
 | 
			
		||||
                    if opt ? default then opt.default
 | 
			
		||||
                    else throw "Not defined."
 | 
			
		||||
                  else opt.merge defs
 | 
			
		||||
                )
 | 
			
		||||
              )
 | 
			
		||||
 | 
			
		||||
            else if hasOpt && lib.attrNames opt == [] then
 | 
			
		||||
              throw (errDefinedWithoutDeclaration name)
 | 
			
		||||
 | 
			
		||||
            else if any (v: isOption (rmProperties v)) values then
 | 
			
		||||
              let
 | 
			
		||||
                badModules =
 | 
			
		||||
                  filter (m: isOption m.config)
 | 
			
		||||
                    (definitionsOf name);
 | 
			
		||||
              in
 | 
			
		||||
                throw "${eol
 | 
			
		||||
                  }Option ${addName name} is defined in the configuration section.${eol
 | 
			
		||||
                  }${errorSource badModules}${eol
 | 
			
		||||
                }"
 | 
			
		||||
 | 
			
		||||
            else if all isAttrs values then
 | 
			
		||||
              (recurseInto name modules).config
 | 
			
		||||
            else
 | 
			
		||||
              throw (errDefinedWithoutDeclaration name)
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
        } modules
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  fixMergeModules = initModules: {...}@args:
 | 
			
		||||
    lib.fix (result:
 | 
			
		||||
      # This trick avoid an infinite loop because names of attribute are
 | 
			
		||||
      # know and it is not require to evaluate the result of moduleMerge to
 | 
			
		||||
      # know which attribute are present as argument.
 | 
			
		||||
      let module = { inherit (result) options config; }; in
 | 
			
		||||
 | 
			
		||||
      moduleMerge "" (
 | 
			
		||||
        moduleClosure initModules (args // module)
 | 
			
		||||
      )
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,6 @@ with import ./lists.nix;
 | 
			
		||||
with import ./misc.nix;
 | 
			
		||||
with import ./attrsets.nix;
 | 
			
		||||
with import ./properties.nix;
 | 
			
		||||
with import ./modules.nix;
 | 
			
		||||
 | 
			
		||||
rec {
 | 
			
		||||
 | 
			
		||||
@ -69,22 +68,43 @@ rec {
 | 
			
		||||
          }
 | 
			
		||||
        else opt;
 | 
			
		||||
 | 
			
		||||
      convertOptionsToModules = opt:
 | 
			
		||||
        if opt ? options then
 | 
			
		||||
          opt // {
 | 
			
		||||
            options = map (decl:
 | 
			
		||||
              let module = lib.applyIfFunction decl {}; in
 | 
			
		||||
              if lib.isModule module then
 | 
			
		||||
                decl
 | 
			
		||||
              else
 | 
			
		||||
                arg: { options = lib.applyIfFunction decl arg; }
 | 
			
		||||
            ) opt.options;
 | 
			
		||||
          }
 | 
			
		||||
        else
 | 
			
		||||
          opt;
 | 
			
		||||
 | 
			
		||||
      handleOptionSets = opt:
 | 
			
		||||
        if decl ? type && decl.type.hasOptions then
 | 
			
		||||
          let
 | 
			
		||||
            
 | 
			
		||||
            optionConfig = opts: config:
 | 
			
		||||
               map (f: applyIfFunction f config)
 | 
			
		||||
                 (decl.options ++ [opts]);
 | 
			
		||||
               map (f: lib.applyIfFunction f config)
 | 
			
		||||
                 (opt.options ++ toList opts);
 | 
			
		||||
          in
 | 
			
		||||
            opt // {
 | 
			
		||||
              merge = list:
 | 
			
		||||
                decl.type.iter
 | 
			
		||||
                  (path: opts:
 | 
			
		||||
                     lib.fix (fixableMergeFun (recurseInto path) (optionConfig opts))
 | 
			
		||||
                    (lib.fix
 | 
			
		||||
                      (fixableMergeFun (recurseInto path) (optionConfig opts))
 | 
			
		||||
                    ).config
 | 
			
		||||
                  )
 | 
			
		||||
                  opt.name
 | 
			
		||||
                  (opt.merge list);
 | 
			
		||||
              options = recurseInto (decl.type.docPath opt.name) decl.options;
 | 
			
		||||
              options =
 | 
			
		||||
                let path = decl.type.docPath opt.name; in
 | 
			
		||||
                (lib.fix
 | 
			
		||||
                  (fixableMergeFun (recurseInto path) (optionConfig []))
 | 
			
		||||
                ).options;
 | 
			
		||||
            }
 | 
			
		||||
        else
 | 
			
		||||
          opt;
 | 
			
		||||
@ -99,6 +119,7 @@ rec {
 | 
			
		||||
        # override settings
 | 
			
		||||
        ensureMergeInputType
 | 
			
		||||
        ensureDefaultType
 | 
			
		||||
        convertOptionsToModules
 | 
			
		||||
        handleOptionSets
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
@ -186,121 +207,33 @@ rec {
 | 
			
		||||
    else head list;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  # Handle the traversal of option sets.  All sets inside 'opts' are zipped
 | 
			
		||||
  # and options declaration and definition are separated.  If no option are
 | 
			
		||||
  # declared at a specific depth, then the function recurse into the values.
 | 
			
		||||
  # Other cases are handled by the optionHandler which contains two
 | 
			
		||||
  # functions that are used to defined your goal.
 | 
			
		||||
  # - export is a function which takes two arguments which are the option
 | 
			
		||||
  # and the list of values.
 | 
			
		||||
  # - notHandle is a function which takes the list of values are not handle
 | 
			
		||||
  # by this function.
 | 
			
		||||
  handleOptionSets = optionHandler@{export, notHandle, ...}: path: opts:
 | 
			
		||||
    if all isAttrs opts then
 | 
			
		||||
      lib.zip (attr: opts:
 | 
			
		||||
        let
 | 
			
		||||
          recurseInto = name: attrs:
 | 
			
		||||
            handleOptionSets optionHandler name attrs;
 | 
			
		||||
 | 
			
		||||
          # Compute the path to reach the attribute.
 | 
			
		||||
          name = if path == "" then attr else path + "." + attr;
 | 
			
		||||
 | 
			
		||||
          # Divide the definitions of the attribute "attr" between
 | 
			
		||||
          # declaration (isOption) and definitions (!isOption).
 | 
			
		||||
          test = partition (x: isOption (rmProperties x)) opts;
 | 
			
		||||
          decls = map rmProperties test.right; defs = test.wrong;
 | 
			
		||||
 | 
			
		||||
          # Make the option declaration more user-friendly by adding default
 | 
			
		||||
          # settings and some verifications based on the declaration content
 | 
			
		||||
          # (like type correctness).
 | 
			
		||||
          opt = addOptionMakeUp
 | 
			
		||||
            { inherit name recurseInto; }
 | 
			
		||||
            (mergeOptionDecls decls);
 | 
			
		||||
 | 
			
		||||
          # Return the list of option sets.
 | 
			
		||||
          optAttrs = map delayProperties defs;
 | 
			
		||||
 | 
			
		||||
          # return the list of option values.
 | 
			
		||||
          # Remove undefined values that are coming from evalIf.
 | 
			
		||||
          optValues = evalProperties defs;
 | 
			
		||||
        in
 | 
			
		||||
          if decls == [] then recurseInto name optAttrs
 | 
			
		||||
          else lib.addErrorContext "while evaluating the option ${name}:" (
 | 
			
		||||
            export opt optValues
 | 
			
		||||
          )
 | 
			
		||||
      ) opts
 | 
			
		||||
   else lib.addErrorContext "while evaluating ${path}:" (notHandle opts);
 | 
			
		||||
 | 
			
		||||
  # Merge option sets and produce a set of values which is the merging of
 | 
			
		||||
  # all options declare and defined.  If no values are defined for an
 | 
			
		||||
  # option, then the default value is used otherwise it use the merge
 | 
			
		||||
  # function of each option to get the result.
 | 
			
		||||
  mergeOptionSets =
 | 
			
		||||
    handleOptionSets {
 | 
			
		||||
      export = opt: values:
 | 
			
		||||
        opt.apply (
 | 
			
		||||
          if values == [] then
 | 
			
		||||
            if opt ? default then opt.default
 | 
			
		||||
            else throw "Not defined."
 | 
			
		||||
          else opt.merge values
 | 
			
		||||
        );
 | 
			
		||||
      notHandle = opts: throw "Used without option declaration.";
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
  # Keep all option declarations.
 | 
			
		||||
  filterOptionSets =
 | 
			
		||||
    handleOptionSets {
 | 
			
		||||
      export = opt: values: opt;
 | 
			
		||||
      notHandle = opts: {};
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  fixableMergeFun = merge: f: config:
 | 
			
		||||
    merge (
 | 
			
		||||
      # remove require because this is not an option.
 | 
			
		||||
      map (m: removeAttrs m ["require"]) (
 | 
			
		||||
        # Delay top-level properties like mkIf
 | 
			
		||||
        map delayProperties (
 | 
			
		||||
          # generate the list of option sets.
 | 
			
		||||
          f config
 | 
			
		||||
        )
 | 
			
		||||
      )
 | 
			
		||||
      # generate the list of option sets.
 | 
			
		||||
      f config
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  fixableMergeModules = merge: initModules: {...}@args: config:
 | 
			
		||||
    fixableMergeFun merge (config:
 | 
			
		||||
      # filter the list of option sets.
 | 
			
		||||
      selectDeclsAndDefs (
 | 
			
		||||
        # generate the list of modules from a closure of imports/require
 | 
			
		||||
        # attribtues.
 | 
			
		||||
        moduleClosure initModules (args // { inherit config; })
 | 
			
		||||
      )
 | 
			
		||||
      lib.moduleClosure initModules (args // { inherit config; })
 | 
			
		||||
    ) config;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  fixableDefinitionsOf = initModules: {...}@args:
 | 
			
		||||
    fixableMergeModules (mergeOptionSets "") initModules args;
 | 
			
		||||
    fixableMergeModules (modules: (lib.moduleMerge "" modules).config) initModules args;
 | 
			
		||||
 | 
			
		||||
  fixableDeclarationsOf = initModules: {...}@args:
 | 
			
		||||
    fixableMergeModules (filterOptionSets "") initModules args;
 | 
			
		||||
    fixableMergeModules (modules: (lib.moduleMerge "" modules).options) initModules args;
 | 
			
		||||
 | 
			
		||||
  definitionsOf = initModules: {...}@args:
 | 
			
		||||
    lib.fix (fixableDefinitionsOf initModules args);
 | 
			
		||||
    (lib.fix (module:
 | 
			
		||||
      fixableMergeModules (lib.moduleMerge "") initModules args module.config
 | 
			
		||||
    )).config;
 | 
			
		||||
 | 
			
		||||
  declarationsOf = initModules: {...}@args:
 | 
			
		||||
    lib.fix (fixableDeclarationsOf initModules args);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  fixMergeModules = merge: initModules: {...}@args:
 | 
			
		||||
    lib.fix (fixableMergeModules merge initModules args);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  # old interface.
 | 
			
		||||
  fixOptionSetsFun = merge: {...}@args: initModules: config:
 | 
			
		||||
    fixableMergeModules (merge "") initModules args config;
 | 
			
		||||
 | 
			
		||||
  fixOptionSets = merge: args: initModules:
 | 
			
		||||
    fixMergeModules (merge "") initModules args;
 | 
			
		||||
    (lib.fix (module:
 | 
			
		||||
      fixableMergeModules (lib.moduleMerge "") initModules args module.config
 | 
			
		||||
    )).options;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  # Generate documentation template from the list of option declaration like
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user