mirror of
https://github.com/ilyakooo0/nixpkgs.git
synced 2024-12-27 05:43:50 +03:00
Merge pull request #156533 from hercules-ci/issue-146882-transparent-submodule-options
lib.modules: Let module declare options directly in bare submodule
This commit is contained in:
commit
b97742c66c
@ -9,7 +9,7 @@ let
|
|||||||
catAttrs
|
catAttrs
|
||||||
concatLists
|
concatLists
|
||||||
concatMap
|
concatMap
|
||||||
count
|
concatStringsSep
|
||||||
elem
|
elem
|
||||||
filter
|
filter
|
||||||
findFirst
|
findFirst
|
||||||
@ -47,6 +47,20 @@ let
|
|||||||
showOption
|
showOption
|
||||||
unknownModule
|
unknownModule
|
||||||
;
|
;
|
||||||
|
|
||||||
|
showDeclPrefix = loc: decl: prefix:
|
||||||
|
" - option(s) with prefix `${showOption (loc ++ [prefix])}' in module `${decl._file}'";
|
||||||
|
showRawDecls = loc: decls:
|
||||||
|
concatStringsSep "\n"
|
||||||
|
(sort (a: b: a < b)
|
||||||
|
(concatMap
|
||||||
|
(decl: map
|
||||||
|
(showDeclPrefix loc decl)
|
||||||
|
(attrNames decl.options)
|
||||||
|
)
|
||||||
|
decls
|
||||||
|
));
|
||||||
|
|
||||||
in
|
in
|
||||||
|
|
||||||
rec {
|
rec {
|
||||||
@ -474,26 +488,61 @@ rec {
|
|||||||
[{ inherit (module) file; inherit value; }]
|
[{ inherit (module) file; inherit value; }]
|
||||||
) configs;
|
) configs;
|
||||||
|
|
||||||
|
# Convert an option tree decl to a submodule option decl
|
||||||
|
optionTreeToOption = decl:
|
||||||
|
if isOption decl.options
|
||||||
|
then decl
|
||||||
|
else decl // {
|
||||||
|
options = mkOption {
|
||||||
|
type = types.submoduleWith {
|
||||||
|
modules = [ { options = decl.options; } ];
|
||||||
|
# `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
|
||||||
|
shorthandOnlyDefinesConfig = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
resultsByName = mapAttrs (name: decls:
|
resultsByName = mapAttrs (name: decls:
|
||||||
# We're descending into attribute ‘name’.
|
# We're descending into attribute ‘name’.
|
||||||
let
|
let
|
||||||
loc = prefix ++ [name];
|
loc = prefix ++ [name];
|
||||||
defns = defnsByName.${name} or [];
|
defns = defnsByName.${name} or [];
|
||||||
defns' = defnsByName'.${name} or [];
|
defns' = defnsByName'.${name} or [];
|
||||||
nrOptions = count (m: isOption m.options) decls;
|
optionDecls = filter (m: isOption m.options) decls;
|
||||||
in
|
in
|
||||||
if nrOptions == length decls then
|
if length optionDecls == length decls then
|
||||||
let opt = fixupOptionType loc (mergeOptionDecls loc decls);
|
let opt = fixupOptionType loc (mergeOptionDecls loc decls);
|
||||||
in {
|
in {
|
||||||
matchedOptions = evalOptionValue loc opt defns';
|
matchedOptions = evalOptionValue loc opt defns';
|
||||||
unmatchedDefns = [];
|
unmatchedDefns = [];
|
||||||
}
|
}
|
||||||
else if nrOptions != 0 then
|
else if optionDecls != [] then
|
||||||
let
|
if all (x: x.options.type.name == "submodule") optionDecls
|
||||||
firstOption = findFirst (m: isOption m.options) "" decls;
|
# Raw options can only be merged into submodules. Merging into
|
||||||
firstNonOption = findFirst (m: !isOption m.options) "" decls;
|
# attrsets might be nice, but ambiguous. Suppose we have
|
||||||
in
|
# attrset as a `attrsOf submodule`. User declares option
|
||||||
throw "The option `${showOption loc}' in `${firstOption._file}' is a prefix of options in `${firstNonOption._file}'."
|
# attrset.foo.bar, this could mean:
|
||||||
|
# a. option `bar` is only available in `attrset.foo`
|
||||||
|
# b. option `foo.bar` is available in all `attrset.*`
|
||||||
|
# c. reject and require "<name>" as a reminder that it behaves like (b).
|
||||||
|
# d. magically combine (a) and (c).
|
||||||
|
# All of the above are merely syntax sugar though.
|
||||||
|
then
|
||||||
|
let opt = fixupOptionType loc (mergeOptionDecls loc (map optionTreeToOption decls));
|
||||||
|
in {
|
||||||
|
matchedOptions = evalOptionValue loc opt defns';
|
||||||
|
unmatchedDefns = [];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
let
|
||||||
|
firstNonOption = findFirst (m: !isOption m.options) "" decls;
|
||||||
|
nonOptions = filter (m: !isOption m.options) decls;
|
||||||
|
in
|
||||||
|
throw "The option `${showOption loc}' in module `${(lib.head optionDecls)._file}' would be a parent of the following options, but its type `${(lib.head optionDecls).options.type.description or "<no description>"}' does not support nested options.\n${
|
||||||
|
showRawDecls loc nonOptions
|
||||||
|
}"
|
||||||
else
|
else
|
||||||
mergeModules' loc decls defns) declsByName;
|
mergeModules' loc decls defns) declsByName;
|
||||||
|
|
||||||
@ -753,13 +802,14 @@ rec {
|
|||||||
compare = a: b: (a.priority or 1000) < (b.priority or 1000);
|
compare = a: b: (a.priority or 1000) < (b.priority or 1000);
|
||||||
in sort compare defs';
|
in sort compare defs';
|
||||||
|
|
||||||
/* Hack for backward compatibility: convert options of type
|
|
||||||
optionSet to options of type submodule. FIXME: remove
|
|
||||||
eventually. */
|
|
||||||
fixupOptionType = loc: opt:
|
fixupOptionType = loc: opt:
|
||||||
let
|
let
|
||||||
options = opt.options or
|
options = opt.options or
|
||||||
(throw "Option `${showOption loc}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}.");
|
(throw "Option `${showOption loc}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}.");
|
||||||
|
|
||||||
|
# Hack for backward compatibility: convert options of type
|
||||||
|
# optionSet to options of type submodule. FIXME: remove
|
||||||
|
# eventually.
|
||||||
f = tp:
|
f = tp:
|
||||||
if tp.name == "option set" || tp.name == "submodule" then
|
if tp.name == "option set" || tp.name == "submodule" then
|
||||||
throw "The option ${showOption loc} uses submodules without a wrapping type, in ${showFiles opt.declarations}."
|
throw "The option ${showOption loc} uses submodules without a wrapping type, in ${showFiles opt.declarations}."
|
||||||
|
@ -62,6 +62,13 @@ checkConfigError() {
|
|||||||
checkConfigOutput '^false$' config.enable ./declare-enable.nix
|
checkConfigOutput '^false$' config.enable ./declare-enable.nix
|
||||||
checkConfigError 'The option .* does not exist. Definition values:\n\s*- In .*: true' config.enable ./define-enable.nix
|
checkConfigError 'The option .* does not exist. Definition values:\n\s*- In .*: true' config.enable ./define-enable.nix
|
||||||
|
|
||||||
|
checkConfigOutput '^1$' config.bare-submodule.nested ./declare-bare-submodule.nix ./declare-bare-submodule-nested-option.nix
|
||||||
|
checkConfigOutput '^2$' config.bare-submodule.deep ./declare-bare-submodule.nix ./declare-bare-submodule-deep-option.nix
|
||||||
|
checkConfigOutput '^42$' config.bare-submodule.nested ./declare-bare-submodule.nix ./declare-bare-submodule-nested-option.nix ./declare-bare-submodule-deep-option.nix ./define-bare-submodule-values.nix
|
||||||
|
checkConfigOutput '^420$' config.bare-submodule.deep ./declare-bare-submodule.nix ./declare-bare-submodule-nested-option.nix ./declare-bare-submodule-deep-option.nix ./define-bare-submodule-values.nix
|
||||||
|
checkConfigOutput '^2$' config.bare-submodule.deep ./declare-bare-submodule.nix ./declare-bare-submodule-deep-option.nix ./define-shorthandOnlyDefinesConfig-true.nix
|
||||||
|
checkConfigError 'The option .bare-submodule.deep. in .*/declare-bare-submodule-deep-option.nix. is already declared in .*/declare-bare-submodule-deep-option-duplicate.nix' config.bare-submodule.deep ./declare-bare-submodule.nix ./declare-bare-submodule-deep-option.nix ./declare-bare-submodule-deep-option-duplicate.nix
|
||||||
|
|
||||||
# Check integer types.
|
# Check integer types.
|
||||||
# unsigned
|
# unsigned
|
||||||
checkConfigOutput '^42$' config.value ./declare-int-unsigned-value.nix ./define-value-int-positive.nix
|
checkConfigOutput '^42$' config.value ./declare-int-unsigned-value.nix ./define-value-int-positive.nix
|
||||||
@ -304,6 +311,12 @@ checkConfigOutput "10" config.processedToplevel ./raw.nix
|
|||||||
checkConfigError "The option .multiple. is defined multiple times" config.multiple ./raw.nix
|
checkConfigError "The option .multiple. is defined multiple times" config.multiple ./raw.nix
|
||||||
checkConfigOutput "bar" config.priorities ./raw.nix
|
checkConfigOutput "bar" config.priorities ./raw.nix
|
||||||
|
|
||||||
|
## Option collision
|
||||||
|
checkConfigError \
|
||||||
|
'The option .set. in module .*/declare-set.nix. would be a parent of the following options, but its type .attribute set of signed integers. does not support nested options.\n\s*- option[(]s[)] with prefix .set.enable. in module .*/declare-enable-nested.nix.' \
|
||||||
|
config.set \
|
||||||
|
./declare-set.nix ./declare-enable-nested.nix
|
||||||
|
|
||||||
# Test that types.optionType merges types correctly
|
# Test that types.optionType merges types correctly
|
||||||
checkConfigOutput '^10$' config.theOption.int ./optionTypeMerging.nix
|
checkConfigOutput '^10$' config.theOption.int ./optionTypeMerging.nix
|
||||||
checkConfigOutput '^"hello"$' config.theOption.str ./optionTypeMerging.nix
|
checkConfigOutput '^"hello"$' config.theOption.str ./optionTypeMerging.nix
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
{ lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) mkOption types;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.bare-submodule.deep = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 2;
|
||||||
|
};
|
||||||
|
}
|
10
lib/tests/modules/declare-bare-submodule-deep-option.nix
Normal file
10
lib/tests/modules/declare-bare-submodule-deep-option.nix
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{ lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) mkOption types;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.bare-submodule.deep = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 2;
|
||||||
|
};
|
||||||
|
}
|
19
lib/tests/modules/declare-bare-submodule-nested-option.nix
Normal file
19
lib/tests/modules/declare-bare-submodule-nested-option.nix
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{ config, lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) mkOption types;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.bare-submodule = mkOption {
|
||||||
|
type = types.submoduleWith {
|
||||||
|
shorthandOnlyDefinesConfig = config.shorthandOnlyDefinesConfig;
|
||||||
|
modules = [
|
||||||
|
{
|
||||||
|
options.nested = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 1;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
18
lib/tests/modules/declare-bare-submodule.nix
Normal file
18
lib/tests/modules/declare-bare-submodule.nix
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{ config, lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) mkOption types;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.bare-submodule = mkOption {
|
||||||
|
type = types.submoduleWith {
|
||||||
|
modules = [ ];
|
||||||
|
shorthandOnlyDefinesConfig = config.shorthandOnlyDefinesConfig;
|
||||||
|
};
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
# config-dependent options: won't recommend, but useful for making this test parameterized
|
||||||
|
options.shorthandOnlyDefinesConfig = mkOption {
|
||||||
|
default = false;
|
||||||
|
};
|
||||||
|
}
|
12
lib/tests/modules/declare-set.nix
Normal file
12
lib/tests/modules/declare-set.nix
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{ lib, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
options.set = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
example = { a = 1; };
|
||||||
|
type = lib.types.attrsOf lib.types.int;
|
||||||
|
description = ''
|
||||||
|
Some descriptive text
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
4
lib/tests/modules/define-bare-submodule-values.nix
Normal file
4
lib/tests/modules/define-bare-submodule-values.nix
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
bare-submodule.nested = 42;
|
||||||
|
bare-submodule.deep = 420;
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
{ shorthandOnlyDefinesConfig = true; }
|
@ -572,14 +572,18 @@ rec {
|
|||||||
let
|
let
|
||||||
inherit (lib.modules) evalModules;
|
inherit (lib.modules) evalModules;
|
||||||
|
|
||||||
coerce = unify: value: if isFunction value
|
shorthandToModule = if shorthandOnlyDefinesConfig == false
|
||||||
then setFunctionArgs (args: unify (value args)) (functionArgs value)
|
then value: value
|
||||||
else unify (if shorthandOnlyDefinesConfig then { config = value; } else value);
|
else value: { config = value; };
|
||||||
|
|
||||||
allModules = defs: imap1 (n: { value, file }:
|
allModules = defs: imap1 (n: { value, file }:
|
||||||
if isAttrs value || isFunction value then
|
if isFunction value
|
||||||
# Annotate the value with the location of its definition for better error messages
|
then setFunctionArgs
|
||||||
coerce (lib.modules.unifyModuleSyntax file "${toString file}-${toString n}") value
|
(args: lib.modules.unifyModuleSyntax file "${toString file}-${toString n}" (value args))
|
||||||
|
(functionArgs value)
|
||||||
|
else if isAttrs value
|
||||||
|
then
|
||||||
|
lib.modules.unifyModuleSyntax file "${toString file}-${toString n}" (shorthandToModule value)
|
||||||
else value
|
else value
|
||||||
) defs;
|
) defs;
|
||||||
|
|
||||||
@ -647,7 +651,11 @@ rec {
|
|||||||
then lhs.specialArgs // rhs.specialArgs
|
then lhs.specialArgs // rhs.specialArgs
|
||||||
else throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\"";
|
else throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\"";
|
||||||
shorthandOnlyDefinesConfig =
|
shorthandOnlyDefinesConfig =
|
||||||
if lhs.shorthandOnlyDefinesConfig == rhs.shorthandOnlyDefinesConfig
|
if lhs.shorthandOnlyDefinesConfig == null
|
||||||
|
then rhs.shorthandOnlyDefinesConfig
|
||||||
|
else if rhs.shorthandOnlyDefinesConfig == null
|
||||||
|
then lhs.shorthandOnlyDefinesConfig
|
||||||
|
else if lhs.shorthandOnlyDefinesConfig == rhs.shorthandOnlyDefinesConfig
|
||||||
then lhs.shorthandOnlyDefinesConfig
|
then lhs.shorthandOnlyDefinesConfig
|
||||||
else throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values";
|
else throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values";
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user