Skip to content

Commit

Permalink
lib/modules: Declare, check, merge, expose and test opt.meta
Browse files Browse the repository at this point in the history
  • Loading branch information
roberth committed Oct 18, 2024
1 parent 016f28c commit 2eda2ad
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 8 deletions.
53 changes: 46 additions & 7 deletions lib/modules.nix
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ let
(specialArgs.modulesPath or "")
(regularModules ++ [ internalModule ])
({ inherit lib options config specialArgs; } // specialArgs);
in mergeModules prefix (reverseList collected);
in mergeModules2 options._module prefix (reverseList collected);

options = merged.matchedOptions;

Expand Down Expand Up @@ -548,10 +548,16 @@ let
}
*/
mergeModules = prefix: modules:
mergeModules' prefix modules
mergeModules2' {} prefix modules
(concatMap (m: map (config: { file = m._file; inherit config; }) (pushDownProperties m.config)) modules);

mergeModules2 = _module: prefix: modules:
mergeModules2' _module prefix modules
(concatMap (m: map (config: { file = m._file; inherit config; }) (pushDownProperties m.config)) modules);

mergeModules' = prefix: modules: configs:
mergeModules2' { };
mergeModules2' = _module: prefix: modules: configs:
let
# an attrset 'name' => list of submodules that declare ‘name’.
declsByName =
Expand Down Expand Up @@ -655,7 +661,7 @@ let
if length optionDecls == length decls then
let opt = fixupOptionType loc (mergeOptionDecls loc decls);
in {
matchedOptions = evalOptionValue loc opt defns';
matchedOptions = evalOptionValue' _module loc opt defns';
unmatchedDefns = [];
}
else if optionDecls != [] then
Expand All @@ -672,7 +678,7 @@ let
then
let opt = fixupOptionType loc (mergeOptionDecls loc (map optionTreeToOption decls));
in {
matchedOptions = evalOptionValue loc opt defns';
matchedOptions = evalOptionValue' _module loc opt defns';
unmatchedDefns = [];
}
else
Expand All @@ -683,7 +689,7 @@ let
showRawDecls loc nonOptions
}"
else
mergeModules' loc decls defns) declsByName;
mergeModules2' _module loc decls defns) declsByName;

matchedOptions = mapAttrs (n: v: v.matchedOptions) resultsByName;

Expand Down Expand Up @@ -789,7 +795,8 @@ let

/* Merge all the definitions of an option to produce the final
config value. */
evalOptionValue = loc: opt: defs:
evalOptionValue = evalOptionValue' {};
evalOptionValue' = _module: loc: opt: defs:
let
# Add in the default value for this option, if any.
defs' =
Expand Down Expand Up @@ -826,8 +833,39 @@ let
inherit (res) isDefined;
# This allows options to be correctly displayed using `${options.path.to.it}`
__toString = _: showOption loc;
meta = checkOptionMeta _module opt;
};

checkOptionMeta = _module: opt:
let
metaConfiguration =
evalModules {
# The option path. Although the things we're checking happen to be options,
# that's not what we're checking against, and that's what the prefix is
# about. The checkable options are more like _file, and we'll make use of that.
prefix = [ "_modules" "optionMeta" ];
modules =
[
{ options = _module.optionMeta or { }; }
]
++ lib.zipListsWith
(decl: pos:
lib.setDefaultModuleLocation
"option declaration at ${pos.file}:${toString pos.line}:${toString pos.column}"
{
config =
(lib.throwIf
(opt?meta._module)
"In option declarations, `meta._module` is not allowed, but option `${showOption opt.loc}` has it."
opt.meta or {});
}
)
opt.declarations
opt.declarationPositions;
};
in
metaConfiguration.config;

# Merge definitions of a value of a given type.
mergeDefinitions = loc: type: defs: rec {
defsFinal' =
Expand Down Expand Up @@ -1462,7 +1500,8 @@ private //
defaultPriority
doRename
evalModules
evalOptionValue # for use by lib.types
evalOptionValue # for use by lib.types (?)
evalOptionValue' # for use by lib.types
filterOverrides
filterOverrides'
fixMergeModules
Expand Down
4 changes: 4 additions & 0 deletions lib/tests/modules.sh
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ checkConfigError 'The option .sub.wrong2. does not exist. Definition values:' co
checkConfigError '.*This can happen if you e.g. declared your options in .types.submodule.' config.sub ./error-mkOption-in-submodule-config.nix
checkConfigError '.*A definition for option .bad. is not of type .non-empty .list of .submodule...\.' config.bad ./error-nonEmptyListOf-submodule.nix

checkConfigOutput '^true$' options.foo.meta.required ./option-meta.nix
checkConfigError '.*The option ._modules\.optionMeta\.reuired. does not exist.*' options.undeclared.meta.reuired ./option-meta.nix
checkConfigError '.*option-meta\.nix:[0-9]+:[0-9]+.: false' options.undeclared.meta.reuired ./option-meta.nix

# types.attrTag
checkConfigOutput '^true$' config.okChecks ./types-attrTag.nix
checkConfigError 'A definition for option .intStrings\.syntaxError. is not of type .attribute-tagged union' config.intStrings.syntaxError ./types-attrTag.nix
Expand Down
12 changes: 12 additions & 0 deletions lib/tests/modules/option-meta-required.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{ lib, ... }:
let
inherit (lib) mkOption types;
in
{
options._module.optionMeta = {
required = lib.mkOption {
type = types.bool;
description = "Whether an option is required or something; just an example meta attribute for testing.";
};
};
}
23 changes: 23 additions & 0 deletions lib/tests/modules/option-meta.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{ lib, ... }:
let
inherit (lib) mkOption types;
in
{
imports = [
./option-meta-required.nix
];
options = {
foo = lib.mkOption {
type = lib.types.str;
meta.required = true;
};
undeclared = lib.mkOption {
type = lib.types.str;
# typo, no q
meta.reuired = false;
};
};
config = {
foo = "bar";
};
}
3 changes: 2 additions & 1 deletion lib/types.nix
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,8 @@ rec {
if tags?${choice}
then
{ ${choice} =
(lib.modules.evalOptionValue
(lib.modules.evalOptionValue'
{ } # Can't have option meta attributes in attrTag for now; needs `merge` function interface upgrade
(loc ++ [choice])
tags.${choice}
checkedValueDefs
Expand Down

0 comments on commit 2eda2ad

Please sign in to comment.