aboutsummaryrefslogtreecommitdiff
path: root/nixpkgs/lib
diff options
context:
space:
mode:
authorKatharina Fey <kookie@spacekookie.de>2020-02-03 09:26:35 +0100
committerKatharina Fey <kookie@spacekookie.de>2020-02-03 09:26:35 +0100
commit899a451e08f7d6d2c8214d119c2a0316849a0ed4 (patch)
tree5e72a7288b7d2b33fead36fbfe91a02a48ff7fef /nixpkgs/lib
parent5962418b6543dfb3ca34965c0fa16dd77543801b (diff)
parenta21c2fa3ea2b88e698db6fc151d9c7259ae14d96 (diff)
Merge commit 'a21c2fa3ea2b88e698db6fc151d9c7259ae14d96'
Diffstat (limited to 'nixpkgs/lib')
-rw-r--r--nixpkgs/lib/attrsets.nix2
-rw-r--r--nixpkgs/lib/cli.nix83
-rw-r--r--nixpkgs/lib/default.nix7
-rw-r--r--nixpkgs/lib/generators.nix3
-rw-r--r--nixpkgs/lib/licenses.nix5
-rw-r--r--nixpkgs/lib/modules.nix40
-rw-r--r--nixpkgs/lib/sources.nix37
-rw-r--r--nixpkgs/lib/systems/examples.nix16
-rw-r--r--nixpkgs/lib/tests/misc.nix37
-rwxr-xr-xnixpkgs/lib/tests/modules.sh17
-rw-r--r--nixpkgs/lib/tests/modules/attrsOf-conditional-check.nix7
-rw-r--r--nixpkgs/lib/tests/modules/attrsOf-lazy-check.nix7
-rw-r--r--nixpkgs/lib/tests/modules/declare-attrsOf.nix6
-rw-r--r--nixpkgs/lib/tests/modules/declare-lazyAttrsOf.nix6
-rw-r--r--nixpkgs/lib/tests/modules/import-from-store.nix11
-rw-r--r--nixpkgs/lib/trivial.nix2
-rw-r--r--nixpkgs/lib/types.nix122
17 files changed, 351 insertions, 57 deletions
diff --git a/nixpkgs/lib/attrsets.nix b/nixpkgs/lib/attrsets.nix
index 086c3d746fc..32994432d53 100644
--- a/nixpkgs/lib/attrsets.nix
+++ b/nixpkgs/lib/attrsets.nix
@@ -60,7 +60,7 @@ rec {
[ { name = head attrPath; value = setAttrByPath (tail attrPath) value; } ];
- /* Like `getAttrPath' without a default value. If it doesn't find the
+ /* Like `attrByPath' without a default value. If it doesn't find the
path it will throw.
Example:
diff --git a/nixpkgs/lib/cli.nix b/nixpkgs/lib/cli.nix
new file mode 100644
index 00000000000..c96d4dbb043
--- /dev/null
+++ b/nixpkgs/lib/cli.nix
@@ -0,0 +1,83 @@
+{ lib }:
+
+rec {
+ /* Automatically convert an attribute set to command-line options.
+
+ This helps protect against malformed command lines and also to reduce
+ boilerplate related to command-line construction for simple use cases.
+
+ `toGNUCommandLine` returns a list of nix strings.
+ `toGNUCommandLineShell` returns an escaped shell string.
+
+ Example:
+ cli.toGNUCommandLine {} {
+ data = builtins.toJSON { id = 0; };
+ X = "PUT";
+ retry = 3;
+ retry-delay = null;
+ url = [ "https://example.com/foo" "https://example.com/bar" ];
+ silent = false;
+ verbose = true;
+ }
+ => [
+ "-X" "PUT"
+ "--data" "{\"id\":0}"
+ "--retry" "3"
+ "--url" "https://example.com/foo"
+ "--url" "https://example.com/bar"
+ "--verbose"
+ ]
+
+ cli.toGNUCommandLineShell {} {
+ data = builtins.toJSON { id = 0; };
+ X = "PUT";
+ retry = 3;
+ retry-delay = null;
+ url = [ "https://example.com/foo" "https://example.com/bar" ];
+ silent = false;
+ verbose = true;
+ }
+ => "'-X' 'PUT' '--data' '{\"id\":0}' '--retry' '3' '--url' 'https://example.com/foo' '--url' 'https://example.com/bar' '--verbose'";
+ */
+ toGNUCommandLineShell =
+ options: attrs: lib.escapeShellArgs (toGNUCommandLine options attrs);
+
+ toGNUCommandLine = {
+ # how to string-format the option name;
+ # by default one character is a short option (`-`),
+ # more than one characters a long option (`--`).
+ mkOptionName ?
+ k: if builtins.stringLength k == 1
+ then "-${k}"
+ else "--${k}",
+
+ # how to format a boolean value to a command list;
+ # by default itā€™s a flag option
+ # (only the option name if true, left out completely if false).
+ mkBool ? k: v: lib.optional v (mkOptionName k),
+
+ # how to format a list value to a command list;
+ # by default the option name is repeated for each value
+ # and `mkOption` is applied to the values themselves.
+ mkList ? k: v: lib.concatMap (mkOption k) v,
+
+ # how to format any remaining value to a command list;
+ # on the toplevel, booleans and lists are handled by `mkBool` and `mkList`,
+ # though they can still appear as values of a list.
+ # By default, everything is printed verbatim and complex types
+ # are forbidden (lists, attrsets, functions). `null` values are omitted.
+ mkOption ?
+ k: v: if v == null
+ then []
+ else [ (mkOptionName k) (lib.generators.mkValueStringDefault {} v) ]
+ }:
+ options:
+ let
+ render = k: v:
+ if builtins.isBool v then mkBool k v
+ else if builtins.isList v then mkList k v
+ else mkOption k v;
+
+ in
+ builtins.concatLists (lib.mapAttrsToList render options);
+}
diff --git a/nixpkgs/lib/default.nix b/nixpkgs/lib/default.nix
index 9f7a088d792..d2fe018aa6a 100644
--- a/nixpkgs/lib/default.nix
+++ b/nixpkgs/lib/default.nix
@@ -37,10 +37,13 @@ let
licenses = callLibs ./licenses.nix;
systems = callLibs ./systems;
+ # serialization
+ cli = callLibs ./cli.nix;
+ generators = callLibs ./generators.nix;
+
# misc
asserts = callLibs ./asserts.nix;
debug = callLibs ./debug.nix;
- generators = callLibs ./generators.nix;
misc = callLibs ./deprecated.nix;
# domain-specific
@@ -100,7 +103,7 @@ let
inherit (sources) pathType pathIsDirectory cleanSourceFilter
cleanSource sourceByRegex sourceFilesBySuffices
commitIdFromGitRepo cleanSourceWith pathHasContext
- canCleanSource;
+ canCleanSource pathIsRegularFile pathIsGitRepo;
inherit (modules) evalModules unifyModuleSyntax
applyIfFunction mergeModules
mergeModules' mergeOptionDecls evalOptionValue mergeDefinitions
diff --git a/nixpkgs/lib/generators.nix b/nixpkgs/lib/generators.nix
index a71654bec6c..a64e94bd5cb 100644
--- a/nixpkgs/lib/generators.nix
+++ b/nixpkgs/lib/generators.nix
@@ -46,7 +46,10 @@ rec {
else if isList v then err "lists" v
# same as for lists, might want to replace
else if isAttrs v then err "attrsets" v
+ # functions canā€™t be printed of course
else if isFunction v then err "functions" v
+ # letā€™s not talk about floats. There is no sensible `toString` for them.
+ else if isFloat v then err "floats" v
else err "this value is" (toString v);
diff --git a/nixpkgs/lib/licenses.nix b/nixpkgs/lib/licenses.nix
index 986b7fa1fdd..e2f94e565ce 100644
--- a/nixpkgs/lib/licenses.nix
+++ b/nixpkgs/lib/licenses.nix
@@ -536,11 +536,6 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
fullName = "University of Illinois/NCSA Open Source License";
};
- notion_lgpl = {
- url = "https://raw.githubusercontent.com/raboof/notion/master/LICENSE";
- fullName = "Notion modified LGPL";
- };
-
nposl3 = spdx {
spdxId = "NPOSL-3.0";
fullName = "Non-Profit Open Software License 3.0";
diff --git a/nixpkgs/lib/modules.nix b/nixpkgs/lib/modules.nix
index 38d6ac8cd91..2b1faf4f0c2 100644
--- a/nixpkgs/lib/modules.nix
+++ b/nixpkgs/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.";
};
@@ -151,8 +157,8 @@ rec {
filterModules = modulesPath: { disabled, modules }:
let
moduleKey = m: if isString m then toString modulesPath + "/" + m else toString m;
- disabledKeys = listToAttrs (map (k: nameValuePair (moduleKey k) null) disabled);
- keyFilter = filter (attrs: ! disabledKeys ? ${attrs.key});
+ disabledKeys = map moduleKey disabled;
+ keyFilter = filter (attrs: ! elem attrs.key disabledKeys);
in map (attrs: attrs.module) (builtins.genericClosure {
startSet = keyFilter modules;
operator = attrs: keyFilter attrs.modules;
@@ -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 != [];
@@ -759,12 +764,15 @@ rec {
fromOpt = getAttrFromPath from options;
toOf = attrByPath to
(abort "Renaming error: option `${showOption to}' does not exist.");
+ toType = let opt = attrByPath to {} options; in opt.type or null;
in
{
options = setAttrByPath from (mkOption {
inherit visible;
description = "Alias of <option>${showOption to}</option>.";
apply = x: use (toOf config);
+ } // optionalAttrs (toType != null) {
+ type = toType;
});
config = mkMerge [
{
diff --git a/nixpkgs/lib/sources.nix b/nixpkgs/lib/sources.nix
index 51bcf5559e3..05519c3e392 100644
--- a/nixpkgs/lib/sources.nix
+++ b/nixpkgs/lib/sources.nix
@@ -9,6 +9,9 @@ rec {
# Returns true if the path exists and is a directory, false otherwise
pathIsDirectory = p: if builtins.pathExists p then (pathType p) == "directory" else false;
+ # Returns true if the path exists and is a regular file, false otherwise
+ pathIsRegularFile = p: if builtins.pathExists p then (pathType p) == "regular" else false;
+
# Bring in a path as a source, filtering out all Subversion and CVS
# directories, as well as backup files (*~).
cleanSourceFilter = name: type: let baseName = baseNameOf (toString name); in ! (
@@ -102,6 +105,7 @@ rec {
in type == "directory" || lib.any (ext: lib.hasSuffix ext base) exts;
in cleanSourceWith { inherit filter; src = path; };
+ pathIsGitRepo = path: (builtins.tryEval (commitIdFromGitRepo path)).success;
# Get the commit id of a git repo
# Example: commitIdFromGitRepo <nixpkgs/.git>
@@ -110,24 +114,45 @@ rec {
with builtins;
let fileName = toString path + "/" + file;
packedRefsName = toString path + "/packed-refs";
- in if lib.pathExists fileName
+ absolutePath = base: path:
+ if lib.hasPrefix "/" path
+ then path
+ else toString (/. + "${base}/${path}");
+ in if pathIsRegularFile path
+ # Resolve git worktrees. See gitrepository-layout(5)
+ then
+ let m = match "^gitdir: (.*)$" (lib.fileContents path);
+ in if m == null
+ then throw ("File contains no gitdir reference: " + path)
+ else
+ let gitDir = absolutePath (dirOf path) (lib.head m);
+ commonDir' = if pathIsRegularFile "${gitDir}/commondir"
+ then lib.fileContents "${gitDir}/commondir"
+ else gitDir;
+ commonDir = absolutePath gitDir commonDir';
+ refFile = lib.removePrefix "${commonDir}/" "${gitDir}/${file}";
+ in readCommitFromFile refFile commonDir
+
+ else if pathIsRegularFile fileName
+ # Sometimes git stores the commitId directly in the file but
+ # sometimes it stores something like: Ā«ref: refs/heads/branch-nameĀ»
then
let fileContent = lib.fileContents fileName;
- # Sometimes git stores the commitId directly in the file but
- # sometimes it stores something like: Ā«ref: refs/heads/branch-nameĀ»
matchRef = match "^ref: (.*)$" fileContent;
- in if matchRef == null
+ in if matchRef == null
then fileContent
else readCommitFromFile (lib.head matchRef) path
+
+ else if pathIsRegularFile packedRefsName
# Sometimes, the file isn't there at all and has been packed away in the
# packed-refs file, so we have to grep through it:
- else if lib.pathExists packedRefsName
then
let fileContent = readFile packedRefsName;
matchRef = match (".*\n([^\n ]*) " + file + "\n.*") fileContent;
- in if matchRef == null
+ in if matchRef == null
then throw ("Could not find " + file + " in " + packedRefsName)
else lib.head matchRef
+
else throw ("Not a .git directory: " + path);
in readCommitFromFile "HEAD";
diff --git a/nixpkgs/lib/systems/examples.nix b/nixpkgs/lib/systems/examples.nix
index cb8bc3de6c4..19b3790ecbe 100644
--- a/nixpkgs/lib/systems/examples.nix
+++ b/nixpkgs/lib/systems/examples.nix
@@ -170,8 +170,8 @@ rec {
iphone64 = {
config = "aarch64-apple-ios";
# config = "aarch64-apple-darwin14";
- sdkVer = "10.2";
- xcodeVer = "8.2";
+ sdkVer = "12.4";
+ xcodeVer = "10.3";
xcodePlatform = "iPhoneOS";
useiOSPrebuilt = true;
platform = {};
@@ -180,8 +180,8 @@ rec {
iphone32 = {
config = "armv7a-apple-ios";
# config = "arm-apple-darwin10";
- sdkVer = "10.2";
- xcodeVer = "8.2";
+ sdkVer = "12.4";
+ xcodeVer = "10.3";
xcodePlatform = "iPhoneOS";
useiOSPrebuilt = true;
platform = {};
@@ -190,8 +190,8 @@ rec {
iphone64-simulator = {
config = "x86_64-apple-ios";
# config = "x86_64-apple-darwin14";
- sdkVer = "10.2";
- xcodeVer = "8.2";
+ sdkVer = "12.4";
+ xcodeVer = "10.3";
xcodePlatform = "iPhoneSimulator";
useiOSPrebuilt = true;
platform = {};
@@ -200,8 +200,8 @@ rec {
iphone32-simulator = {
config = "i686-apple-ios";
# config = "i386-apple-darwin11";
- sdkVer = "10.2";
- xcodeVer = "8.2";
+ sdkVer = "12.4";
+ xcodeVer = "10.3";
xcodePlatform = "iPhoneSimulator";
useiOSPrebuilt = true;
platform = {};
diff --git a/nixpkgs/lib/tests/misc.nix b/nixpkgs/lib/tests/misc.nix
index b064faa1e1b..59ed1e507e2 100644
--- a/nixpkgs/lib/tests/misc.nix
+++ b/nixpkgs/lib/tests/misc.nix
@@ -441,4 +441,41 @@ runTests {
expected = "Ā«fooĀ»";
};
+
+# CLI
+
+ testToGNUCommandLine = {
+ expr = cli.toGNUCommandLine {} {
+ data = builtins.toJSON { id = 0; };
+ X = "PUT";
+ retry = 3;
+ retry-delay = null;
+ url = [ "https://example.com/foo" "https://example.com/bar" ];
+ silent = false;
+ verbose = true;
+ };
+
+ expected = [
+ "-X" "PUT"
+ "--data" "{\"id\":0}"
+ "--retry" "3"
+ "--url" "https://example.com/foo"
+ "--url" "https://example.com/bar"
+ "--verbose"
+ ];
+ };
+
+ testToGNUCommandLineShell = {
+ expr = cli.toGNUCommandLineShell {} {
+ data = builtins.toJSON { id = 0; };
+ X = "PUT";
+ retry = 3;
+ retry-delay = null;
+ url = [ "https://example.com/foo" "https://example.com/bar" ];
+ silent = false;
+ verbose = true;
+ };
+
+ expected = "'-X' 'PUT' '--data' '{\"id\":0}' '--retry' '3' '--url' 'https://example.com/foo' '--url' 'https://example.com/bar' '--verbose'";
+ };
}
diff --git a/nixpkgs/lib/tests/modules.sh b/nixpkgs/lib/tests/modules.sh
index 2997fb1ada1..8cd632a439c 100755
--- a/nixpkgs/lib/tests/modules.sh
+++ b/nixpkgs/lib/tests/modules.sh
@@ -12,7 +12,7 @@ evalConfig() {
local attr=$1
shift;
local script="import ./default.nix { modules = [ $@ ];}"
- nix-instantiate --timeout 1 -E "$script" -A "$attr" --eval-only --show-trace
+ nix-instantiate --timeout 1 -E "$script" -A "$attr" --eval-only --show-trace --read-write-mode
}
reportFailure() {
@@ -174,8 +174,7 @@ checkConfigOutput "true" config.submodule.inner ./declare-submoduleWith-modules.
checkConfigOutput "true" config.submodule.outer ./declare-submoduleWith-modules.nix
## Paths should be allowed as values and work as expected
-# Temporarily disabled until https://github.com/NixOS/nixpkgs/pull/76861
-#checkConfigOutput "true" config.submodule.enable ./declare-submoduleWith-path.nix
+checkConfigOutput "true" config.submodule.enable ./declare-submoduleWith-path.nix
# Check that disabledModules works recursively and correctly
checkConfigOutput "true" config.enable ./disable-recursive/main.nix
@@ -183,6 +182,18 @@ checkConfigOutput "true" config.enable ./disable-recursive/{main.nix,disable-foo
checkConfigOutput "true" config.enable ./disable-recursive/{main.nix,disable-bar.nix}
checkConfigError 'The option .* defined in .* does not exist' config.enable ./disable-recursive/{main.nix,disable-foo.nix,disable-bar.nix}
+# 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/nixpkgs/lib/tests/modules/attrsOf-conditional-check.nix b/nixpkgs/lib/tests/modules/attrsOf-conditional-check.nix
new file mode 100644
index 00000000000..0f00ebca155
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/lib/tests/modules/attrsOf-lazy-check.nix b/nixpkgs/lib/tests/modules/attrsOf-lazy-check.nix
new file mode 100644
index 00000000000..ec5b418b15a
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/lib/tests/modules/declare-attrsOf.nix b/nixpkgs/lib/tests/modules/declare-attrsOf.nix
new file mode 100644
index 00000000000..b3999de7e5f
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/lib/tests/modules/declare-lazyAttrsOf.nix b/nixpkgs/lib/tests/modules/declare-lazyAttrsOf.nix
new file mode 100644
index 00000000000..1d9fec25f90
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/lib/tests/modules/import-from-store.nix b/nixpkgs/lib/tests/modules/import-from-store.nix
new file mode 100644
index 00000000000..f5af22432ce
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/import-from-store.nix
@@ -0,0 +1,11 @@
+{ lib, ... }:
+{
+
+ imports = [
+ "${builtins.toFile "drv" "{}"}"
+ ./declare-enable.nix
+ ./define-enable.nix
+ ];
+
+}
+
diff --git a/nixpkgs/lib/trivial.nix b/nixpkgs/lib/trivial.nix
index 3a25e31fb05..a281cd70fb0 100644
--- a/nixpkgs/lib/trivial.nix
+++ b/nixpkgs/lib/trivial.nix
@@ -191,7 +191,7 @@ rec {
let
revisionFile = "${toString ./..}/.git-revision";
gitRepo = "${toString ./..}/.git";
- in if lib.pathIsDirectory gitRepo
+ in if lib.pathIsGitRepo gitRepo
then lib.commitIdFromGitRepo gitRepo
else if lib.pathExists revisionFile then lib.fileContents revisionFile
else default;
diff --git a/nixpkgs/lib/types.nix b/nixpkgs/lib/types.nix
index 4872a676657..6fd6de7e1fd 100644
--- a/nixpkgs/lib/types.nix
+++ b/nixpkgs/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
@@ -305,29 +340,79 @@ rec {
let
padWidth = stringLength (toString (length def.value));
unnamed = i: unnamedPrefix + fixedWidthNumber padWidth i;
+ anyString = placeholder "name";
+ nameAttrs = [
+ { path = [ "environment" "etc" ];
+ name = "target";
+ }
+ { path = [ "containers" anyString "bindMounts" ];
+ name = "mountPoint";
+ }
+ { path = [ "programs" "ssh" "knownHosts" ];
+ # hostNames is actually a list so we would need to handle it only when singleton
+ name = "hostNames";
+ }
+ { path = [ "fileSystems" ];
+ name = "mountPoint";
+ }
+ { path = [ "boot" "specialFileSystems" ];
+ name = "mountPoint";
+ }
+ { path = [ "services" "znapzend" "zetup" ];
+ name = "dataset";
+ }
+ { path = [ "services" "znapzend" "zetup" anyString "destinations" ];
+ name = "label";
+ }
+ { path = [ "services" "geoclue2" "appConfig" ];
+ name = "desktopID";
+ }
+ ];
+ matched = let
+ equals = a: b: b == anyString || a == b;
+ fallback = { name = "name"; };
+ in findFirst ({ path, ... }: all (v: v == true) (zipListsWith equals loc path)) fallback nameAttrs;
+ nameAttr = matched.name;
+ nameValueOld = value:
+ if isList value then
+ if length value > 0 then
+ "[ " + concatMapStringsSep " " escapeNixString value + " ]"
+ else
+ "[ ]"
+ else
+ escapeNixString value;
+ nameValueNew = value: unnamed:
+ if isList value then
+ if length value > 0 then
+ head value
+ else
+ unnamed
+ else
+ value;
res =
{ inherit (def) file;
value = listToAttrs (
imap1 (elemIdx: elem:
- { name = elem.name or (unnamed elemIdx);
+ { name = nameValueNew (elem.${nameAttr} or (unnamed elemIdx)) (unnamed elemIdx);
value = elem;
}) def.value);
};
option = concatStringsSep "." loc;
sample = take 3 def.value;
- list = concatMapStrings (x: ''{ name = "${x.name or "unnamed"}"; ...} '') sample;
- set = concatMapStrings (x: ''${x.name or "unnamed"} = {...}; '') sample;
+ more = lib.optionalString (length def.value > 3) "... ";
+ list = concatMapStrings (x: ''{ ${nameAttr} = ${nameValueOld (x.${nameAttr} or "unnamed")}; ...} '') sample;
+ set = concatMapStrings (x: ''${nameValueNew (x.${nameAttr} or "unnamed") "unnamed"} = {...}; '') sample;
msg = ''
In file ${def.file}
a list is being assigned to the option config.${option}.
This will soon be an error as type loaOf is deprecated.
- See https://git.io/fj2zm for more information.
+ See https://github.com/NixOS/nixpkgs/pull/63103 for more information.
Do
${option} =
- { ${set}...}
+ { ${set}${more}}
instead of
${option} =
- [ ${list}...]
+ [ ${list}${more}]
'';
in
lib.warn msg res
@@ -339,6 +424,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 +436,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 +454,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);
@@ -392,14 +480,16 @@ rec {
else unify (if shorthandOnlyDefinesConfig then { config = value; } else value);
allModules = defs: modules ++ imap1 (n: { value, file }:
- # Annotate the value with the location of its definition for better error messages
- coerce (lib.modules.unifyModuleSyntax file "${toString file}-${toString n}") value
+ if isAttrs value || isFunction value then
+ # Annotate the value with the location of its definition for better error messages
+ coerce (lib.modules.unifyModuleSyntax file "${toString file}-${toString n}") value
+ else value
) defs;
in
mkOptionType rec {
name = "submodule";
- check = x: isAttrs x || isFunction x;
+ check = x: isAttrs x || isFunction x || path.check x;
merge = loc: defs:
(evalModules {
modules = allModules defs;
@@ -407,6 +497,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,
@@ -499,7 +590,7 @@ rec {
tail' = tail ts;
in foldl' either head' tail';
- # Either value of type `finalType` or `coercedType`, the latter is
+ # Either value of type `coercedType` or `finalType`, the former is
# converted to `finalType` using `coerceFunc`.
coercedTo = coercedType: coerceFunc: finalType:
assert lib.assertMsg (coercedType.getSubModules == null)
@@ -508,13 +599,14 @@ rec {
mkOptionType rec {
name = "coercedTo";
description = "${finalType.description} or ${coercedType.description} convertible to it";
- check = x: finalType.check x || (coercedType.check x && finalType.check (coerceFunc x));
+ check = x: (coercedType.check x && finalType.check (coerceFunc x)) || finalType.check x;
merge = loc: defs:
let
coerceVal = val:
- if finalType.check val then val
- else coerceFunc val;
+ if coercedType.check val then coerceFunc val
+ else 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);