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
|
# !!! don't include everything at top-level; perhaps only the most
|
||||||
# commonly used functions.
|
# commonly used functions.
|
||||||
// trivial // lists // strings // stringsWithDeps // attrsets // sources
|
// 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
|
# Merge sets of attributes and use the function f to merge
|
||||||
# attributes values.
|
# attributes values.
|
||||||
zip = f: sets:
|
zip = f: sets:
|
||||||
|
zipWithNames (concatMap builtins.attrNames sets) f sets;
|
||||||
|
|
||||||
|
zipWithNames = names: f: sets:
|
||||||
builtins.listToAttrs (map (name: {
|
builtins.listToAttrs (map (name: {
|
||||||
inherit name;
|
inherit name;
|
||||||
value =
|
value =
|
||||||
f name
|
f name
|
||||||
(map (__getAttr name)
|
(map (__getAttr name)
|
||||||
(filter (__hasAttr name) sets));
|
(filter (__hasAttr name) sets));
|
||||||
}) (concatMap builtins.attrNames sets));
|
}) names);
|
||||||
|
|
||||||
|
|
||||||
lazyGenericClosure = {startSet, operator}:
|
lazyGenericClosure = {startSet, operator}:
|
||||||
let
|
let
|
||||||
|
|
|
@ -7,6 +7,7 @@ with import ./trivial.nix;
|
||||||
with import ./lists.nix;
|
with import ./lists.nix;
|
||||||
with import ./misc.nix;
|
with import ./misc.nix;
|
||||||
with import ./attrsets.nix;
|
with import ./attrsets.nix;
|
||||||
|
with import ./options.nix;
|
||||||
with import ./properties.nix;
|
with import ./properties.nix;
|
||||||
|
|
||||||
rec {
|
rec {
|
||||||
|
@ -32,6 +33,10 @@ rec {
|
||||||
else
|
else
|
||||||
f;
|
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
|
# Convert module to a set which has imports / options and config
|
||||||
# attributes.
|
# attributes.
|
||||||
unifyModuleSyntax = m:
|
unifyModuleSyntax = m:
|
||||||
|
@ -48,7 +53,7 @@ rec {
|
||||||
getConfig = m:
|
getConfig = m:
|
||||||
removeAttrs (delayProperties m) ["require"];
|
removeAttrs (delayProperties m) ["require"];
|
||||||
in
|
in
|
||||||
if m ? config || m ? options then
|
if isModule m then
|
||||||
m
|
m
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -88,4 +93,165 @@ rec {
|
||||||
[ m ]
|
[ m ]
|
||||||
) modules;
|
) 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 ./misc.nix;
|
||||||
with import ./attrsets.nix;
|
with import ./attrsets.nix;
|
||||||
with import ./properties.nix;
|
with import ./properties.nix;
|
||||||
with import ./modules.nix;
|
|
||||||
|
|
||||||
rec {
|
rec {
|
||||||
|
|
||||||
|
@ -69,22 +68,43 @@ rec {
|
||||||
}
|
}
|
||||||
else opt;
|
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:
|
handleOptionSets = opt:
|
||||||
if decl ? type && decl.type.hasOptions then
|
if decl ? type && decl.type.hasOptions then
|
||||||
let
|
let
|
||||||
|
|
||||||
optionConfig = opts: config:
|
optionConfig = opts: config:
|
||||||
map (f: applyIfFunction f config)
|
map (f: lib.applyIfFunction f config)
|
||||||
(decl.options ++ [opts]);
|
(opt.options ++ toList opts);
|
||||||
in
|
in
|
||||||
opt // {
|
opt // {
|
||||||
merge = list:
|
merge = list:
|
||||||
decl.type.iter
|
decl.type.iter
|
||||||
(path: opts:
|
(path: opts:
|
||||||
lib.fix (fixableMergeFun (recurseInto path) (optionConfig opts))
|
(lib.fix
|
||||||
|
(fixableMergeFun (recurseInto path) (optionConfig opts))
|
||||||
|
).config
|
||||||
)
|
)
|
||||||
opt.name
|
opt.name
|
||||||
(opt.merge list);
|
(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
|
else
|
||||||
opt;
|
opt;
|
||||||
|
@ -99,6 +119,7 @@ rec {
|
||||||
# override settings
|
# override settings
|
||||||
ensureMergeInputType
|
ensureMergeInputType
|
||||||
ensureDefaultType
|
ensureDefaultType
|
||||||
|
convertOptionsToModules
|
||||||
handleOptionSets
|
handleOptionSets
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -186,121 +207,33 @@ rec {
|
||||||
else head list;
|
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:
|
fixableMergeFun = merge: f: config:
|
||||||
merge (
|
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.
|
# generate the list of option sets.
|
||||||
f config
|
f config
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
fixableMergeModules = merge: initModules: {...}@args: config:
|
fixableMergeModules = merge: initModules: {...}@args: config:
|
||||||
fixableMergeFun merge (config:
|
fixableMergeFun merge (config:
|
||||||
# filter the list of option sets.
|
lib.moduleClosure initModules (args // { inherit config; })
|
||||||
selectDeclsAndDefs (
|
|
||||||
# generate the list of modules from a closure of imports/require
|
|
||||||
# attribtues.
|
|
||||||
moduleClosure initModules (args // { inherit config; })
|
|
||||||
)
|
|
||||||
) config;
|
) config;
|
||||||
|
|
||||||
|
|
||||||
fixableDefinitionsOf = initModules: {...}@args:
|
fixableDefinitionsOf = initModules: {...}@args:
|
||||||
fixableMergeModules (mergeOptionSets "") initModules args;
|
fixableMergeModules (modules: (lib.moduleMerge "" modules).config) initModules args;
|
||||||
|
|
||||||
fixableDeclarationsOf = initModules: {...}@args:
|
fixableDeclarationsOf = initModules: {...}@args:
|
||||||
fixableMergeModules (filterOptionSets "") initModules args;
|
fixableMergeModules (modules: (lib.moduleMerge "" modules).options) initModules args;
|
||||||
|
|
||||||
definitionsOf = initModules: {...}@args:
|
definitionsOf = initModules: {...}@args:
|
||||||
lib.fix (fixableDefinitionsOf initModules args);
|
(lib.fix (module:
|
||||||
|
fixableMergeModules (lib.moduleMerge "") initModules args module.config
|
||||||
|
)).config;
|
||||||
|
|
||||||
declarationsOf = initModules: {...}@args:
|
declarationsOf = initModules: {...}@args:
|
||||||
lib.fix (fixableDeclarationsOf initModules args);
|
(lib.fix (module:
|
||||||
|
fixableMergeModules (lib.moduleMerge "") initModules args module.config
|
||||||
|
)).options;
|
||||||
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;
|
|
||||||
|
|
||||||
|
|
||||||
# Generate documentation template from the list of option declaration like
|
# Generate documentation template from the list of option declaration like
|
||||||
|
|
Loading…
Reference in a new issue