diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index 943deebe3c093..05028bc42a90a 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -233,6 +233,22 @@ checkConfigError 'infinite recursion encountered' config.foo ./freeform-attrsOf. checkConfigError 'The option .* is used but not defined' config.foo ./freeform-lazyAttrsOf.nix ./freeform-unstr-dep-str.nix checkConfigOutput 24 config.foo ./freeform-lazyAttrsOf.nix ./freeform-unstr-dep-str.nix ./define-value-string.nix +## orderOf +# Check whether two elements with after/before are ordered correctly +checkConfigOutput e config.value.0.data ./order/single-option.nix ./order/orderable.nix +checkConfigOutput d config.value.1.data ./order/single-option.nix ./order/orderable.nix +checkConfigOutput c config.value.2.data ./order/single-option.nix ./order/orderable.nix +checkConfigOutput b config.value.3.data ./order/single-option.nix ./order/orderable.nix +checkConfigOutput a config.value.4.data ./order/single-option.nix ./order/orderable.nix +# Check that a cycle throws an error +checkConfigError 'Cycle detected when trying to order option .*: a -> e -> d -> b -> a' config.value ./order/single-option.nix ./order/cycle.nix +# Check that multiple orderOf declarations can be merged +checkConfigOutput e config.value.0.data ./order/multiple-options.nix ./order/orderable.nix +checkConfigOutput d config.value.1.data ./order/multiple-options.nix ./order/orderable.nix +checkConfigOutput c config.value.2.data ./order/multiple-options.nix ./order/orderable.nix +checkConfigOutput b config.value.3.data ./order/multiple-options.nix ./order/orderable.nix +checkConfigOutput a config.value.4.data ./order/multiple-options.nix ./order/orderable.nix + cat < " (x: x.name) (reverseList (cycle ++ [ (head cycle) ])); + in throw "Cycle detected when trying to order option `${showOption loc}': ${showCycle sortedEntries.cycle}"; + in if sortedEntries ? result then map (v: v.value) sortedEntries.result else cycleError; + getSubOptions = elemType.getSubOptions; + getSubModules = elemType.getSubModules; + substSubModules = m: orderOf { inherit before; elemType = elemType.substSubModules m; }; + functor = defaultFunctor name // { + payload = { inherit before elemType; }; + binOp = lhs: rhs: { + before = a: b: lhs.before a b || rhs.before a b; + elemType = lhs.elemType.typeMerge rhs.elemType.functor; + }; + }; + }; + # A version of attrsOf that's lazy in its values at the expense of # conditional definitions not working properly. E.g. defining a value with # `foo.attr = mkIf false 10`, then `foo ? attr == true`, whereas with diff --git a/nixos/doc/manual/development/option-types.xml b/nixos/doc/manual/development/option-types.xml index 5a6dae6e9912e..2d4bf3b5d0493 100644 --- a/nixos/doc/manual/development/option-types.xml +++ b/nixos/doc/manual/development/option-types.xml @@ -360,6 +360,106 @@ + + + types.orderOf { + before ? a: b: false, + elemType } + + + + A set of elements of type elemType, ordered + according to the given partial ordering function before + , equivalent to a + + DAG (directed acyclic graph). This type takes an attribute set + with the following attributes as a parameter: + + + elemType + The type of elements. The orderOf type accepts + the same values as attrsOf elemType accepts. + + before + The partial ordering function. It takes two parameters, each an + attribute set of the form + + + name + + + The attribute name of the entry. + + + + + value + + + The attribute value of the entry. This is of type elemType. + + + + + This function should return true if the first parameter should be ordered before the second one. + + + The result of this type is a list of elemType + items, ordered according to the before function. + If there are multiple possible orderings an arbitrary one of them is chosen. + + <literal>orderOf</literal> Example + Here is a simple example of using this type to order a list of text + entries by allowing them to specify which entries they should come + before of. + +{ lib, ... }: +{ + options.value = lib.mkOption { + type = lib.types.orderOf { + # a should come before b if the value of a specifies { before.<b> = true; } + before = a: b: a.value.before.${b.name} or false; + elemType = lib.types.submodule { + options.text = lib.mkOption { + type = lib.types.str; + }; + options.before = lib.mkOption { + type = lib.types.attrsOf lib.types.bool; + default = {}; + }; + }; + }; + }; + + config.value = { + foo = { + text = "This is foo"; + before.bar = true; + }; + bar = { + text = "This is bar"; + }; + }; +} + + This option will evaluate to + +[ + { + text = "This is foo"; + before = { + bar = true; + }; + } + { + text = "This is bar"; + before = { }; + } +] + + + + types.lazyAttrsOf t