267 lines
8.7 KiB
Nix
267 lines
8.7 KiB
Nix
|
# Nixpkgs/NixOS option handling.
|
||
|
|
||
|
let lib = import ./default.nix; in
|
||
|
|
||
|
with { inherit (builtins) head tail; };
|
||
|
with import ./lists.nix;
|
||
|
with import ./attrsets.nix;
|
||
|
|
||
|
rec {
|
||
|
|
||
|
|
||
|
mkOption = attrs: attrs // {_type = "option";};
|
||
|
|
||
|
typeOf = x: if (__isAttrs x && x ? _type) then x._type else "";
|
||
|
|
||
|
isOption = attrs: (typeOf attrs) == "option";
|
||
|
|
||
|
addDefaultOptionValues = defs: opts: opts //
|
||
|
builtins.listToAttrs (map (defName:
|
||
|
{ name = defName;
|
||
|
value =
|
||
|
let
|
||
|
defValue = builtins.getAttr defName defs;
|
||
|
optValue = builtins.getAttr defName opts;
|
||
|
in
|
||
|
if typeOf defValue == "option"
|
||
|
then
|
||
|
# `defValue' is an option.
|
||
|
if builtins.hasAttr defName opts
|
||
|
then builtins.getAttr defName opts
|
||
|
else defValue.default
|
||
|
else
|
||
|
# `defValue' is an attribute set containing options.
|
||
|
# So recurse.
|
||
|
if builtins.hasAttr defName opts && builtins.isAttrs optValue
|
||
|
then addDefaultOptionValues defValue optValue
|
||
|
else addDefaultOptionValues defValue {};
|
||
|
}
|
||
|
) (builtins.attrNames defs));
|
||
|
|
||
|
mergeDefaultOption = list:
|
||
|
if list != [] && tail list == [] then head list
|
||
|
else if all __isFunction list then x: mergeDefaultOption (map (f: f x) list)
|
||
|
else if all __isList list then concatLists list
|
||
|
else if all __isAttrs list then fold lib.mergeAttrs {} list
|
||
|
else if all (x: true == x || false == x) list then fold lib.or false list
|
||
|
else if all (x: x == toString x) list then lib.concatStrings list
|
||
|
else throw "Cannot merge values.";
|
||
|
|
||
|
mergeTypedOption = typeName: predicate: merge: list:
|
||
|
if all predicate list then merge list
|
||
|
else throw "Expect a ${typeName}.";
|
||
|
|
||
|
mergeEnableOption = mergeTypedOption "boolean"
|
||
|
(x: true == x || false == x) (fold lib.or false);
|
||
|
|
||
|
mergeListOption = mergeTypedOption "list"
|
||
|
__isList concatLists;
|
||
|
|
||
|
mergeStringOption = mergeTypedOption "string"
|
||
|
(x: if builtins ? isString then builtins.isString x else x + "")
|
||
|
lib.concatStrings;
|
||
|
|
||
|
mergeOneOption = list:
|
||
|
if list == [] then abort "This case should never happens."
|
||
|
else if tail list != [] then throw "Multiple definitions. Only one is allowed for this option."
|
||
|
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
|
||
|
# 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 isOption opts;
|
||
|
decls = test.right; defs = test.wrong;
|
||
|
|
||
|
# Return the option declaration and add missing default
|
||
|
# attributes.
|
||
|
opt = {
|
||
|
inherit name;
|
||
|
merge = mergeDefaultOption;
|
||
|
apply = lib.id;
|
||
|
} // (head decls);
|
||
|
|
||
|
# Return the list of option sets.
|
||
|
optAttrs = map delayIf defs;
|
||
|
|
||
|
# return the list of option values.
|
||
|
# Remove undefined values that are coming from evalIf.
|
||
|
optValues = filter (x: !isNotdef x) (map evalIf defs);
|
||
|
in
|
||
|
if decls == [] then handleOptionSets optionHandler name optAttrs
|
||
|
else lib.addErrorContext "while evaluating the option ${name}:" (
|
||
|
if tail decls != [] then throw "Multiple options."
|
||
|
else 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 = noOption: newMergeOptionSets; # ignore argument
|
||
|
newMergeOptionSets =
|
||
|
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: {};
|
||
|
};
|
||
|
|
||
|
# Evaluate a list of option sets that would be merged with the
|
||
|
# function "merge" which expects two arguments. The attribute named
|
||
|
# "require" is used to imports option declarations and bindings.
|
||
|
#
|
||
|
# * cfg[0-9]: configuration
|
||
|
# * cfgSet[0-9]: configuration set
|
||
|
#
|
||
|
# merge: the function used to merge options sets.
|
||
|
# pkgs: is the set of packages available. (nixpkgs)
|
||
|
# opts: list of option sets or option set functions.
|
||
|
# config: result of this evaluation.
|
||
|
fixOptionSetsFun = merge: pkgs: opts: config:
|
||
|
let
|
||
|
# remove possible mkIf to access the require attribute.
|
||
|
noImportConditions = cfgSet0:
|
||
|
let cfgSet1 = delayIf cfgSet0; in
|
||
|
if cfgSet1 ? require then
|
||
|
cfgSet1 // { require = rmIf cfgSet1.require; }
|
||
|
else
|
||
|
cfgSet1;
|
||
|
|
||
|
# call configuration "files" with one of the existing convention.
|
||
|
argumentHandler = cfg:
|
||
|
let
|
||
|
# {..}
|
||
|
cfg0 = cfg;
|
||
|
# {pkgs, config, ...}: {..}
|
||
|
cfg1 = cfg { inherit pkgs config merge; };
|
||
|
# pkgs: config: {..}
|
||
|
cfg2 = cfg {} {};
|
||
|
in
|
||
|
if __isFunction cfg0 then
|
||
|
if builtins.isAttrs cfg1 then cfg1
|
||
|
else builtins.trace "Use '{pkgs, config, ...}:'." cfg2
|
||
|
else cfg0;
|
||
|
|
||
|
preprocess = cfg0:
|
||
|
let cfg1 = argumentHandler cfg0;
|
||
|
cfg2 = noImportConditions cfg1;
|
||
|
in cfg2;
|
||
|
|
||
|
getRequire = x: toList (getAttr ["require"] [] (preprocess x));
|
||
|
rmRequire = x: removeAttrs (preprocess x) ["require"];
|
||
|
in
|
||
|
merge "" (
|
||
|
map rmRequire (
|
||
|
lib.uniqFlatten getRequire [] [] (toList opts)
|
||
|
)
|
||
|
);
|
||
|
|
||
|
fixOptionSets = merge: pkgs: opts:
|
||
|
lib.fix (fixOptionSetsFun merge pkgs opts);
|
||
|
|
||
|
optionAttrSetToDocList = l: attrs:
|
||
|
if (getAttr ["_type"] "" attrs) == "option" then
|
||
|
[({
|
||
|
#inherit (attrs) description;
|
||
|
description = if attrs ? description then attrs.description else
|
||
|
throw ("No description ${toString l} : ${lib.whatis attrs}");
|
||
|
}
|
||
|
// (if attrs ? example then {inherit(attrs) example;} else {} )
|
||
|
// (if attrs ? default then {inherit(attrs) default;} else {} )
|
||
|
// {name = l;}
|
||
|
)]
|
||
|
else (concatLists (map (s: (optionAttrSetToDocList
|
||
|
(l + (if l=="" then "" else ".") + s) (builtins.getAttr s attrs)))
|
||
|
(builtins.attrNames attrs)));
|
||
|
|
||
|
|
||
|
/* If. ThenElse. Always. */
|
||
|
# !!! cleanup needed
|
||
|
|
||
|
# create "if" statement that can be dealyed on sets until a "then-else" or
|
||
|
# "always" set is reached. When an always set is reached the condition
|
||
|
# is ignore.
|
||
|
|
||
|
isIf = attrs: (typeOf attrs) == "if";
|
||
|
mkIf = condition: thenelse:
|
||
|
if isIf thenelse then
|
||
|
mkIf (condition && thenelse.condition) thenelse.thenelse
|
||
|
else {
|
||
|
_type = "if";
|
||
|
inherit condition thenelse;
|
||
|
};
|
||
|
|
||
|
|
||
|
isNotdef = attrs: (typeOf attrs) == "notdef";
|
||
|
mkNotdef = {_type = "notdef";};
|
||
|
|
||
|
|
||
|
isThenElse = attrs: (typeOf attrs) == "then-else";
|
||
|
mkThenElse = attrs:
|
||
|
assert attrs ? thenPart && attrs ? elsePart;
|
||
|
attrs // { _type = "then-else"; };
|
||
|
|
||
|
|
||
|
isAlways = attrs: (typeOf attrs) == "always";
|
||
|
mkAlways = value: { inherit value; _type = "always"; };
|
||
|
|
||
|
pushIf = f: attrs:
|
||
|
if isIf attrs then pushIf f (
|
||
|
let val = attrs.thenelse; in
|
||
|
# evaluate the condition.
|
||
|
if isThenElse val then
|
||
|
if attrs.condition then
|
||
|
val.thenPart
|
||
|
else
|
||
|
val.elsePart
|
||
|
# ignore the condition.
|
||
|
else if isAlways val then
|
||
|
val.value
|
||
|
# otherwise
|
||
|
else
|
||
|
f attrs.condition val)
|
||
|
else
|
||
|
attrs;
|
||
|
|
||
|
# take care otherwise you will have to handle this by hand.
|
||
|
rmIf = pushIf (condition: val: val);
|
||
|
|
||
|
evalIf = pushIf (condition: val:
|
||
|
if condition then val else mkNotdef
|
||
|
);
|
||
|
|
||
|
delayIf = pushIf (condition: val:
|
||
|
# rewrite the condition on sub-attributes.
|
||
|
lib.mapAttrs (name: mkIf condition) val
|
||
|
);
|
||
|
|
||
|
}
|