diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index fbbccd8172f67..8a0954c823750 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -394,7 +394,12 @@ checkConfigOutput '^"pkgs\.\\"123\\"\.\\"with\\\\\\"quote\\"\.hello"$' options.p ## specialArgs should work checkConfigOutput '^"foo"$' config.submodule.foo ./declare-submoduleWith-special.nix -## shorthandOnlyDefines config behaves as expected +## onlyDefinesConfig behaves as expected +checkConfigOutput '^true$' config.submodule.config ./declare-submoduleWith-onlydefinesconfig.nix ./define-submoduleWith-onlydefinesconfig.nix +checkConfigOutput '^true$' config.submodule.config ./declare-submoduleWith-onlydefinesconfig.nix ./define-submoduleWith-onlydefinesconfig-path.nix +checkConfigError 'is not of type `boolean' config.submodule.config ./declare-submoduleWith-onlydefinesconfig.nix ./define-submoduleWith-noshorthand.nix + +## shorthandOnlyDefinesConfig behaves as expected checkConfigOutput '^true$' config.submodule.config ./declare-submoduleWith-shorthand.nix ./define-submoduleWith-shorthand.nix checkConfigError 'is not of type `boolean' config.submodule.config ./declare-submoduleWith-shorthand.nix ./define-submoduleWith-noshorthand.nix checkConfigError "In module ..*define-submoduleWith-shorthand.nix., you're trying to define a value of type \`bool'\n\s*rather than an attribute set for the option" config.submodule.config ./declare-submoduleWith-noshorthand.nix ./define-submoduleWith-shorthand.nix diff --git a/lib/tests/modules/declare-submoduleWith-onlydefinesconfig.nix b/lib/tests/modules/declare-submoduleWith-onlydefinesconfig.nix new file mode 100644 index 0000000000000..2101d1c8237f0 --- /dev/null +++ b/lib/tests/modules/declare-submoduleWith-onlydefinesconfig.nix @@ -0,0 +1,21 @@ +{ lib, ... }: +let + sub = { + # An option named `config`; reason to have `onlyDefinesConfig = true;` + options.config = lib.mkOption { + type = lib.types.bool; + default = false; + }; + config._module.args.bar = true; + }; +in +{ + options.submodule = lib.mkOption { + type = lib.types.submoduleWith { + modules = [ sub ]; + specialArgs.foo = true; + onlyDefinesConfig = true; + }; + default = { }; + }; +} diff --git a/lib/tests/modules/define-submoduleWith-onlydefinesconfig-path-file.nix b/lib/tests/modules/define-submoduleWith-onlydefinesconfig-path-file.nix new file mode 100644 index 0000000000000..abf58534ecd12 --- /dev/null +++ b/lib/tests/modules/define-submoduleWith-onlydefinesconfig-path-file.nix @@ -0,0 +1,4 @@ +{ foo, bar, ... }: +{ + config = foo && bar; +} diff --git a/lib/tests/modules/define-submoduleWith-onlydefinesconfig-path.nix b/lib/tests/modules/define-submoduleWith-onlydefinesconfig-path.nix new file mode 100644 index 0000000000000..5fac10cf444ac --- /dev/null +++ b/lib/tests/modules/define-submoduleWith-onlydefinesconfig-path.nix @@ -0,0 +1,3 @@ +{ + submodule = ./define-submoduleWith-onlydefinesconfig-path-file.nix; +} diff --git a/lib/tests/modules/define-submoduleWith-onlydefinesconfig.nix b/lib/tests/modules/define-submoduleWith-onlydefinesconfig.nix new file mode 100644 index 0000000000000..795834be31d7f --- /dev/null +++ b/lib/tests/modules/define-submoduleWith-onlydefinesconfig.nix @@ -0,0 +1,7 @@ +{ + submodule = + { foo, bar, ... }: + { + config = foo && bar; + }; +} diff --git a/lib/types.nix b/lib/types.nix index 6b51f9254a00c..cb16cf2db518b 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -1184,6 +1184,7 @@ let { modules, specialArgs ? { }, + onlyDefinesConfig ? false, shorthandOnlyDefinesConfig ? false, description ? null, class ? null, @@ -1195,11 +1196,21 @@ let defs: map ( { value, file }: - if isAttrs value && shorthandOnlyDefinesConfig then + if isAttrs value && (onlyDefinesConfig || shorthandOnlyDefinesConfig) then { _file = file; config = value; } + else if isFunction value && onlyDefinesConfig then + lib.setFunctionArgs (args: { + _file = file; + config = value args; + }) (lib.functionArgs value) + else if builtins.isPath value && onlyDefinesConfig then + lib.setFunctionArgs (args: { + _file = file; + config = import value args; + }) (lib.functionArgs (import value)) else { _file = file; @@ -1273,12 +1284,10 @@ let getSubOptions = prefix: let - docsEval = ( - base.extendModules { - inherit prefix; - modules = [ noCheckForDocsModule ]; - } - ); + docsEval = base.extendModules { + inherit prefix; + modules = [ noCheckForDocsModule ]; + }; # Intentionally shadow the freeformType from the possibly *checked* # configuration. See `noCheckForDocsModule` comment. inherit (docsEval._module) freeformType; @@ -1309,6 +1318,7 @@ let modules class specialArgs + onlyDefinesConfig shorthandOnlyDefinesConfig description ; @@ -1334,6 +1344,15 @@ let lhs.specialArgs // rhs.specialArgs else throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\""; + onlyDefinesConfig = + if lhs.onlyDefinesConfig == null then + rhs.onlyDefinesConfig + else if rhs.onlyDefinesConfig == null then + lhs.onlyDefinesConfig + else if lhs.onlyDefinesConfig == rhs.onlyDefinesConfig then + lhs.onlyDefinesConfig + else + throw "A submoduleWith option is declared multiple times with conflicting onlyDefinesConfig values"; shorthandOnlyDefinesConfig = if lhs.shorthandOnlyDefinesConfig == null then rhs.shorthandOnlyDefinesConfig diff --git a/nixos/doc/manual/development/option-types.section.md b/nixos/doc/manual/development/option-types.section.md index b7b9f04acd95e..8f494e9e93e6e 100644 --- a/nixos/doc/manual/development/option-types.section.md +++ b/nixos/doc/manual/development/option-types.section.md @@ -251,7 +251,7 @@ Submodules are detailed in [Submodule](#section-option-types-submodule). options. This is equivalent to `types.submoduleWith { modules = toList o; shorthandOnlyDefinesConfig = true; }`. -`types.submoduleWith` { *`modules`*, *`specialArgs`* ? {}, *`shorthandOnlyDefinesConfig`* ? false } +`types.submoduleWith` { *`modules`*, *`specialArgs`* ? {}, *`onlyDefinesConfig`* ? false, *`shorthandOnlyDefinesConfig`* ? false } : Like `types.submodule`, but more flexible and with better defaults. It has parameters @@ -274,6 +274,12 @@ Submodules are detailed in [Submodule](#section-option-types-submodule). because `lib` itself is used to define `_module.args`, which makes using `_module.args` to define it impossible. + - *`onlyDefinesConfig`* Whether definitions of this type should + always default to the `config` section of a module. In contrast to + `shorthandOnlyDefinesConfig`, this applies to all definition values, + including functions and paths. When a value is a function, it is invoked + with the same arguments as the module itself. + - *`shorthandOnlyDefinesConfig`* Whether definitions of this type should default to the `config` section of a module (see [Example: Structure of NixOS Modules](#ex-module-syntax)) @@ -290,6 +296,8 @@ Submodules are detailed in [Submodule](#section-option-types-submodule). With this option enabled, defining a non-`config` section requires using a function: `the-submodule = { ... }: { options = { ... }; }`. + Note that this behavior does *not* apply to [path values](https://nix.dev/manual/nix/latest/language/types.html#type-path), + similar to how it does not apply to [function values](https://nix.dev/manual/nix/latest/language/types.html#type-function). `types.deferredModule`