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:
Nicolas Pierron 2009-09-15 13:36:30 +00:00
parent 07ed9e4611
commit 0c16b00cbd
4 changed files with 209 additions and 108 deletions

View file

@ -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

View file

@ -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

View file

@ -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)
)
);
}

View file

@ -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