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
4 changed files with 209 additions and 108 deletions
|
@ -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…
Reference in a new issue