diff --git a/pkgs/lib/default.nix b/pkgs/lib/default.nix
index 2c8e29cc04b..d94f5b48565 100644
--- a/pkgs/lib/default.nix
+++ b/pkgs/lib/default.nix
@@ -287,12 +287,55 @@ rec {
checker
else condConcat
name (tail (tail list)) checker;
+
+ # Merge sets of attributes and use the function f to merge
+ # attributes values.
+ zip = f: sets:
+ builtins.listToAttrs (map (name: {
+ inherit name;
+ value =
+ f name
+ (map (__getAttr name)
+ (filter (__hasAttr name) sets));
+ }) (concatMap builtins.attrNames sets));
+
+ # divide a list in two depending on the evaluation of a predicate.
+ partition = pred:
+ fold (h: t:
+ if pred h
+ then { right = [h] ++ t.right; wrong = t.wrong; }
+ else { right = t.right; wrong = [h] ++ t.wrong; }
+ ) { right = []; wrong = []; };
+
+ # Take a function and evaluate it with its own returned value.
+ finalReference = f:
+ (rec { result = f result; }).result;
+
+ # flatten a list of sets returned by 'f'.
+ # f : function to evaluate each set.
+ # attr : name of the attribute which contains more values.
+ # default: result if 'x' is empty.
+ # x : list of values that have to be processed.
+ uniqFlattenAttr = f: attr: default: x:
+ if x == []
+ then default
+ else let h = f (head x); t = tail x; in
+ if elem h default
+ then uniqFlattenAttr f attr default t
+ else uniqFlattenAttr f attr (default ++ [h]) (toList (getAttr [attr] [] h) ++ t)
+ ;
+
/* Options. */
-
+
mkOption = attrs: attrs // {_type = "option";};
typeOf = x: if x ? _type then x._type else "";
+ isOption = attrs:
+ __isAttrs attrs
+ && attrs ? _type
+ && attrs._type == "option";
+
addDefaultOptionValues = defs: opts: opts //
builtins.listToAttrs (map (defName:
{ name = defName;
@@ -315,7 +358,67 @@ rec {
else addDefaultOptionValues defValue {};
}
) (builtins.attrNames defs));
-
+
+ mergeDefaultOption = name: 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 mergeAttrs list
+ else throw "Default merge method does not work on '${name}'.";
+
+ mergeEnableOption = name: fold logicalOR false;
+
+ mergeListOption = name: list:
+ if all __isList list then list
+ else throw "${name}: Expect a list.";
+
+ # Merge sets of options and bindings.
+ # noOption: function to call if no option is declared.
+ mergeOptionSets = noOption: path: opts:
+ if all __isAttrs opts then
+ zip (attr: opts:
+ let
+ name = if path == "" then attr else path + "." + attr;
+ defaultOpt = { merge = mergeDefaultOption; };
+ test = partition isOption opts;
+ in
+ if test.right == [] then mergeOptionSets noOption name test.wrong
+ else if tail test.right != [] then throw "Multiple options for '${name}'."
+ else if test.wrong == [] then (head test.right).default
+ else (defaultOpt // head test.right).merge name test.wrong
+ ) opts
+ else noOption path opts;
+
+ # Keep all option declarations and add an attribute "name" inside
+ # each option which contains the path that has to be followed to
+ # access it.
+ filterOptionSets = path: opts:
+ if all __isAttrs opts then
+ zip (attr: opts:
+ let
+ name = if path == "" then attr else path + "." + attr;
+ test = partition isOption opts;
+ in
+ if test.right == []
+ then filterOptionSets name test.wrong
+ else map (x: x // { inherit name; }) test.right
+ ) opts
+ else {};
+
+ # 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.
+ finalReferenceOptionSets = merge: pkgs: opts:
+ let optionSet = final: configFun:
+ if __isFunction configFun then configFun pkgs final
+ else configFun; # backward compatibility.
+ in
+ finalReference (final: merge ""
+ (map (x: removeAttrs x ["require"])
+ (uniqFlattenAttr (optionSet final) "require" [] (toList opts))
+ )
+ );
+
optionAttrSetToDocList = (l: attrs:
(if (getAttr ["_type"] "" attrs) == "option" then
[({
diff --git a/pkgs/test/mkOption/declare.nix b/pkgs/test/mkOption/declare.nix
new file mode 100644
index 00000000000..9e89a1c096d
--- /dev/null
+++ b/pkgs/test/mkOption/declare.nix
@@ -0,0 +1,53 @@
+# sets of small configurations:
+# Each configuration
+rec {
+ # has 2 arguments pkgs and this.
+ configA = pkgs: this: {
+ # Can depends on other configuration
+ require = configB;
+
+ # Defines new options
+ optionA = pkgs.lib.mkOption {
+ # With default values
+ default = false;
+ # And merging functions.
+ merge = pkgs.lib.mergeEnableOption;
+ };
+
+ # Add a new definition to other options.
+ optionB = this.optionA;
+ };
+
+ # Can be used for option header.
+ configB = pkgs: this: {
+ # Can depends on more than one configuration.
+ require = [ configC configD ];
+
+ optionB = pkgs.lib.mkOption {
+ default = false;
+ };
+
+ # Is not obliged to define other options.
+ };
+
+ configC = pkgs: this: {
+ require = [ configA ];
+
+ optionC = pkgs.lib.mkOption {
+ default = false;
+ };
+
+ # Use the default value if it is not overwritten.
+ optionA = this.optionC;
+ };
+
+ # Can also be used as option configuration only.
+ # without any arguments (backward compatibility)
+ configD = {
+ # Is not forced to specify the require attribute.
+
+ # Is not force to make new options.
+ optionA = true;
+ optionD = false;
+ };
+}
diff --git a/pkgs/test/mkOption/keep.nix b/pkgs/test/mkOption/keep.nix
new file mode 100644
index 00000000000..c26064d89f7
--- /dev/null
+++ b/pkgs/test/mkOption/keep.nix
@@ -0,0 +1,11 @@
+let
+ pkgs = import ../../top-level/all-packages.nix {};
+ config = import ./declare.nix;
+in
+ with (pkgs.lib);
+
+ finalReferenceOptionSets
+ filterOptionSets
+ pkgs
+ # List of main configurations.
+ [ config.configB config.configC ]
diff --git a/pkgs/test/mkOption/keep.ref b/pkgs/test/mkOption/keep.ref
new file mode 100644
index 00000000000..a3a051eb48c
--- /dev/null
+++ b/pkgs/test/mkOption/keep.ref
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pkgs/test/mkOption/merge.nix b/pkgs/test/mkOption/merge.nix
new file mode 100644
index 00000000000..0d4b3c1acd1
--- /dev/null
+++ b/pkgs/test/mkOption/merge.nix
@@ -0,0 +1,15 @@
+let
+ pkgs = import ../../top-level/all-packages.nix {};
+ config = import ./declare.nix;
+
+ # Define the handler of unbound options.
+ noOption = name: values:
+ builtins.trace "Attribute named '${name}' does not match any option declaration." values;
+in
+ with (pkgs.lib);
+
+ finalReferenceOptionSets
+ (mergeOptionSets noOption)
+ pkgs
+ # List of main configurations.
+ [ config.configB config.configC ]
diff --git a/pkgs/test/mkOption/merge.ref b/pkgs/test/mkOption/merge.ref
new file mode 100644
index 00000000000..6956f65dbbc
--- /dev/null
+++ b/pkgs/test/mkOption/merge.ref
@@ -0,0 +1,20 @@
+trace: Str("Attribute named 'optionD' does not match any option declaration.",[])
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pkgs/test/mkOption/test.sh b/pkgs/test/mkOption/test.sh
new file mode 100755
index 00000000000..5478846d563
--- /dev/null
+++ b/pkgs/test/mkOption/test.sh
@@ -0,0 +1,9 @@
+#! /bin/sh -e
+
+echo 1>&2 "Test: Merge of option bindings."
+nix-instantiate merge.nix --eval-only --strict --xml >& merge.out
+diff merge.ref merge.out
+
+echo 1>&2 "Test: Filter of option declarations."
+nix-instantiate keep.nix --eval-only --strict --xml >& keep.out
+diff keep.ref keep.out