summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--pkgs/lib/options.nix412
1 files changed, 364 insertions, 48 deletions
diff --git a/pkgs/lib/options.nix b/pkgs/lib/options.nix
index 6d8bf4633de4..cde0b6df34cc 100644
--- a/pkgs/lib/options.nix
+++ b/pkgs/lib/options.nix
@@ -3,7 +3,9 @@
 let lib = import ./default.nix; in
 
 with { inherit (builtins) head tail; };
+with import ./trivial.nix;
 with import ./lists.nix;
+with import ./misc.nix;
 with import ./attrsets.nix;
 
 rec {
@@ -11,7 +13,8 @@ rec {
 
   mkOption = attrs: attrs // {_type = "option";};
 
-  typeOf = x: if (__isAttrs x && x ? _type) then x._type else "";
+  hasType = x: __isAttrs x && x ? _type;
+  typeOf = x: if hasType x then x._type else "";
 
   isOption = attrs: (typeOf attrs) == "option";
 
@@ -97,11 +100,11 @@ rec {
           } // (head decls);
 
           # Return the list of option sets.
-          optAttrs = map delayIf defs;
+          optAttrs = map delayProperties defs;
 
           # return the list of option values.
           # Remove undefined values that are coming from evalIf.
-          optValues = filter (x: !isNotdef x) (map evalIf defs);
+          optValues = evalProperties defs;
         in
           if decls == [] then handleOptionSets optionHandler name optAttrs
           else lib.addErrorContext "while evaluating the option ${name}:" (
@@ -150,9 +153,9 @@ rec {
     let
       # remove possible mkIf to access the require attribute.
       noImportConditions = cfgSet0:
-        let cfgSet1 = delayIf cfgSet0; in
+        let cfgSet1 = delayProperties cfgSet0; in
         if cfgSet1 ? require then
-          cfgSet1 // { require = rmIf cfgSet1.require; }
+          cfgSet1 // { require = rmProperties cfgSet1.require; }
         else
           cfgSet1;
 
@@ -176,12 +179,39 @@ rec {
             cfg2 = noImportConditions cfg1;
         in cfg2;
 
-      getRequire = x: toList (getAttr ["require"] [] (preprocess x));
+      getRequire = x:
+        toList (getAttr ["require"] [] (preprocess x));
+      getCleanRequire = x: map rmProperties (getRequire x);
       rmRequire = x: removeAttrs (preprocess x) ["require"];
+
+      duplicateIncludeProperties = list:
+        # iterate on all configurations
+        fold (cfg: l:
+          # iterate on all imported configuration from cfg
+          fold (include: l:
+            # clean up the included cfg to get the same result
+            let includedCfg = rmProperties include; in
+            # if the include has properties
+            if include != includedCfg then
+              # iterate on all configurations
+              map (cfg:
+                # if the imported configuration is seen
+                if (rmProperties cfg) == includedCfg then
+                  # copy the properties from the import to the configuration.
+                  delayProperties (copyProperties include cfg)
+                else
+                  cfg
+              ) l
+            else
+              l
+          ) l (getRequire cfg)
+        ) list list;
     in
       merge "" (
         map rmRequire (
-          lib.uniqFlatten getRequire [] [] (toList opts)
+          duplicateIncludeProperties (
+            lib.uniqFlatten getCleanRequire [] [] (toList opts)
+          )
         )
       );
 
@@ -203,65 +233,351 @@ rec {
         (l + (if l=="" then "" else ".") + s) (builtins.getAttr s attrs)))
         (builtins.attrNames attrs)));
 
-        
+  /* Option Properties */
+  # Generalize the problem of delayable properties.  Any property can be created
+
+
+  # Tell that nothing is defined.  When properties are evaluated, this type
+  # is used to remove an entry.  Thus if your property evaluation semantic
+  # implies that you have to mute the content of an attribute, then your
+  # property should produce this value.
+  isNotdef = attrs: (typeOf attrs) == "notdef";
+  mkNotdef = {_type = "notdef";};
+
+  # General property type, it has a property attribute and a content
+  # attribute.  The property attribute refer to an attribute set which
+  # contains a _type attribute and a list of functions which are used to
+  # evaluate this property.  The content attribute is used to stack property
+  # on top of each other.
+  # 
+  # The optional functions which may be contained in the property attribute
+  # are:
+  #  - onDelay: run on a copied property.
+  #  - onGlobalDelay: run on all copied properties.
+  #  - onEval: run on an evaluated property.
+  #  - onGlobalEval: run on a list of property stack on top of their values.
+  isProperty = attrs: (typeOf attrs) == "property";
+  mkProperty = p@{property, content, ...}: p // {
+    _type = "property";
+  };
+
+  # Go throw the stack of properties and apply the function `op' on all
+  # property and call the function `nul' on the final value which is not a
+  # property.  The stack is traversed in reversed order.  The `op' function
+  # should expect a property with a content which have been modified.
+  # 
+  # Warning: The `op' function expects only one argument in order to avoid
+  # calls to mkProperties as the argument is already a valid property which
+  # contains the result of the folding inside the content attribute.
+  foldProperty = op: nul: attrs:
+    if isProperty attrs then
+      op (attrs // {
+        content = foldProperty op nul attrs.content;
+      })
+    else
+      nul attrs;
+
+  # Simple function which can be used as the `op' argument of the
+  # foldProperty function.  Properties that you don't want to handle can be
+  # ignored with the `id' function.  `isSearched' is a function which should
+  # check the type of a property and return a boolean value.  `thenFun' and
+  # `elseFun' are functions which behave as the `op' argument of the
+  # foldProperty function.
+  foldFilter = isSearched: thenFun: elseFun: attrs:
+    if isSearched attrs.property then
+      thenFun attrs
+    else
+      elseFun attrs;
+
+
+  # Move properties from the current attribute set to the attribute
+  # contained in this attribute set.  This trigger property handlers called
+  # `onDelay' and `onGlobalDelay'.
+  delayProperties = attrs:
+    let cleanAttrs = rmProperties attrs; in
+    if cleanAttrs != attrs then
+      lib.mapAttrs (a: v:
+        lib.addErrorContext "while moving properties on the attribute `${a}'." (
+          triggerPropertiesGlobalDelay a (
+            triggerPropertiesDelay a (
+              copyProperties attrs v
+      )))) cleanAttrs
+    else
+      attrs;
+
+  # Call onDelay functions.
+  triggerPropertiesDelay = name: attrs:
+    let
+      callOnDelay = p@{property, ...}:
+        lib.addErrorContext "while calling a onDelay function." (
+          if property ? onDelay then
+            property.onDelay name p
+          else
+            p
+        );
+    in
+      foldProperty callOnDelay id attrs;
+
+  # Call onGlobalDelay functions.
+  triggerPropertiesGlobalDelay = name: attrs:
+    let
+      globalDelayFuns = uniqListExt {
+        getter = property: property._type;
+        inputList = foldProperty (p@{property, content, ...}:
+          if property ? onGlobalDelay then
+            [ property ] ++ content
+          else
+            content
+        ) (a: []) attrs;
+      };
+
+      callOnGlobalDelay = property: content:
+        lib.addErrorContext "while calling a onGlobalDelay function." (
+          property.onGlobalDelay name content
+        );
+    in
+      fold callOnGlobalDelay attrs globalDelayFuns;
+
+  # Expect a list of values which may have properties and return the same
+  # list of values where all properties have been evaluated and where all
+  # ignored values are removed.  This trigger property handlers called
+  # `onEval' and `onGlobalEval'.
+  evalProperties = valList:
+    if valList != [] then
+      filter (x: !isNotdef x) (
+        lib.addErrorContext "while evaluating properties an attribute." (
+          triggerPropertiesGlobalEval (
+            map triggerPropertiesEval valList
+      )))
+    else
+      valList;
+
+  # Call onEval function
+  triggerPropertiesEval = val:
+    foldProperty (p@{property, ...}:
+      lib.addErrorContext "while calling a onEval function." (
+        if property ? onEval then
+          property.onEval p
+        else
+          p
+      )
+    ) id val;
+
+  # Call onGlobalEval function
+  triggerPropertiesGlobalEval = valList:
+    let
+      globalEvalFuns = uniqListExt {
+        getter = property: property._type;
+        inputList =
+          fold (attrs: list:
+            foldProperty (p@{property, content, ...}:
+              if property ? onGlobalEval then
+                [ property ] ++ content
+              else
+                content
+            ) (a: list) attrs
+          ) [] valList;
+      };
+
+      callOnGlobalEval = property: valList:
+        lib.addErrorContext "while calling a onGlobalEval function." (
+          property.onGlobalEval valList
+        );
+    in
+      fold callOnGlobalEval valList globalEvalFuns;
+
+  # Remove all properties on top of a value and return the value.
+  rmProperties =
+    foldProperty (p@{content, ...}: content) id;
+
+  # Copy properties defined on a value on another value.
+  copyProperties = attrs: newAttrs:
+    foldProperty id (x: newAttrs) 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.
 
+  # Create a "If" property which only contains a condition.
   isIf = attrs: (typeOf attrs) == "if";
-  mkIf = condition: thenelse:
-    if isIf thenelse then
-      mkIf (condition && thenelse.condition) thenelse.thenelse
-    else {
+  mkIf = condition: content: mkProperty {
+    property = {
       _type = "if";
-      inherit condition thenelse;
+      onGlobalDelay = onIfGlobalDelay;
+      onEval = onIfEval;
+      inherit condition;
     };
+    inherit content;
+  };
 
-
-  isNotdef = attrs: (typeOf attrs) == "notdef";
-  mkNotdef = {_type = "notdef";};
-
-
+  # Create a "ThenElse" property which contains choices which can choosed by
+  # the evaluation of an "If" statement.
   isThenElse = attrs: (typeOf attrs) == "then-else";
   mkThenElse = attrs:
     assert attrs ? thenPart && attrs ? elsePart;
-    attrs // { _type = "then-else"; };
-
+    mkProperty {
+      property = {
+        _type = "then-else";
+        onEval = val: throw "Missing mkIf statement.";
+        inherit (attrs) thenPart elsePart;
+      };
+      content = mkNotdef;
+    };
 
+  # Create an "Always" property remove ignore all "If" statement.
   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
+  mkAlways = value:
+    mkProperty {
+      property = {
+        _type = "always";
+        onEval = p@{content, ...}: content;
+        inherit value;
+      };
+      content = mkNotdef;
+    };
+
+  # Remove all "If" statement defined on a value.
+  rmIf = foldProperty (
+      foldFilter isIf
+        ({content, ...}: content)
+        id
+    ) id;
+
+  # Evaluate the "If" statements when either "ThenElse" or "Always"
+  # statement is encounter.  Otherwise it remove multiple If statement and
+  # replace them by one "If" staement where the condition is the list of all
+  # conditions joined with a "and" operation.
+  onIfGlobalDelay = name: content:
+    let
+      # extract if statements and non-if statements and repectively put them
+      # in the attribute list and attrs.
+      ifProps =
+        foldProperty
+          (foldFilter (p: isIf p || isThenElse p || isAlways p)
+            # then, push the codition inside the list list
+            (p@{property, content, ...}:
+              { inherit (content) attrs;
+                list = [property] ++ content.list;
+              }
+            )
+            # otherwise, add the propertie.
+            (p@{property, content, ...}:
+              { inherit (content) list;
+                attrs = p // { content = content.attrs; };
+              }
+            )
+          )
+          (attrs: { list = []; inherit attrs; })
+          content;
+
+      # compute the list of if statements.
+      evalIf = content: condition: list:
+        if list == [] then
+          mkIf condition content
         else
-          val.elsePart
-      # ignore the condition.
-      else if isAlways val then
-        val.value
-      # otherwise
+          let p = head list; in
+
+          # evaluate the condition.
+          if isThenElse p then
+            if condition then
+              foldProperty (a: p.thenPart) id content
+            else
+              foldProperty (a: p.elsePart) id content
+          # ignore the condition.
+          else if isAlways p then
+            foldProperty (a: p.value) id content
+          # otherwise (isIf)
+          else
+            evalIf content (condition && p.condition) (tail list);
+    in
+      evalIf ifProps.attrs true ifProps.list;
+
+  # Evaluate the condition of the "If" statement to either get the value or
+  # to ignore the value.
+  onIfEval = p@{property, content, ...}:
+    if property.condition then
+      content
+    else
+      mkNotdef;
+
+  /* mkOverride */
+
+  # Create an "Override" statement which allow the user to define
+  # prioprities between values.  The default priority is 100 and the lowest
+  # priorities are kept.  The template argument must reproduce the same
+  # attribute set hierachy to override leaves of the hierarchy.
+  isOverride = attrs: (typeOf attrs) == "override";
+  mkOverride = priority: template: content: mkProperty {
+    property = {
+      _type = "override";
+      onDelay = onOverrideDelay;
+      onGlobalEval = onOverrideGlobalEval;
+      inherit priority template;
+    };
+    inherit content;
+  };
+
+  # Make the template traversal in function of the property traversal.  If
+  # the template define a non-empty attribute set, then the property is
+  # copied only on all mentionned attributes inside the template.
+  # Otherwise, the property is kept on all sub-attribute definitions.
+  onOverrideDelay = name: p@{property, content, ...}:
+    let inherit (property) template; in
+    if builtins.isAttrs template && template != {} then
+      if builtins.hasAttr name template then
+        p // {
+          property = p.property // {
+            template = builtins.getAttr name template;
+          };
+        }
+      # Do not override the attribute \name\
       else
-        f attrs.condition val)
+        content
+    # Override values defined inside the attribute \name\.
     else
-      attrs;
+      p;
 
-  # 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
-  );
+  # Ignore all values which have a higher value of the priority number.
+  onOverrideGlobalEval = valList:
+    let
+      defaultPrio = 100;
+
+      inherit (builtins) lessThan;
+
+      getPrioVal =
+        foldProperty
+          (foldFilter isOverride
+            (p@{property, content, ...}:
+              if lessThan content.priority property.priority then
+                content
+              else
+                content // {
+                  inherit (property) priority;
+                }
+            )
+            (p@{property, content, ...}:
+              content // {
+                value = p // { content = content.value; };
+              }
+            )
+          ) (value: { priority = defaultPrio; inherit value; });
+
+      prioValList = map getPrioVal valList;
+
+      higherPrio = fold (x: y:
+        if lessThan x.priority y then
+          x.priority
+        else
+          y
+      ) defaultPrio prioValList;
+    in
+      map (x:
+        if x.priority == higherPrio then
+          x.value
+        else
+          mkNotdef
+      ) prioValList;
 
 }
\ No newline at end of file