16 May 2025 |
SomeoneSerge (back on matrix) |
On line 45 you're defining apply which is meaningless in a module, it's a feature of options.
Yeah I was wondering if I misunderstood the "freeformType is an option"
| 22:15:13 |
SomeoneSerge (back on matrix) | Whoops, the snippet is screwed then I guess | 22:15:50 |
Matt Sturgeon | Here's what I meant by freeformType is an option: https://github.com/NixOS/nixpkgs/blob/ec56f7ae8cdc39eebb8a3b4aa7a3a6365c214fb5/lib/modules.nix#L213-L227
You can set it explicitly in a submodule by defining e.g. config._module.freeformType = attrsOf str , but the module system allows a "shorthand" syntax where freeformType can be defined as a top-level attr in a module and it'll do the conversion to assigning the option for you.
| 22:17:34 |
SomeoneSerge (back on matrix) |
Here's what I meant by freeformType is an option:
Was my second guess. Thanks for walking me through these!
| 22:19:58 |
SomeoneSerge (back on matrix) | Does work with mkOption indeed: https://gist.github.com/SomeoneSerge/b278317b83e5090fbfa9472c78701703 | 22:51:20 |
Matt Sturgeon | Awesome!
I'd highly recommend checking the marker_a.isDefined && marker_b.isDefined case too, which I assume should be an error?
The else case where neither are defined may need some special handling too: is it an error? Is it still an error if cfg is empty?
| 22:56:09 |
SomeoneSerge (back on matrix) | Yes, an error. Still struggling a bit conceptually though, seems like there are three different kinds of entities in evalModules : "types" are things that have check s, "modules" are things that produce options and config s, but options are confusing | 23:17:40 |
SomeoneSerge (back on matrix) | It's not possible to extend options._module.freeformType with an apply from outside? | 23:18:12 |
SomeoneSerge (back on matrix) | * It's not possible to extend options._module.freeformType with an apply from outside is it? | 23:18:15 |
SomeoneSerge (back on matrix) | * Yes, an error. Still struggling a bit conceptually though, seems like there are three different kinds of entities in evalModules : "types" are things that have check s, "modules" are things that produce options and config s, but "options" are confusing | 23:18:43 |
Matt Sturgeon | Yes, there's a few "types" of thing in the module system:
configuration is the final evaluated module eval, returned by evalModules . It contains _type , options, config, type` (a submodule option-type), etc
module these are the "modules" the module system evaluates. Can be a long-form or short-hand attrset, a function returning one, or a file containing one.
options these describe the attrs that can be defined in the configuration. Declared using options.foo = mkOption {} , defined using config.foo = "value" (or the short-hand syntax foo = "value" ).
option-types these are types that options can declare they have. This is where type checking and definition merging is done.
Submodules are an option-type representing another configuration . The submodule type expects the option definitions to be modules , and it merges those modules using evalModules and uses the configuration's config as its final value.
| 23:24:13 |
Matt Sturgeon | I'm not sure. Usually merging things relates to having multiple definitions for an option, but the module system does also support merging multiple declarations for an option too, in some scenarios.
You may be able to declare options._module.freeformType = mkOption { apply = x: x; } , but IDK why you would want to do that... ?
| 23:26:47 |
SomeoneSerge (back on matrix) | Ah right, I was trying ...freeformType.apply = instead, but I actually knew this bit. But I realized I don't actually need apply in freeformType : I was trying to reproduce the same trick at the top-level instead of at foo. | 23:35:26 |
17 May 2025 |
| oddlama changed their display name from oddlama to Malte. | 20:12:20 |
20 May 2025 |
| kongrooparadox joined the room. | 21:44:52 |
21 May 2025 |
| oddlama changed their display name from Malte to oddlama. | 17:42:04 |
SomeoneSerge (back on matrix) | I see that nixos/lib and nixos/modules use normal path values in _file (except for modulesPath = toString ./eval-config.nix ), but somehow in man configuration.nix we get relative paths without hashes? How does that happen | 17:48:12 |
Matt Sturgeon | path values only get copied to the store when used with string interpolation (e.g. "${./some/path}" ). If you instead stringify them with toString , then they are not copied.
E.g.
"${./foo}"
=> "/nix/store/r1l6z113nnzx44iiqp728f50lpczp577-foo"
toString ./foo
=> "/home/matt/foo"
./foo
=> /home/matt/foo
| 17:53:58 |
SomeoneSerge (back on matrix) | Ohhh so nixos/modules actually does pass strings around and not paths? | 18:00:37 |
SomeoneSerge (back on matrix) | nixos/lib/eval-config.nix
71- pkgsModule = rec {
72: _file = ./eval-config.nix;
73: key = _file;
| 18:02:08 |
SomeoneSerge (back on matrix) | This is 100% a "path" | 18:02:19 |
SomeoneSerge (back on matrix) | $ man configuration.nix
...
nixpkgs.pkgs
...
Declared by:
<nixpkgs/nixos/modules/misc/nixpkgs.nix>
| 18:03:29 |
SomeoneSerge (back on matrix) | So there is some magic happening that makes this ./eval-config.nix into a string that is relative to.... hmmm parent of the checkout | 18:03:57 |
Matt Sturgeon | IIRC specialArgs.modulesPath is removed if it is a prefix of the stringified _file . | 18:05:42 |
Matt Sturgeon |
actually does pass strings around
No, but when rendering files in errors, docs, etc they are stringified using toString | 18:06:40 |
| Spaenny joined the room. | 18:21:49 |
23 May 2025 |
SomeoneSerge (back on matrix) | Btw that magic was in pkgs/ in nixosOptionsDoc | 22:32:42 |
SomeoneSerge (back on matrix) | Next one! Given (evalModules {...}).options and a path in options , is there a ready tool to tell which definition (with location) won the priority? I see I can access definitionsWithLocations which is an unfiltered list, and, to further complicate things, in case of an attrsOf option there's just the top-level list, which doesn't directly relate paths under the option to definitions or locations. | 22:36:26 |
Matt Sturgeon | For simple/flat options, all the definition listed in the option are the ones that "won" the priority.
Specifically, all of opt.definitions and opt.definitionsWithLocations have opt.highestPrio .
That's because most wrappers like mkIf , mkMerge , mkOverride , and mkOrder are resolved before creating the option's definitions list.
However, this is only done for the option's definitions (i.e. the top-level). Wrappers on nested attrs are applied by the option's type when it merges the option's definitions.
I don't know of an "easy" way to filter for definitions of a specific attr on an attrsOf type.
There are some functions like lib.modules.mergeAttrDefinitionsWithPrio , so maybe something similar exists or could be written that exposes definition location for attrsOf sub-attrs?
If you were dealing with a submodule, and had an explicit suboption declared, then you could access the suboption's definitions within the submodule eval. E.g., you could expose them to the wider module eval by defining an internal option in the submodule. | 22:48:40 |
24 May 2025 |
jappie | I'm trying to create a settings option for services.dovecot2 (https://github.com/NixOS/nixpkgs/pull/388463/) and running into some issues trying to use a submodule for the type of the settings option.
Dovecot allows you to do something like this:
# dovecot config format:
service imap-login {
restart_request_count = 1
inet_listener imaps {
port = 993
ssl = yes
}
}
# Nix:
"service imap-login" = {
restart_request_count = 1;
"inet_listener imaps" = {
port = 993;
ssl = true;
};
};
Technically the following works for options.settings.type :
type =
let
inherit (lib.types) bool int listOf nullOr oneOf str submodule;
inherit (lib) elemAt singleton split;
any = oneOf [ int str bool (lib.types.lazyAttrsOf configValueType) ];
configValueType = nullOr (oneOf [
any
(listOf any)
]);
in
configValueType;
But a better solution would be to define the section type & section name (e.g. service & imap-login or inet_listener & imaps from above) in the options part of a submodule instead of hiding it in the parser that writes the config file:
type =
let
inherit (lib.types) attrsOf bool int listOf nullOr oneOf str submodule;
inherit (lib) elemAt singleton split;
section = submodule (
{ name, config, ... }:
let
# split on first space
splits = elemAt (elemAt (split "^([^ ]+)( +(.+))?$" name) 1);
typeDefault = splits 0;
nameDefault = splits 1; # 2;
in
{
options = {
section = {
type = mkOption {
description = "...";
type = str;
default = typeDefault;
};
name = mkOption {
description = "...";
type = nullOr str;
default = nameDefault;
};
};
};
freeformType = configValueType;
}
);
any = oneOf [ int str bool (lib.types.lazyAttrsOf section) ];
configValueType = nullOr (oneOf [
any
(listOf any)
]);
in
configValueType;
However this throws and then infrecs trying to evaluate description :
… from call site
at /nix/store/iarry5sq6zg1wm91f4dn68a91nqs369w-source/nixos/modules/services/mail/dovecot.nix:794:65:
793| }
794| // optionalAttrs (cfg.settings ? mail_uid && cfg.settings.mail_uid != null && cfg.createMailUser) {
| ^
795| ${cfg.settings.mail_uid} =
… while calling anonymous lambda
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:818:27:
817| (
818| name: defs:
| ^
819| let
… while evaluating the attribute 'optionalValue.value'
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/modules.nix:1148:41:
1147|
1148| optionalValue = if isDefined then { value = mergedValue; } else { };
| ^
1149| };
… while calling the 'throw' builtin
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/modules.nix:1139:11:
1138| in
1139| throw "A definition for option `${showOption loc}' is not of type `${type.description}'. Definition values:${showDefs allInvalid}"
| ^
1140| else
… while evaluating the attribute 'description'
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
265| functor;
266| description = if description == null then name else description;
| ^
267| };
… while evaluating description
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:26:
265| functor;
266| description = if description == null then name else description;
| ^
267| };
… while evaluating the attribute '_module.freeformType.description'
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
265| functor;
266| description = if description == null then name else description;
| ^
267| };
… while evaluating description
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:26:
265| functor;
266| description = if description == null then name else description;
| ^
267| };
… from call site
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:1026:13:
1025| description = "null or ${
1026| optionDescriptionPhrase (class: class == "noun" || class == "conjunction") elemType
| ^
1027| }";
… while calling 'optionDescriptionPhrase'
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:290:23:
289| optionDescriptionPhrase =
290| unparenthesize: t:
| ^
291| if unparenthesize (t.descriptionClass or null) then t.description else "(${t.description})";
… while evaluating the attribute 'description'
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
265| functor;
266| description = if description == null then name else description;
| ^
267| };
… while evaluating description
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:26:
265| functor;
266| description = if description == null then name else description;
| ^
267| };
… from call site
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:1383:18:
1382| else
1383| "${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t1} or ${
| ^
1384| optionDescriptionPhrase (
… while calling 'optionDescriptionPhrase'
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:290:23:
289| optionDescriptionPhrase =
290| unparenthesize: t:
| ^
291| if unparenthesize (t.descriptionClass or null) then t.description else "(${t.description})";
… while evaluating the attribute 'description'
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
265| functor;
266| description = if description == null then name else description;
| ^
267| };
… while evaluating description
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:26:
265| functor;
266| description = if description == null then name else description;
| ^
267| };
… from call site
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:1384:17:
1383| "${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t1} or ${
1384| optionDescriptionPhrase (
| ^
1385| class: class == "noun" || class == "conjunction" || class == "composite"
… while calling 'optionDescriptionPhrase'
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:290:23:
289| optionDescriptionPhrase =
290| unparenthesize: t:
| ^
291| if unparenthesize (t.descriptionClass or null) then t.description else "(${t.description})";
… while evaluating the attribute 'description'
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
265| functor;
266| description = if description == null then name else description;
| ^
267| };
… while evaluating description
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:26:
265| functor;
266| description = if description == null then name else description;
| ^
267| };
… from call site
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:808:22:
807| (if lazy then "lazy attribute set" else "attribute set")
808| + " of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
| ^
809| descriptionClass = "composite";
… while calling 'optionDescriptionPhrase'
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:290:23:
289| optionDescriptionPhrase =
290| unparenthesize: t:
| ^
291| if unparenthesize (t.descriptionClass or null) then t.description else "(${t.description})";
… while evaluating the attribute 'description'
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
265| functor;
266| description = if description == null then name else description;
| ^
267| };
error: infinite recursion encountered
at /nix/store/p893dkrzm5rxvhnqh092prgi1a7dzmcy-source/lib/types.nix:266:9:
265| functor;
266| description = if description == null then name else description;
| ^
267| };
Is what I'm trying to do here not possible with submodules due to the recursion or am I missing something?
| 12:06:15 |