Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion lib/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ let
mkMergedOptionModule mkChangedOptionModule
mkAliasOptionModule mkDerivedConfig doRename
mkAliasOptionModuleMD;
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
getValues getFiles
Expand Down
65 changes: 58 additions & 7 deletions lib/modules.nix
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,15 @@ let
within a configuration, but can be used in module imports.
'';
};
_module.optionMeta = mkOption {
visible = false;
internal = true;
type = types.deferredModuleWith {
staticModules = [
./modules/default-meta-interface.nix
];
};
};
};

config = {
Expand All @@ -243,7 +252,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 +557,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 +670,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 +687,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 +698,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 +804,8 @@ let

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

checkOptionMeta = _moduleOptions: opt:
let
metaConfiguration =
evalModules {
specialArgs = {
optionDeclaration = opt;
};
# 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 = [ "options" ] ++ opt.loc ++ [ "meta" ];
modules =
[
_moduleOptions.optionMeta.value
]
++ 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 +1512,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
36 changes: 36 additions & 0 deletions lib/modules/default-meta-interface.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{ lib, optionDeclaration, ... }:
let
inherit (lib) types mkOption;
in
{
options = {
defaultText = mkOption {
apply =
v: if v == null && !optionDeclaration ? default then null else lib.options.renderOptionValue v;
type = types.raw;
default = optionDeclaration.default or null;
};
example = mkOption {
apply =
v: if v == null && !optionDeclaration ? default then null else lib.options.renderOptionValue v;
type = types.raw;
default = optionDeclaration.example or null;
};
description = mkOption {
type = types.nullOr types.str;
default = optionDeclaration.description or null;
};
internal = mkOption {
type = types.bool;
default = optionDeclaration.internal or false;
};
visible = mkOption {
type = types.enum [
true
false
"shallow"
];
default = optionDeclaration.visible or true;
};
};
}
11 changes: 7 additions & 4 deletions lib/options.nix
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ rec {
{
# Default value used when no definition is given in the configuration.
default ? null,
# Option type, providing type-checking and value merging.
type ? null,
# Whether the option can be set only once
readOnly ? null,


# Textual representation of the default, for the manual.
defaultText ? null,
# Example value used in the manual.
Expand All @@ -75,16 +81,13 @@ rec {
description ? null,
# Related packages used in the manual (see `genRelatedPackages` in ../nixos/lib/make-options-doc/default.nix).
relatedPackages ? null,
# Option type, providing type-checking and value merging.
type ? null,
# Function that converts the option value to something else.
apply ? null,
# Whether the option is for NixOS developers only.
internal ? null,
# Whether the option shows up in the manual. Default: true. Use false to hide the option and any sub-options from submodules. Use "shallow" to hide only sub-options.
visible ? null,
# Whether the option can be set only once
readOnly ? null,
meta ? {},
} @ attrs:
attrs // { _type = "option"; };

Expand Down
6 changes: 6 additions & 0 deletions lib/tests/modules.sh
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,12 @@ 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
checkConfigOutput '^"ok"$' config.assertions ./option-meta.nix
checkConfigError '.*The option .options\.undeclared\.meta\.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
checkConfigError '.*The option .options\.missingDef\.meta\.required. was accessed but has no value defined\. Try setting the option.*' options.missingDef.meta.required ./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
14 changes: 14 additions & 0 deletions lib/tests/modules/option-meta-required.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{ 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.";
# Most meta options should have a default, but for this test we don't define,
# as missingDef in ./option-meta.nix relies on this.
};
};
}
45 changes: 45 additions & 0 deletions lib/tests/modules/option-meta.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
config,
lib,
options,
...
}:
let
inherit (lib) isAttrs mkOption types;
nullAttrs = lib.mapAttrs (_: _: null);
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;
};
missingDef = mkOption {
type = lib.types.str;
};
brokenMeta = mkOption {
meta = abort "brokenMeta.meta is broken and must not be evaluated as a matter of laziness, performance, robustness.";
default = "ok";
};

assertions = mkOption { };
};
config = {
foo = "bar";
assertions =
assert options.foo.meta == { required = true; };
# laziness
assert isAttrs options.missingDef.meta;
assert nullAttrs options.missingDef.meta == { required = null; };
assert config.brokenMeta == "ok";
"ok";
};
}
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'
{ } # Would need something like `attrTagWith { optionMeta = { }; }`, and a good way to retrieve the `meta`s
(loc ++ [choice])
tags.${choice}
checkedValueDefs
Expand Down
13 changes: 13 additions & 0 deletions nixos/modules/misc/optionMeta.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{ lib, optionDeclaration, ... }:
let
inherit (lib) types mkOption;
in
{
# Additional options available in mkOption { ..., meta = { ... } }
options = {
relatedPackages = mkOption {
type = types.raw;
default = optionDeclaration.relatedPackages or [ ];
};
};
}
3 changes: 3 additions & 0 deletions nixos/modules/misc/register-optionMeta.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
_module.optionMeta = ./optionMeta.nix;
}
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
./misc/man-db.nix
./misc/mandoc.nix
./misc/meta.nix
./misc/register-optionMeta.nix
./misc/nixops-autoluks.nix
./misc/nixpkgs.nix
./misc/nixpkgs-flake.nix
Expand Down