lib.modules: Let module declare options directly in bare submodule#156533
Conversation
dd5a249 to
8040564
Compare
3a25e26 to
5a617d0
Compare
lib/modules.nix
Outdated
There was a problem hiding this comment.
This leads to an error when a users option is declared with just types.submodule:
let
lib = import ./lib;
in lib.evalModules {
modules = [
{
_file = "a.nix";
options.foo = lib.mkOption {
type = lib.types.submodule {};
default = {};
};
}
{
_file = "b.nix";
options.foo.bar = lib.mkOption {
type = lib.types.str;
};
}
];
}gives
error: A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values
(use '--show-trace' to show detailed location information)
Not sure if this can be fixed nicely or if it even makes sense.
There was a problem hiding this comment.
submodule is the legacy one, but yeah, not great. Seems like "don't care" needs to be representable and pick true as the default. false was probably a bad idea.
There was a problem hiding this comment.
Added null support, but only when merging.
|
I wonder if it would be feasible for option trees (like |
It occurred to me too, but it wasn't clear to me whether it will be effective. |
a855279 to
d77b16d
Compare
lib/types.nix
Outdated
There was a problem hiding this comment.
Since null is now an allowed value, we should also define its semantics and document it. The semantics should be that if this value is needed to make a decision and it's null, an error is thrown. This means that if you do any shorthand definitions, you should get an error, unless another option declaration specifies the value of shorthandOnlyDefinesConfig, e.g. whereas
let
lib = import ./lib;
in lib.evalModules {
modules = [
{
options.foo = lib.mkOption {
type = lib.types.submoduleWith {
modules = [];
shorthandOnlyDefinesConfig = null;
};
};
config.foo.bar = 10;
}
];
}with this PR currently throws
error: value is null while a Boolean was expected
at /home/infinisil/src/nixpkgs/lib/types.nix:538:23:
537| then setFunctionArgs (args: unify (value args)) (functionArgs value)
538| else unify (if shorthandOnlyDefinesConfig then { config = value; } else value);
| ^
539|
(use '--show-trace' to show detailed location information)
it should instead be something like
error: shorthandOnlyDefinesConfig is null
(use '--show-trace' to show detailed location information)
There was a problem hiding this comment.
Since
nullis now an allowed value
I disagree. You'd have to read the implementation to find that null is acceptable in some situations.
To keep things simple and, I've decided to default it to true. I don't see much point in documenting this though.
... where a bare submodule is an option that has a type like `submoduleWith x`, as opposed to `attrsOf (submoduleWith x)`. This makes migration unnecessary when introducing a freeform type in an existing option tree. Closes NixOS#146882
This scans the options with fewer function calls, improving performance. It also removes a let Env from the happy flow of the new logic.
This should save about four calls per module.
1d0be4d to
2050669
Compare
lib/modules.nix
Outdated
| # d. magically combine (a) and (c). | ||
| # All of the above are merely syntax sugar though. | ||
| then | ||
| let opt = mergeOptionDecls loc (map optionTreeToOption decls); |
There was a problem hiding this comment.
After a lot of trial and error, trying to prove why fixupOptionType should be used here or not, I figured it out: It's needed for the sake of file locations in error messages. E.g. with
let lib = import ./lib;
in lib.evalModules {
modules = [
{
_file = "a.nix";
options.foo = lib.mkOption {
type = lib.types.submodule {
options.x = lib.mkOption {
type = lib.types.int;
};
};
default = {};
};
}
{
_file = "b.nix";
options.foo.x = lib.mkOption {
type = lib.types.str;
};
}
];
}it currently produces this error:
$ nix-instantiate --eval -A config.foo.x
error: The option `foo.x' in `<unknown-file>' is already declared in `<unknown-file>'.
(use '--show-trace' to show detailed location information)
whereas with a fixupOptionType here, it's
error: The option `foo.x' in `a.nix' is already declared in `b.nix'.
(use '--show-trace' to show detailed location information)
would be good to have a test case for this
| options = mkOption { | ||
| type = types.submoduleWith { | ||
| modules = [ { options = decl.options; } ]; | ||
| shorthandOnlyDefinesConfig = null; |
There was a problem hiding this comment.
This should have a comment explaining the reasoning behind this: How it's a bit hacky but needed as a default, that null is only an internally valid value, that it's only safe here because of the merging function patch and because it's always going to be merged with at least one other module.
In the future this might become obsolete with #162398
There was a problem hiding this comment.
Added
# `null` is not intended for use by modules. It is an internal
# value that means "whatever the user has declared elsewhere".
# This might become obsolete with https://github.com/NixOS/nixpkgs/issues/162398
…tion" This reverts commit 6b077c4. Thanks Infinisil for discovering this problem: > After a lot of trial and error, trying to prove why fixupOptionType should > be used here or not, I figured it out: It's needed for the sake of file > locations in error messages.
|
Could you explain the example for systemd a bit? Would this allow something like: (That would be amazing) |
|
@bobvanderlinden Yes, while this was already valid, it can now be mixed with "top-level" options like This was not allowed to coexist with your example. |
Today pennae said on IRC:
and it struck me that such a refactoring probably increases strictness by merging attrsets too soon. I don't think this insight affects this PR itself, because while it makes the introduction of submodules in existing option trees more feasible, it is not this change but those introductions that cause the increased strictness. Perhaps a better solution would be to treat bare submodules as option trees with a prefix. This eliminates the extra strictness caused by bare submodule introductions. |
|
tl;dr we can improve laziness by translating bare submodules to option trees rather than the other way around, or we can implement a special "import at prefix" that does only what we need. Submodules are more than just option trees though, so a "prefixing" operation on modules seems like it'd need quite a lot of code. |
|
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/genodepkgs-extending-nixpkgs-nixos-to-genode/8779/7 |
|
This PR reinterprets parts of the option tree based on information of explicitly declared options. I call it static merging for two reasons:
Motivating example: hercules-ci/flake-parts#142 |
|
I wonder if we could allow for merging an option with its default values, no matter what the type is. |
This is no longer necessary as of NixOS/nixpkgs#156533.
The workaround is no longer needed since Nixpkgs 22.05 (NixOS/nixpkgs#156533). Declaring options directly in a submodule now works, e.g. `options.flake.foo`. The function is kept for backwards compatibility but documented as deprecated. The minimum supported Nixpkgs lib version is already 22.05, so this change does not drop support for any previously supported version.
The workaround is no longer needed since Nixpkgs 22.05 (NixOS/nixpkgs#156533). Declaring options directly in a submodule now works, e.g. `options.flake.foo`. The function is kept for backwards compatibility but documented as deprecated. The minimum supported Nixpkgs lib version is already 22.05, so this change does not drop support for any previously supported version.
Motivation for this change
Let module declare options directly in bare submodule, where a bare submodule is an option that has a type like
submoduleWith x, as opposed toattrsOf (submoduleWith x).This makes migration unnecessary when introducing a freeform type in an existing option tree and it removes some unwieldy syntax. Refs https://github.com/NixOS/nixpkgs/pull/156503/files#r790768951
Mainly, it allows the introduction of a freeformType at any place in an existing options tree without breaking compatibility with existing option declarations inside the new freeformType.
Another use case is for modules that should be imported in multiple locations in the options tree, such as
systemdandsystemd.user.Closes #146882
Things done
sandbox = trueset innix.conf? (See Nix manual)nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD". Note: all changes have to be committed, also see nixpkgs-review usage./result/bin/)nixos/doc/manual/md-to-db.shto update generated release notes