From d376fb3c70f4bfea30f12322eb8a5391bcfd5760 Mon Sep 17 00:00:00 2001 From: Ryan Hendrickson Date: Wed, 5 Jun 2024 17:23:10 -0400 Subject: [PATCH] lib.modules: add mkForAllItems --- lib/default.nix | 2 +- lib/modules.nix | 12 +++ lib/tests/modules.sh | 3 + lib/tests/modules/for-all-items.nix | 74 +++++++++++++++++++ lib/types.nix | 33 +++++++-- .../manual/development/option-def.section.md | 35 +++++++++ .../manual/release-notes/rl-2411.section.md | 2 + 7 files changed, 153 insertions(+), 8 deletions(-) create mode 100644 lib/tests/modules/for-all-items.nix diff --git a/lib/default.nix b/lib/default.nix index d5d47defb8e64..6a9c37b0e52a6 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -137,7 +137,7 @@ let mkRenamedOptionModule mkRenamedOptionModuleWith mkMergedOptionModule mkChangedOptionModule mkAliasOptionModule mkDerivedConfig doRename - mkAliasOptionModuleMD; + mkAliasOptionModuleMD mkForAllItems; evalOptionValue = lib.warn "External use of `lib.evalOptionValue` is deprecated. If your use case isn't covered by non-deprecated functions, we'd like to know more and perhaps support your use case well, instead of providing access to these low level functions. In this case please open an issue in https://github.com/nixos/nixpkgs/issues/." self.modules.evalOptionValue; inherit (self.options) isOption mkEnableOption mkSinkUndeclaredOptions mergeDefaultOption mergeOneOption mergeEqualOption mergeUniqueOption diff --git a/lib/modules.nix b/lib/modules.nix index 79892f50c4fe2..9f796d3af1818 100644 --- a/lib/modules.nix +++ b/lib/modules.nix @@ -1036,6 +1036,17 @@ let defaultOrderPriority = 1000; mkAfter = mkOrder 1500; + /* + For use with options of type `attrsOf *`, `lazyAttrsOf *`, or `listOf *`. + Accepts a value that is merged with every element defined elsewhere. May + also accept a function that takes the attribute name or index (1-based), + the result of which is used as above. + */ + mkForAllItems = content: + { _type = "forAllItems"; + content = if isFunction content then content else _: content; + }; + # Convenient property used to transfer all definitions and their # properties from one option to another. This property is useful for # renaming options, and also for including properties from another module @@ -1421,6 +1432,7 @@ private // mkDefault mkDerivedConfig mkFixStrictness + mkForAllItems mkForce mkIf mkImageMediaOverride diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index 750b1d025e026..df92360c38c0e 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -513,6 +513,9 @@ checkConfigOutput '^34|23$' options.submoduleLine34.declarationPositions.1.line # nested options work checkConfigOutput '^30$' options.nested.nestedLine30.declarationPositions.0.line ./declaration-positions.nix +# mkForAllItems +checkConfigOutput '^true$' config.result ./for-all-items.nix + cat <"]); getSubModules = elemType.getSubModules; @@ -605,13 +618,19 @@ rec { descriptionClass = "composite"; check = isAttrs; merge = loc: defs: + let + # partitionedDefs.right: all forAllItems definitions + # partitionedDefs.wrong: all regular definitions + partitionedDefs = partition (d: d.value._type or "" == "forAllItems") defs; + extraDefs = name: map (def: { inherit (def) file; value = def.value.content name; }) partitionedDefs.right; + in zipAttrsWith (name: defs: - let merged = mergeDefinitions (loc ++ [name]) elemType defs; + let merged = mergeDefinitions (loc ++ [name]) elemType (defs ++ extraDefs name); # mergedValue will trigger an appropriate error when accessed in merged.optionalValue.value or elemType.emptyValue.value or merged.mergedValue ) # Push down position info. - (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs); + (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) partitionedDefs.wrong); emptyValue = { value = {}; }; getSubOptions = prefix: elemType.getSubOptions (prefix ++ [""]); getSubModules = elemType.getSubModules; diff --git a/nixos/doc/manual/development/option-def.section.md b/nixos/doc/manual/development/option-def.section.md index 227f41d812ff1..f4220a02c4c00 100644 --- a/nixos/doc/manual/development/option-def.section.md +++ b/nixos/doc/manual/development/option-def.section.md @@ -123,3 +123,38 @@ they were declared in separate modules. This can be done using ]; } ``` + +## Defining Over All Elements/Attributes {#sec-option-definitions-for-all-items} + +For options that take lists or attrsets, it may be useful to define +defaults or overrides on all of the elements or attributes of that option +independently of the population of the list or set. For example, one +module can define a list of `swapDevices`, while another module can +define that for every element of the list of `swapDevices`, +`swapDevices.*.discardPolicy` should have a particular value. This can be +done using `mkForAllItems`: + +```nix +{ + swapDevices = mkForAllItems { + discardPolicy = mkDefault "once"; + }; +} +``` + +The argument to `mkForAllItems` will be merged with every element of the +list, or every attribute of the attrset. As usual, use `mkDefault`, +`mkForce`, `mkBefore`, `mkAfter`, etc. to control how multiple +definitions of an option are combined. + +`mkForAllItems` can also accept a function which will receive the element +index (starting at 1) or attribute name for each element or attribute on +which it is used. The result of this function is used as described above. For example: + +```nix +{ + services.wordpress.sites = mkForAllItems (name: { + uploadsDir = mkDefault "/mnt/wordpress-data/${name}/uploads"; + }); +} +``` diff --git a/nixos/doc/manual/release-notes/rl-2411.section.md b/nixos/doc/manual/release-notes/rl-2411.section.md index d821a6e6cc1ff..105d7d20d0d4a 100644 --- a/nixos/doc/manual/release-notes/rl-2411.section.md +++ b/nixos/doc/manual/release-notes/rl-2411.section.md @@ -56,6 +56,8 @@ +- New library function `lib.mkForAllItems` allows for defining values uniformly on lists and attrsets; see [documentation](#sec-option-definitions-for-all-items). + - To facilitate dependency injection, the `imgui` package now builds a static archive using vcpkg' CMake rules. The derivation now installs "impl" headers selectively instead of by a wildcard. Use `imgui.src` if you just want to access the unpacked sources.