diff options
author | Frederik Rietdijk <fridh@fridh.nl> | 2020-01-10 18:32:07 +0100 |
---|---|---|
committer | Frederik Rietdijk <fridh@fridh.nl> | 2020-01-10 18:32:07 +0100 |
commit | 348eaa280bdc649211dc34a2a79ab3c21db05532 (patch) | |
tree | c70787b060c878d1cfc487cdb7812db7abb7260e /lib | |
parent | 5ad16cb27fd81cb67db4da80283e147bea4e1c25 (diff) | |
parent | e2e90a0e6422b143d6d6a9539280eded7306126d (diff) | |
download | nixpkgs-348eaa280bdc649211dc34a2a79ab3c21db05532.tar nixpkgs-348eaa280bdc649211dc34a2a79ab3c21db05532.tar.gz nixpkgs-348eaa280bdc649211dc34a2a79ab3c21db05532.tar.bz2 nixpkgs-348eaa280bdc649211dc34a2a79ab3c21db05532.tar.lz nixpkgs-348eaa280bdc649211dc34a2a79ab3c21db05532.tar.xz nixpkgs-348eaa280bdc649211dc34a2a79ab3c21db05532.tar.zst nixpkgs-348eaa280bdc649211dc34a2a79ab3c21db05532.zip |
Merge master into staging-next
Diffstat (limited to 'lib')
-rw-r--r-- | lib/modules.nix | 33 | ||||
-rwxr-xr-x | lib/tests/modules.sh | 9 | ||||
-rw-r--r-- | lib/tests/modules/attrsOf-conditional-check.nix | 7 | ||||
-rw-r--r-- | lib/tests/modules/attrsOf-lazy-check.nix | 7 | ||||
-rw-r--r-- | lib/tests/modules/declare-attrsOf.nix | 6 | ||||
-rw-r--r-- | lib/tests/modules/declare-lazyAttrsOf.nix | 6 | ||||
-rw-r--r-- | lib/tests/modules/import-from-store.nix | 12 | ||||
-rw-r--r-- | lib/types.nix | 44 |
8 files changed, 99 insertions, 25 deletions
diff --git a/lib/modules.nix b/lib/modules.nix index 559697b3d57..e2315290ff0 100644 --- a/lib/modules.nix +++ b/lib/modules.nix @@ -41,7 +41,13 @@ rec { options = { _module.args = mkOption { - type = types.attrsOf types.unspecified; + # Because things like `mkIf` are entirely useless for + # `_module.args` (because there's no way modules can check which + # arguments were passed), we'll use `lazyAttrsOf` which drops + # support for that, in turn it's lazy in its values. This means e.g. + # a `_module.args.pkgs = import (fetchTarball { ... }) {}` won't + # start a download when `pkgs` wasn't evaluated. + type = types.lazyAttrsOf types.unspecified; internal = true; description = "Arguments passed to each module."; }; @@ -365,16 +371,9 @@ rec { else mergeDefinitions loc opt.type defs'; - - # The value with a check that it is defined - valueDefined = if res.isDefined then res.mergedValue else - # (nixos-option detects this specific error message and gives it special - # handling. If changed here, please change it there too.) - throw "The option `${showOption loc}' is used but not defined."; - # Apply the 'apply' function to the merged value. This allows options to # yield a value computed from the definitions - value = if opt ? apply then opt.apply valueDefined else valueDefined; + value = if opt ? apply then opt.apply res.mergedValue else res.mergedValue; in opt // { value = builtins.addErrorContext "while evaluating the option `${showOption loc}':" value; @@ -408,11 +407,17 @@ rec { }; defsFinal = defsFinal'.values; - # Type-check the remaining definitions, and merge them. - mergedValue = foldl' (res: def: - if type.check def.value then res - else throw "The option value `${showOption loc}' in `${def.file}' is not of type `${type.description}'.") - (type.merge loc defsFinal) defsFinal; + # Type-check the remaining definitions, and merge them. Or throw if no definitions. + mergedValue = + if isDefined then + foldl' (res: def: + if type.check def.value then res + else throw "The option value `${showOption loc}' in `${def.file}' is not of type `${type.description}'." + ) (type.merge loc defsFinal) defsFinal + else + # (nixos-option detects this specific error message and gives it special + # handling. If changed here, please change it there too.) + throw "The option `${showOption loc}' is used but not defined."; isDefined = defsFinal != []; diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index 79d90670fb5..c8340ff7f15 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -186,6 +186,15 @@ checkConfigError 'The option .* defined in .* does not exist' config.enable ./di # Check that imports can depend on derivations checkConfigOutput "true" config.enable ./import-from-store.nix +# Check attrsOf and lazyAttrsOf. Only lazyAttrsOf should be lazy, and only +# attrsOf should work with conditional definitions +# In addition, lazyAttrsOf should honor an options emptyValue +checkConfigError "is not lazy" config.isLazy ./declare-attrsOf.nix ./attrsOf-lazy-check.nix +checkConfigOutput "true" config.isLazy ./declare-lazyAttrsOf.nix ./attrsOf-lazy-check.nix +checkConfigOutput "true" config.conditionalWorks ./declare-attrsOf.nix ./attrsOf-conditional-check.nix +checkConfigOutput "false" config.conditionalWorks ./declare-lazyAttrsOf.nix ./attrsOf-conditional-check.nix +checkConfigOutput "empty" config.value.foo ./declare-lazyAttrsOf.nix ./attrsOf-conditional-check.nix + cat <<EOF ====== module tests ====== $pass Pass diff --git a/lib/tests/modules/attrsOf-conditional-check.nix b/lib/tests/modules/attrsOf-conditional-check.nix new file mode 100644 index 00000000000..0f00ebca155 --- /dev/null +++ b/lib/tests/modules/attrsOf-conditional-check.nix @@ -0,0 +1,7 @@ +{ lib, config, ... }: { + options.conditionalWorks = lib.mkOption { + default = ! config.value ? foo; + }; + + config.value.foo = lib.mkIf false "should not be defined"; +} diff --git a/lib/tests/modules/attrsOf-lazy-check.nix b/lib/tests/modules/attrsOf-lazy-check.nix new file mode 100644 index 00000000000..ec5b418b15a --- /dev/null +++ b/lib/tests/modules/attrsOf-lazy-check.nix @@ -0,0 +1,7 @@ +{ lib, config, ... }: { + options.isLazy = lib.mkOption { + default = ! config.value ? foo; + }; + + config.value.bar = throw "is not lazy"; +} diff --git a/lib/tests/modules/declare-attrsOf.nix b/lib/tests/modules/declare-attrsOf.nix new file mode 100644 index 00000000000..b3999de7e5f --- /dev/null +++ b/lib/tests/modules/declare-attrsOf.nix @@ -0,0 +1,6 @@ +{ lib, ... }: { + options.value = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = {}; + }; +} diff --git a/lib/tests/modules/declare-lazyAttrsOf.nix b/lib/tests/modules/declare-lazyAttrsOf.nix new file mode 100644 index 00000000000..1d9fec25f90 --- /dev/null +++ b/lib/tests/modules/declare-lazyAttrsOf.nix @@ -0,0 +1,6 @@ +{ lib, ... }: { + options.value = lib.mkOption { + type = lib.types.lazyAttrsOf (lib.types.str // { emptyValue.value = "empty"; }); + default = {}; + }; +} diff --git a/lib/tests/modules/import-from-store.nix b/lib/tests/modules/import-from-store.nix index 64e7ec2e388..f5af22432ce 100644 --- a/lib/tests/modules/import-from-store.nix +++ b/lib/tests/modules/import-from-store.nix @@ -1,17 +1,11 @@ { lib, ... }: -let - drv = derivation { - name = "derivation"; - system = builtins.currentSystem; - builder = "/bin/sh"; - args = [ "-c" "echo {} > $out" ]; - }; -in { +{ imports = [ - "${drv}" + "${builtins.toFile "drv" "{}"}" ./declare-enable.nix ./define-enable.nix ]; } + diff --git a/lib/types.nix b/lib/types.nix index 4872a676657..e86f6d36476 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -65,6 +65,11 @@ rec { # definition values and locations (e.g. [ { file = "/foo.nix"; # value = 1; } { file = "/bar.nix"; value = 2 } ]). merge ? mergeDefaultOption + , # Whether this type has a value representing nothingness. If it does, + # this should be a value of the form { value = <the nothing value>; } + # If it doesn't, this should be {} + # This may be used when a value is required for `mkIf false`. This allows the extra laziness in e.g. `lazyAttrsOf`. + emptyValue ? {} , # Return a flat list of sub-options. Used to generate # documentation. getSubOptions ? prefix: {} @@ -88,7 +93,7 @@ rec { functor ? defaultFunctor name }: { _type = "option-type"; - inherit name check merge getSubOptions getSubModules substSubModules typeMerge functor; + inherit name check merge emptyValue getSubOptions getSubModules substSubModules typeMerge functor; description = if description == null then name else description; }; @@ -225,6 +230,7 @@ rec { description = "attribute set"; check = isAttrs; merge = loc: foldl' (res: def: mergeAttrs res def.value) {}; + emptyValue = { value = {}; }; }; # derivation is a reserved keyword. @@ -265,6 +271,7 @@ rec { ) def.value else throw "The option value `${showOption loc}` in `${def.file}` is not a list.") defs))); + emptyValue = { value = {}; }; getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]); getSubModules = elemType.getSubModules; substSubModules = m: listOf (elemType.substSubModules m); @@ -273,7 +280,10 @@ rec { nonEmptyListOf = elemType: let list = addCheck (types.listOf elemType) (l: l != []); - in list // { description = "non-empty " + list.description; }; + in list // { + description = "non-empty " + list.description; + # Note: emptyValue is left as is, because another module may define an element. + }; attrsOf = elemType: mkOptionType rec { name = "attrsOf"; @@ -285,12 +295,37 @@ rec { ) # Push down position info. (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs))); + emptyValue = { value = {}; }; getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]); getSubModules = elemType.getSubModules; substSubModules = m: attrsOf (elemType.substSubModules m); functor = (defaultFunctor name) // { wrapped = elemType; }; }; + # A version of attrsOf that's lazy in its values at the expense of + # conditional definitions not working properly. E.g. defining a value with + # `foo.attr = mkIf false 10`, then `foo ? attr == true`, whereas with + # attrsOf it would correctly be `false`. Accessing `foo.attr` would throw an + # error that it's not defined. Use only if conditional definitions don't make sense. + lazyAttrsOf = elemType: mkOptionType rec { + name = "lazyAttrsOf"; + description = "lazy attribute set of ${elemType.description}s"; + check = isAttrs; + merge = loc: defs: + zipAttrsWith (name: defs: + let merged = mergeDefinitions (loc ++ [name]) elemType defs; + # mergedValue will trigger an appropriate error when accessed + in merged.optionalValue.value or elemType.emptyValue.value or merged.mergedValue + ) + # Push down position info. + (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs); + emptyValue = { value = {}; }; + getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]); + getSubModules = elemType.getSubModules; + substSubModules = m: lazyAttrsOf (elemType.substSubModules m); + functor = (defaultFunctor name) // { wrapped = elemType; }; + }; + # List or attribute set of ... loaOf = elemType: let @@ -339,6 +374,7 @@ rec { description = "list or attribute set of ${elemType.description}s"; check = x: isList x || isAttrs x; merge = loc: defs: attrOnly.merge loc (convertAllLists loc defs); + emptyValue = { value = {}; }; getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]); getSubModules = elemType.getSubModules; substSubModules = m: loaOf (elemType.substSubModules m); @@ -350,6 +386,7 @@ rec { name = "uniq"; inherit (elemType) description check; merge = mergeOneOption; + emptyValue = elemType.emptyValue; getSubOptions = elemType.getSubOptions; getSubModules = elemType.getSubModules; substSubModules = m: uniq (elemType.substSubModules m); @@ -367,6 +404,7 @@ rec { else if nrNulls != 0 then throw "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}." else elemType.merge loc defs; + emptyValue = { value = null; }; getSubOptions = elemType.getSubOptions; getSubModules = elemType.getSubModules; substSubModules = m: nullOr (elemType.substSubModules m); @@ -407,6 +445,7 @@ rec { args.name = last loc; prefix = loc; }).config; + emptyValue = { value = {}; }; getSubOptions = prefix: (evalModules { inherit modules prefix specialArgs; # This is a work-around due to the fact that some sub-modules, @@ -515,6 +554,7 @@ rec { if finalType.check val then val else coerceFunc val; in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs); + emptyValue = finalType.emptyValue; getSubOptions = finalType.getSubOptions; getSubModules = finalType.getSubModules; substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m); |