summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/attrsets.nix51
-rw-r--r--lib/cli.nix157
-rw-r--r--lib/customisation.nix19
-rw-r--r--lib/debug.nix6
-rw-r--r--lib/default.nix16
-rw-r--r--lib/licenses.nix27
-rw-r--r--lib/options.nix18
-rw-r--r--lib/strings.nix23
-rw-r--r--lib/systems/default.nix7
-rw-r--r--lib/systems/examples.nix31
-rw-r--r--lib/systems/inspect.nix8
-rw-r--r--lib/systems/parse.nix12
-rw-r--r--lib/tests/misc.nix97
-rwxr-xr-xlib/tests/modules.sh82
-rw-r--r--lib/trivial.nix39
-rw-r--r--lib/types.nix2885
16 files changed, 1907 insertions, 1571 deletions
diff --git a/lib/attrsets.nix b/lib/attrsets.nix
index b05f8a2f867e..0e79a3b5a39b 100644
--- a/lib/attrsets.nix
+++ b/lib/attrsets.nix
@@ -1628,26 +1628,43 @@ rec {
binaryMerge 0 (length list);
/**
- Does the same as the update operator `//` except that attributes are
- merged until the given predicate is verified. The predicate should
- accept 3 arguments which are the path to reach the attribute, a part of
- the first attribute set and a part of the second attribute set. When
- the predicate is satisfied, the value of the first attribute set is
- replaced by the value of the second attribute set.
+ Update `lhs` so that `rhs` wins for any given attribute path that occurs in both.
+
+ Unlike the `//` (update) operator, which operates on a single attribute set,
+ This function views its operands `lhs` and `rhs` as a mapping from attribute *paths*
+ to values.
+
+ The caller-provided function `pred` decides whether any given path is one of the following:
+
+ - `true`: a value in the mapping
+ - `false`: an attribute set whose purpose is to create the nesting structure.
# Inputs
`pred`
- : Predicate, taking the path to the current attribute as a list of strings for attribute names, and the two values at that path from the original arguments.
+ : Predicate function (of type `List String -> Any -> Any -> Bool`)
+
+ Inputs:
+
+ - `path : List String`: the path to the current attribute as a list of strings for attribute names
+ - `lhsAtPath : Any`: the value at that path in `lhs`; same as `getAttrFromPath path lhs`
+ - `rhsAtPath : Any`: the value at that path in `rhs`; same as `getAttrFromPath path rhs`
+
+ Output:
+
+ - `true`: `path` points to a value in the mapping, and `rhsAtPath` will appear in the return value of `recursiveUpdateUntil`
+ - `false`: `path` is part of the nesting structure and will be an attrset in the return value of `recursiveUpdateUntil`
+
+ `pred` is only called for `path`s that extend prefixes for which `pred` returned `false`.
`lhs`
- : Left attribute set of the merge.
+ : Left attribute set of the update.
`rhs`
- : Right attribute set of the merge.
+ : Right attribute set of the update.
# Type
@@ -1660,23 +1677,23 @@ rec {
## `lib.attrsets.recursiveUpdateUntil` usage example
```nix
- recursiveUpdateUntil (path: l: r: path == ["foo"]) {
- # first attribute set
+ recursiveUpdateUntil (path: lhs: rhs: path == ["foo"]) {
+ # left attribute set
foo.bar = 1;
foo.baz = 2;
bar = 3;
} {
- #second attribute set
+ # right attribute set
foo.bar = 1;
foo.quz = 2;
baz = 4;
}
=> {
- foo.bar = 1; # 'foo.*' from the second set
+ foo.bar = 1; # 'foo.*' from the 'right' set
foo.quz = 2; #
- bar = 3; # 'bar' from the first set
- baz = 4; # 'baz' from the second set
+ bar = 3; # 'bar' from the 'left' set
+ baz = 4; # 'baz' from the 'right' set
}
```
@@ -1688,9 +1705,9 @@ rec {
f =
attrPath:
zipAttrsWith (
- n: values:
+ name: values:
let
- here = attrPath ++ [ n ];
+ here = attrPath ++ [ name ];
in
if length values == 1 || pred here (elemAt values 1) (head values) then
head values
diff --git a/lib/cli.nix b/lib/cli.nix
index 4cf97ba7a25e..37f9e270a313 100644
--- a/lib/cli.nix
+++ b/lib/cli.nix
@@ -156,15 +156,18 @@
);
/**
- Converts the given attributes into a single shell-escaped command-line string.
- Similar to `toCommandLineGNU`, but returns a single escaped string instead of an array of arguments.
- For further reference see: [`lib.cli.toCommandLineGNU`](#function-library-lib.cli.toCommandLineGNU)
+ Converts the given attributes into a single shell-escaped command-line
+ string.
+ Similar to `toCommandLineGNU`, but returns a single escaped string instead
+ of a list of arguments.
+ For further reference see:
+ [`lib.cli.toCommandLineGNU`](#function-library-lib.cli.toCommandLineGNU)
*/
toCommandLineShellGNU =
options: attrs: lib.escapeShellArgs (lib.cli.toCommandLineGNU options attrs);
/**
- Converts an attribute set into a list of GNU-style command line options.
+ Converts an attribute set into a list of GNU-style command-line arguments.
`toCommandLineGNU` returns a list of string arguments.
@@ -238,31 +241,77 @@
lib.cli.toCommandLine optionFormat;
/**
- Converts the given attributes into a single shell-escaped command-line string.
- Similar to `toCommandLine`, but returns a single escaped string instead of an array of arguments.
- For further reference see: [`lib.cli.toCommandLine`](#function-library-lib.cli.toCommandLine)
+ Converts the given attributes into a single shell-escaped command-line
+ string.
+ Similar to `toCommandLine`, but returns a single escaped string instead of
+ a list of arguments.
+ For further reference see:
+ [`lib.cli.toCommandLine`](#function-library-lib.cli.toCommandLine)
*/
toCommandLineShell =
optionFormat: attrs: lib.escapeShellArgs (lib.cli.toCommandLine optionFormat attrs);
/**
- Converts an attribute set into a list of command line options.
+ Converts an attribute set into a list of command-line arguments.
- `toCommandLine` returns a list of string arguments.
+ This is the most general command-line construction helper in `lib.cli`.
+ It is parameterized by an `optionFormat` function, which defines how each
+ option name and its value are rendered.
+
+ All other helpers in this file are thin wrappers around this function.
+
+ `toCommandLine` returns a *flat list of strings*, suitable for use as `argv`
+ arguments or for further processing (e.g. shell escaping).
# Inputs
`optionFormat`
- : The option format that describes how options and their arguments should be formatted.
+ : A function that takes the option name and returns an option spec, where
+ the option spec is an attribute set describing how the option should be
+ rendered.
+
+ The returned attribute set must contain:
+
+ - `option` (string):
+ The option flag itself, e.g. `"-v"` or `"--verbose"`.
+
+ - `sep` (string or null):
+ How to separate the option from its argument.
+ If `null`, the option and its argument are returned as two separate
+ list elements.
+ If a string (e.g. `"="`), the option and argument are concatenated.
+
+ - `explicitBool` (bool):
+ Controls how boolean values are handled:
+ - `false`:
+ `true` emits only the option flag, `false` emits nothing.
+ - `true`:
+ both `true` and `false` are rendered as explicit arguments via
+ `formatArg`.
+
+ Optional fields:
+
+ - `formatArg`:
+ Converts the option value to a string.
+ Defaults to `lib.generators.mkValueStringDefault { }`.
`attrs`
- : The attributes to transform into arguments.
+ : An attribute set mapping option names to values.
+
+ Supported value types:
+ - null: omitted entirely
+ - bool: handled according to `explicitBool`
+ - list: each element is rendered as a separate occurrence of the option
+ - any other value: rendered as a single option argument
+
+ Empty attribute names are rejected.
# Examples
+
:::{.example}
- ## `lib.cli.toCommandLine` usage example
+ ## `lib.cli.toCommandLine` basic usage example
```nix
let
@@ -271,14 +320,26 @@
sep = "=";
explicitBool = true;
};
- in lib.cli.toCommandLine optionFormat {
+ in
+ lib.cli.toCommandLine optionFormat {
v = true;
- verbose = [true true false null];
+ verbose = [
+ true
+ true
+ false
+ null
+ ];
i = ".bak";
- testsuite = ["unit" "integration"];
- e = ["s/a/b/" "s/b/c/"];
+ testsuite = [
+ "unit"
+ "integration"
+ ];
+ e = [
+ "s/a/b/"
+ "s/b/c/"
+ ];
n = false;
- data = builtins.toJSON {id = 0;};
+ data = builtins.toJSON { id = 0; };
}
=> [
"-data={\"id\":0}"
@@ -294,8 +355,70 @@
"-verbose=false"
]
```
+ :::
+
+ :::{.example}
+ ## `lib.cli.toCommandLine` usage with a more complex option format
+ ```nix
+ let
+ optionFormat =
+ optionName:
+ let
+ isLong = builtins.stringLength optionName > 1;
+ in
+ {
+ option = if isLong then "--${optionName}" else "-${optionName}";
+ sep = if isLong then "=" else null;
+ explicitBool = true;
+ formatArg =
+ value:
+ if builtins.isAttrs value then
+ builtins.toJSON value
+ else
+ lib.generators.mkValueStringDefault { } value;
+ };
+ in
+ lib.cli.toCommandLine optionFormat {
+ v = true;
+ verbose = [
+ true
+ true
+ false
+ null
+ ];
+ n = false;
+ output = "result.txt";
+ testsuite = [
+ "unit"
+ "integration"
+ ];
+ data = {
+ id = 0;
+ name = "test";
+ };
+ }
+ => [
+ "--data={\"id\":0,\"name\":\"test\"}"
+ "-n"
+ "false"
+ "--output=result.txt"
+ "--testsuite=unit"
+ "--testsuite=integration"
+ "-v"
+ "true"
+ "--verbose=true"
+ "--verbose=true"
+ "--verbose=false"
+ ]
+ ```
:::
+
+ # See also
+
+ - `lib.cli.toCommandLineShell`
+ - `lib.cli.toCommandLineGNU`
+ - `lib.cli.toCommandLineShellGNU`
*/
toCommandLine =
optionFormat: attrs:
diff --git a/lib/customisation.nix b/lib/customisation.nix
index bb759fd1a0db..32bc70f61fde 100644
--- a/lib/customisation.nix
+++ b/lib/customisation.nix
@@ -156,8 +156,25 @@ rec {
let
# Creates a functor with the same arguments as f
mirrorArgs = mirrorFunctionArgs f;
+ # Recover overrider and additional attributes for f
+ # When f is a callable attribute set,
+ # it may contain its own `f.override` and additional attributes.
+ # This helper function recovers those attributes and decorate the overrider.
+ recoverMetadata =
+ if isAttrs f then
+ fDecorated:
+ # Preserve additional attributes for f
+ f
+ // fDecorated
+ # Decorate f.override if presented
+ // lib.optionalAttrs (f ? override) {
+ override = fdrv: makeOverridable (f.override fdrv);
+ }
+ else
+ id;
+ decorate = f': recoverMetadata (mirrorArgs f');
in
- mirrorArgs (
+ decorate (
origArgs:
let
result = f origArgs;
diff --git a/lib/debug.nix b/lib/debug.nix
index 8dac6bb727a0..775db5a32997 100644
--- a/lib/debug.nix
+++ b/lib/debug.nix
@@ -28,11 +28,15 @@ let
generators
id
mapAttrs
- trace
;
in
rec {
+ inherit (builtins)
+ trace
+ addErrorContext
+ unsafeGetAttrPos
+ ;
# -- TRACING --
diff --git a/lib/default.nix b/lib/default.nix
index 044277fa24f5..661f76bed0ef 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -105,21 +105,12 @@ let
# network
network = callLibs ./network;
- # TODO: For consistency, all builtins should also be available from a sub-library;
- # these are the only ones that are currently not
- inherit (builtins)
- addErrorContext
- isPath
- trace
- typeOf
- unsafeGetAttrPos
- ;
inherit (self.trivial)
id
const
pipe
concat
- or
+ "or"
and
xor
bitAnd
@@ -335,6 +326,7 @@ let
escape
escapeShellArg
escapeShellArgs
+ isPath
isStorePath
isStringLike
isValidPosixName
@@ -351,6 +343,7 @@ let
toUpper
toCamelCase
toSentenceCase
+ typeOf
addContextFrom
splitString
splitStringBy
@@ -518,6 +511,7 @@ let
assertOneOf
;
inherit (self.debug)
+ trace
traceIf
traceVal
traceValFn
@@ -528,6 +522,8 @@ let
traceValSeqN
traceValSeqNFn
traceFnSeqN
+ addErrorContext
+ unsafeGetAttrPos
runTests
testAllTrue
;
diff --git a/lib/licenses.nix b/lib/licenses.nix
index b67421054e8b..63fc954d50a6 100644
--- a/lib/licenses.nix
+++ b/lib/licenses.nix
@@ -64,6 +64,11 @@ lib.mapAttrs mkLicense (
free = false;
};
+ adobeDisplayPostScript = {
+ spdxId = "Adobe-Display-PostScript";
+ fullName = "Adobe Display PostScript License";
+ };
+
adobeUtopia = {
fullName = "Adobe Utopia Font License";
spdxId = "Adobe-Utopia";
@@ -501,6 +506,11 @@ lib.mapAttrs mkLicense (
fullName = "curl License";
};
+ dec3Clause = {
+ spdxId = "DEC-3-Clause";
+ fullName = "DEC 3-Clause License";
+ };
+
doc = {
spdxId = "DOC";
fullName = "DOC License";
@@ -727,6 +737,11 @@ lib.mapAttrs mkLicense (
spdxId = "HPND-sell-variant";
};
+ hpndSellVariantMitDisclaimerXserver = {
+ spdxId = "HPND-sell-MIT-disclaimer-xserver";
+ fullName = "Historical Permission Notice and Disclaimer - sell xserver variant with MIT disclaimer";
+ };
+
hpndDec = {
fullName = "Historical Permission Notice and Disclaimer - DEC variant";
spdxId = "HPND-DEC";
@@ -1113,6 +1128,13 @@ lib.mapAttrs mkLicense (
fullName = "Non-Profit Open Software License 3.0";
};
+ # NTP is basically HPND, but spdx and the OSI recognize it
+ # hpnd says "and without fee", ntp "with or without fee"
+ ntp = {
+ spdxId = "NTP";
+ fullName = "NTP License";
+ };
+
nvidiaCuda = {
shortName = "CUDA EULA";
fullName = "CUDA Toolkit End User License Agreement (EULA)";
@@ -1520,6 +1542,11 @@ lib.mapAttrs mkLicense (
fullName = "X11 License";
};
+ x11BsdClause = {
+ fullName = "X11 License with third BSD clause";
+ url = "https://gitlab.freedesktop.org/xorg/driver/xf86-video-geode/-/blob/d147c3f1b6907ae9db6f12853cedd450537d99d2/COPYING";
+ };
+
x11NoPermitPersons = {
spdxId = "X11-no-permit-persons";
fullName = "X11 no permit persons clause";
diff --git a/lib/options.nix b/lib/options.nix
index e6b51fb0603e..195ba79765e9 100644
--- a/lib/options.nix
+++ b/lib/options.nix
@@ -77,12 +77,12 @@ rec {
isOption = lib.isType "option";
/**
- Creates an Option attribute set. `mkOption` accepts an attribute set with the following keys:
+ Creates an Option declaration for use with the module system.
# Inputs
- Structured attribute set
- : Attribute set containing none or some of the following attributes.
+ Attribute set
+ : containing none or some of the following attributes.
`default`
: Optional default value used when no definition is given in the configuration.
@@ -122,16 +122,16 @@ rec {
`readOnly`
: Optional boolean indicating whether the option can be set only once.
- `...` (any other attribute)
- : Any other attribute is passed through to the resulting option attribute set.
-
# Examples
:::{.example}
## `lib.options.mkOption` usage example
```nix
- mkOption { } // => { _type = "option"; }
- mkOption { default = "foo"; } // => { _type = "option"; default = "foo"; }
+ mkOption { }
+ # => Empty option; type = types.anything
+
+ mkOption { default = "foo"; }
+ # => Same as above, with a default value
```
:::
@@ -433,7 +433,7 @@ rec {
else if all isAttrs list then
foldl' lib.mergeAttrs { } list
else if all isBool list then
- foldl' lib.or false list
+ foldl' lib."or" false list
else if all isString list then
lib.concatStrings list
else if all isInt list && all (x: x == head list) list then
diff --git a/lib/strings.nix b/lib/strings.nix
index f3c7018d4403..cd0027e1a165 100644
--- a/lib/strings.nix
+++ b/lib/strings.nix
@@ -1432,9 +1432,27 @@ rec {
:::
*/
escapeNixIdentifier =
+ let
+ # see https://nix.dev/manual/nix/2.26/language/identifiers#keywords
+ nixKeywords = [
+ "assert"
+ "else"
+ "if"
+ "in"
+ "inherit"
+ "let"
+ "or"
+ "rec"
+ "then"
+ "with"
+ ];
+ in
s:
# Regex from https://github.com/NixOS/nix/blob/d048577909e383439c2549e849c5c2f2016c997e/src/libexpr/lexer.l#L91
- if match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null then s else escapeNixString s;
+ if (match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null) && (!lib.elem s nixKeywords) then
+ s
+ else
+ escapeNixString s;
/**
Escapes a string `s` such that it is safe to include verbatim in an XML
@@ -2097,6 +2115,9 @@ rec {
`feature`
: The feature to be set
+ `feature`
+ : The feature to be set
+
`value`
: The desired value
diff --git a/lib/systems/default.nix b/lib/systems/default.nix
index cd1448d811fa..f8bc6ac987b3 100644
--- a/lib/systems/default.nix
+++ b/lib/systems/default.nix
@@ -102,13 +102,10 @@ let
# assume compatible cpu have all the instructions included
final.parsed.cpu == platform.parsed.cpu
->
- # if both have gcc.arch defined, check whether final can execute the given platform
+ # if platform has gcc.arch, final must also have and can execute the gcc.arch of platform
(
- (final ? gcc.arch && platform ? gcc.arch)
- -> architectures.canExecute final.gcc.arch platform.gcc.arch
+ platform ? gcc.arch -> final ? gcc.arch && architectures.canExecute final.gcc.arch platform.gcc.arch
)
- # if platform has gcc.arch defined but final doesn't, don't assume it can be executed
- || (platform ? gcc.arch -> !(final ? gcc.arch))
);
isCompatible =
diff --git a/lib/systems/examples.nix b/lib/systems/examples.nix
index d05c05910523..64f329f2a3db 100644
--- a/lib/systems/examples.nix
+++ b/lib/systems/examples.nix
@@ -341,32 +341,49 @@ rec {
# Windows
#
- # 32 bit mingw-w64
- mingw32 = {
+ # mingw-w64 with MSVCRT for i686
+ mingw-msvcrt-i686 = {
config = "i686-w64-mingw32";
libc = "msvcrt"; # This distinguishes the mingw (non posix) toolchain
};
- # 64 bit mingw-w64
- mingwW64 = {
+ # mingw-w64 with MSVCRT for x86_64
+ mingw-msvcrt-x86_64 = {
# That's the triplet they use in the mingw-w64 docs.
config = "x86_64-w64-mingw32";
libc = "msvcrt"; # This distinguishes the mingw (non posix) toolchain
};
- ucrt64 = {
+ # mingw-w64 with UCRT for x86_64, default compiler
+ mingw-ucrt-x86_64 = {
config = "x86_64-w64-mingw32";
libc = "ucrt"; # This distinguishes the mingw (non posix) toolchain
};
- # LLVM-based mingw-w64 for ARM
- ucrtAarch64 = {
+ # mingw-w64 with UCRT for x86_64, LLVM
+ mingw-ucrt-x86_64-llvm = {
+ config = "x86_64-w64-mingw32";
+ libc = "ucrt";
+ rust.rustcTarget = "x86_64-pc-windows-gnullvm";
+ useLLVM = true;
+ };
+
+ # mingw-w64 with ucrt for Aarch64, default compiler (which is LLVM
+ # because GCC does not support this platform yet).
+ mingw-ucrt-aarch64 = {
config = "aarch64-w64-mingw32";
libc = "ucrt";
rust.rustcTarget = "aarch64-pc-windows-gnullvm";
useLLVM = true;
};
+ # mingw-64 back compat
+ # TODO: Warn after 26.05, and remove after 26.11.
+ mingw32 = mingw-msvcrt-i686;
+ mingwW64 = mingw-msvcrt-x86_64;
+ ucrt64 = mingw-ucrt-x86_64;
+ ucrtAarch64 = mingw-ucrt-aarch64;
+
# Target the MSVC ABI
x86_64-windows = {
config = "x86_64-pc-windows-msvc";
diff --git a/lib/systems/inspect.nix b/lib/systems/inspect.nix
index ad6ff2b380a8..0a8e1ac8e702 100644
--- a/lib/systems/inspect.nix
+++ b/lib/systems/inspect.nix
@@ -437,6 +437,14 @@ rec {
isMacho = {
kernel.execFormat = execFormats.macho;
};
+ isPE = {
+ kernel.execFormat = execFormats.pe;
+ };
+
+ isEabi = {
+ abi.eabi = true;
+ };
+
};
# given two patterns, return a pattern which is their logical AND.
diff --git a/lib/systems/parse.nix b/lib/systems/parse.nix
index c76aa592fc28..86f8c169d901 100644
--- a/lib/systems/parse.nix
+++ b/lib/systems/parse.nix
@@ -662,15 +662,19 @@ rec {
# On ARM, this corresponds to ARMEABI.
eabi = {
float = "soft";
+ eabi = true;
};
eabihf = {
float = "hard";
+ eabi = true;
};
# Other architectures should use ELF in embedded situations.
elf = { };
- androideabi = { };
+ androideabi = {
+ eabi = true;
+ };
android = {
assertions = [
{
@@ -684,9 +688,11 @@ rec {
gnueabi = {
float = "soft";
+ eabi = true;
};
gnueabihf = {
float = "hard";
+ eabi = true;
};
gnu = {
assertions = [
@@ -730,17 +736,21 @@ rec {
musleabi = {
float = "soft";
+ eabi = true;
};
musleabihf = {
float = "hard";
+ eabi = true;
};
musl = { };
uclibceabi = {
float = "soft";
+ eabi = true;
};
uclibceabihf = {
float = "hard";
+ eabi = true;
};
uclibc = { };
diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix
index 8c41f13e0a58..6c1a8c54a547 100644
--- a/lib/tests/misc.nix
+++ b/lib/tests/misc.nix
@@ -203,6 +203,85 @@ runTests {
};
};
+ testOverridePreserveFunctionMetadata =
+ let
+ toCallableAttrs = f: setFunctionArgs f (functionArgs f);
+ constructDefinition =
+ {
+ a ? 3,
+ }:
+ toCallableAttrs (
+ {
+ b ? 5,
+ }:
+ {
+ inherit a b;
+ }
+ )
+ // {
+ inherit a;
+ c = 7;
+ };
+ construct0 = makeOverridable constructDefinition { };
+ construct1 = makeOverridable construct0;
+ construct0p = construct0.override { a = 11; };
+ construct1p = construct1.override { a = 11; };
+ in
+ {
+ expr = {
+ construct-metadata = {
+ inherit (construct1) a c;
+ };
+ construct-overridden-metadata = {
+ v = construct0p.a;
+ inherit (construct1p) a c;
+ };
+ construct-overridden-result-overrider = {
+ result-overriders-exist = mapAttrs (_: f: (f { }) ? override) {
+ inherit construct1 construct1p;
+ };
+ result-overrider-functionality = {
+ overridden = {
+ inherit ((construct1p { }).override { b = 13; }) a b;
+ };
+ direct = {
+ inherit (construct1p { b = 13; }) a b;
+ };
+ v = {
+ inherit (construct0p { b = 13; }) a b;
+ };
+ };
+ };
+ };
+ expected = {
+ construct-metadata = {
+ inherit (construct0) a c;
+ };
+ construct-overridden-metadata = {
+ v = 11;
+ inherit (construct0p) a c;
+ };
+ construct-overridden-result-overrider = {
+ result-overriders-exist = {
+ construct1 = true;
+ construct1p = true;
+ };
+ result-overrider-functionality = {
+ overridden = {
+ inherit (construct0p { b = 13; }) a b;
+ };
+ direct = {
+ inherit (construct0p { b = 13; }) a b;
+ };
+ v = {
+ a = 11;
+ b = 13;
+ };
+ };
+ };
+ };
+ };
+
testCallPackageWithOverridePreservesArguments =
let
f =
@@ -786,6 +865,21 @@ runTests {
expected = "'á'";
};
+ testEscapeNixIdentifierNoQuote = {
+ expr = strings.escapeNixIdentifier "foo";
+ expected = ''foo'';
+ };
+
+ testEscapeNixIdentifierNumber = {
+ expr = strings.escapeNixIdentifier "1foo";
+ expected = ''"1foo"'';
+ };
+
+ testEscapeNixIdentifierKeyword = {
+ expr = strings.escapeNixIdentifier "assert";
+ expected = ''"assert"'';
+ };
+
testSplitStringsDerivation = {
expr = lib.dropEnd 1 (strings.splitString "/" dummyDerivation);
expected = strings.splitString "/" builtins.storeDir;
@@ -2706,6 +2800,7 @@ runTests {
];
emptylist = [ ];
attrs = {
+ "assert" = false;
foo = null;
"foo b/ar" = "baz";
};
@@ -2725,7 +2820,7 @@ runTests {
functionArgs = "<function, args: {arg?, foo}>";
list = "[ 3 4 ${function} [ false ] ]";
emptylist = "[ ]";
- attrs = "{ foo = null; \"foo b/ar\" = \"baz\"; }";
+ attrs = "{ \"assert\" = false; foo = null; \"foo b/ar\" = \"baz\"; }";
emptyattrs = "{ }";
drv = "<derivation ${deriv.name}>";
};
diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh
index f2e6d3b452b5..8ea8adab72c9 100755
--- a/lib/tests/modules.sh
+++ b/lib/tests/modules.sh
@@ -59,7 +59,11 @@ evalConfig() {
local attr=$1
shift
local script="import ./default.nix { modules = [ $* ];}"
- local-nix-instantiate -E "$script" -A "$attr"
+ if [ "${ABORT_ON_WARN-0}" = "1" ]; then
+ local-nix-instantiate --option abort-on-warn true -E "$script" -A "$attr"
+ else
+ local-nix-instantiate -E "$script" -A "$attr"
+ fi
}
reportFailure() {
@@ -495,49 +499,49 @@ checkConfigError 'The option .mergedName. in .*\.nix. is already declared in .*\
# - non-merged types
# - nestedTypes elemType
# attrsWith
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.attrsWith.type.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedAttrsWith.type.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.attrsWith.type.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedAttrsWith.type.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.attrsWith.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedAttrsWith.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.attrsWith.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedAttrsWith.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
# listOf
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.listOf.type.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedListOf.type.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.listOf.type.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedListOf.type.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.listOf.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedListOf.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.listOf.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedListOf.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
# unique / uniq
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.unique.type.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedUnique.type.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.unique.type.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedUnique.type.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.unique.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedUnique.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.unique.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedUnique.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
# nullOr
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.nullOr.type.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedNullOr.type.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.nullOr.type.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedNullOr.type.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.nullOr.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedNullOr.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.nullOr.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedNullOr.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
# functionTo
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.functionTo.type.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedFunctionTo.type.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.functionTo.type.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedFunctionTo.type.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.functionTo.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedFunctionTo.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.functionTo.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedFunctionTo.type.nestedTypes.elemType.functor.wrapped ./deprecated-wrapped.nix
# coercedTo
# Note: test 'nestedTypes.finalType' and 'nestedTypes.coercedType'
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.coercedTo.type.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.coercedTo.type.nestedTypes.finalType.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.coercedTo.type.nestedTypes.coercedType.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.coercedTo.type.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.coercedTo.type.nestedTypes.finalType.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.coercedTo.type.nestedTypes.coercedType.functor.wrapped ./deprecated-wrapped.nix
# either
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.either.type.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedEither.type.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.either.type.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedEither.type.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.either.type.nestedTypes.left.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.either.type.nestedTypes.right.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedEither.type.nestedTypes.left.functor.wrapped ./deprecated-wrapped.nix
-NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedEither.type.nestedTypes.right.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.either.type.nestedTypes.left.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.either.type.nestedTypes.right.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedEither.type.nestedTypes.left.functor.wrapped ./deprecated-wrapped.nix
+ABORT_ON_WARN=1 checkConfigError 'The deprecated `.*functor.wrapped` attribute .*is accessed, use `.*nestedTypes.elemType` instead.' options.mergedEither.type.nestedTypes.right.functor.wrapped ./deprecated-wrapped.nix
# Even with multiple assignments, a type error should be thrown if any of them aren't valid
checkConfigError 'A definition for option .* is not of type .*' \
@@ -575,24 +579,24 @@ checkConfigOutput '^10$' config.free.yyy.bar ./freeform-submodules.nix
# Regression of either, due to freeform not beeing checked previously
checkConfigOutput '^"foo"$' config.either.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix
-NIX_ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.either.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix
+ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.either.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix
checkConfigOutput '^"foo"$' config.eitherBehindNullor.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix
-NIX_ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.eitherBehindNullor.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix
+ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.eitherBehindNullor.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix
checkConfigOutput '^"foo"$' config.oneOf.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix
-NIX_ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.oneOf.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix
+ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.oneOf.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix
checkConfigOutput '^"foo"$' config.number.str ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix
-NIX_ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.number.str ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix
+ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.number.str ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong.nix
checkConfigOutput '^42$' config.either.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix
-NIX_ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.either.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix
+ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.either.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix
checkConfigOutput '^42$' config.eitherBehindNullor.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix
-NIX_ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.eitherBehindNullor.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix
+ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.eitherBehindNullor.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix
checkConfigOutput '^42$' config.oneOf.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix
-NIX_ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.oneOf.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix
+ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.oneOf.int ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix
checkConfigOutput '^42$' config.number.str ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix
-NIX_ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.number.str ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix
+ABORT_ON_WARN=1 checkConfigError "One or more definitions did not pass the type-check of the \'either\' type" config.number.str ./freeform-deprecated-malicous.nix ./freeform-deprecated-malicous-wrong2.nix
# Value OK: Fail if a warning is emitted
-NIX_ABORT_ON_WARN=1 checkConfigOutput "^42$" config.number.int ./freeform-attrsof-either.nix
+ABORT_ON_WARN=1 checkConfigOutput "^42$" config.number.int ./freeform-attrsof-either.nix
## types.anything
diff --git a/lib/trivial.nix b/lib/trivial.nix
index 317b993f12d2..45a8ec9169f5 100644
--- a/lib/trivial.nix
+++ b/lib/trivial.nix
@@ -18,6 +18,23 @@ let
;
in
{
+ # Pull in some builtins not included elsewhere.
+ inherit (builtins)
+ pathExists
+ readFile
+ isBool
+ isInt
+ isFloat
+ add
+ sub
+ lessThan
+ seq
+ deepSeq
+ genericClosure
+ bitAnd
+ bitOr
+ bitXor
+ ;
## Simple (higher order) functions
@@ -180,7 +197,7 @@ in
: 2\. Function argument
*/
- or = x: y: x || y;
+ "or" = x: y: x || y;
/**
boolean “and”
@@ -388,24 +405,6 @@ in
*/
mapNullable = f: a: if a == null then a else f a;
- # Pull in some builtins not included elsewhere.
- inherit (builtins)
- pathExists
- readFile
- isBool
- isInt
- isFloat
- add
- sub
- lessThan
- seq
- deepSeq
- genericClosure
- bitAnd
- bitOr
- bitXor
- ;
-
## nixpkgs version strings
/**
@@ -433,7 +432,7 @@ in
*/
oldestSupportedRelease =
# Update on master only. Do not backport.
- 2505;
+ 2511;
/**
Whether a feature is supported in all supported releases (at the time of
diff --git a/lib/types.nix b/lib/types.nix
index bc6e28ed9363..bd9ac93df472 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -18,6 +18,7 @@ let
throwIf
toDerivation
toList
+ types
;
inherit (lib.lists)
concatLists
@@ -87,7 +88,7 @@ let
{
inherit name payload;
wrappedDeprecationMessage = makeWrappedDeprecationMessage payload;
- type = outer_types.types.${name};
+ type = types.${name};
binOp =
a: b:
let
@@ -134,1568 +135,1568 @@ let
baseType // { check = value: /* your check */; }
'';
- outer_types = rec {
- isType = type: x: (x._type or "") == type;
+in
+rec {
+ isType = type: x: (x._type or "") == type;
- setType =
- typeName: value:
- value
- // {
- _type = typeName;
- };
+ setType =
+ typeName: value:
+ value
+ // {
+ _type = typeName;
+ };
- # Default type merging function
- # takes two type functors and return the merged type
- defaultTypeMerge =
- f: f':
- let
- mergedWrapped = f.wrapped.typeMerge f'.wrapped.functor;
- mergedPayload = f.binOp f.payload f'.payload;
+ # Default type merging function
+ # takes two type functors and return the merged type
+ defaultTypeMerge =
+ f: f':
+ let
+ mergedWrapped = f.wrapped.typeMerge f'.wrapped.functor;
+ mergedPayload = f.binOp f.payload f'.payload;
- hasPayload =
- assert (f'.payload != null) == (f.payload != null);
- f.payload != null;
- hasWrapped =
- assert (f'.wrapped != null) == (f.wrapped != null);
- f.wrapped != null;
+ hasPayload =
+ assert (f'.payload != null) == (f.payload != null);
+ f.payload != null;
+ hasWrapped =
+ assert (f'.wrapped != null) == (f.wrapped != null);
+ f.wrapped != null;
- typeFromPayload = if mergedPayload == null then null else f.type mergedPayload;
- typeFromWrapped = if mergedWrapped == null then null else f.type mergedWrapped;
- in
- # Abort early: cannot merge different types
- if f.name != f'.name then
- null
- else
+ typeFromPayload = if mergedPayload == null then null else f.type mergedPayload;
+ typeFromWrapped = if mergedWrapped == null then null else f.type mergedWrapped;
+ in
+ # Abort early: cannot merge different types
+ if f.name != f'.name then
+ null
+ else
- if hasPayload then
- # Just return the payload if returning wrapped is deprecated
- if f ? wrappedDeprecationMessage then
- typeFromPayload
- else if hasWrapped then
- # Has both wrapped and payload
- throw ''
- Type ${f.name} defines both `functor.payload` and `functor.wrapped` at the same time, which is not supported.
+ if hasPayload then
+ # Just return the payload if returning wrapped is deprecated
+ if f ? wrappedDeprecationMessage then
+ typeFromPayload
+ else if hasWrapped then
+ # Has both wrapped and payload
+ throw ''
+ Type ${f.name} defines both `functor.payload` and `functor.wrapped` at the same time, which is not supported.
- Use either `functor.payload` or `functor.wrapped` but not both.
+ Use either `functor.payload` or `functor.wrapped` but not both.
- If your code worked before remove either `functor.wrapped` or `functor.payload` from the type definition.
- ''
- else
- typeFromPayload
- else if hasWrapped then
- typeFromWrapped
+ If your code worked before remove either `functor.wrapped` or `functor.payload` from the type definition.
+ ''
else
- f.type;
+ typeFromPayload
+ else if hasWrapped then
+ typeFromWrapped
+ else
+ f.type;
- # Default type functor
- defaultFunctor = name: {
- inherit name;
- type = types.${name} or null;
- wrapped = null;
- payload = null;
- binOp = a: b: null;
+ # Default type functor
+ defaultFunctor = name: {
+ inherit name;
+ type = lib.types.${name} or null;
+ wrapped = null;
+ payload = null;
+ binOp = a: b: null;
+ };
+
+ isOptionType = isType "option-type";
+ mkOptionType =
+ {
+ # Human-readable representation of the type, should be equivalent to
+ # the type function name.
+ name,
+ # Description of the type, defined recursively by embedding the wrapped type if any.
+ description ? null,
+ # A hint for whether or not this description needs parentheses. Possible values:
+ # - "noun": a noun phrase
+ # Example description: "positive integer",
+ # - "conjunction": a phrase with a potentially ambiguous "or" connective
+ # Example description: "int or string"
+ # - "composite": a phrase with an "of" connective
+ # Example description: "list of string"
+ # - "nonRestrictiveClause": a noun followed by a comma and a clause
+ # Example description: "positive integer, meaning >0"
+ # See the `optionDescriptionPhrase` function.
+ descriptionClass ? null,
+ # DO NOT USE WITHOUT KNOWING WHAT YOU ARE DOING!
+ # Function applied to each definition that must return false when a definition
+ # does not match the type. It should not check more than the root of the value,
+ # because checking nested values reduces laziness, leading to unnecessary
+ # infinite recursions in the module system.
+ # Further checks of nested values should be performed by throwing in
+ # the merge function.
+ # Strict and deep type checking can be performed by calling lib.deepSeq on
+ # the merged value.
+ #
+ # See https://github.com/NixOS/nixpkgs/pull/6794 that introduced this change,
+ # https://github.com/NixOS/nixpkgs/pull/173568 and
+ # https://github.com/NixOS/nixpkgs/pull/168295 that attempted to revert this,
+ # https://github.com/NixOS/nixpkgs/issues/191124 and
+ # https://github.com/NixOS/nixos-search/issues/391 for what happens if you ignore
+ # this disclaimer.
+ check ? (x: true),
+ # Merge a list of definitions together into a single value.
+ # This function is called with two arguments: the location of
+ # the option in the configuration as a list of strings
+ # (e.g. ["boot" "loader "grub" "enable"]), and a list of
+ # 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 attrset of sub-options. Used to generate
+ # documentation.
+ getSubOptions ? prefix: { },
+ # List of modules if any, or null if none.
+ getSubModules ? null,
+ # Function for building the same option type with a different list of
+ # modules.
+ substSubModules ? m: null,
+ # Function that merge type declarations.
+ # internal, takes a functor as argument and returns the merged type.
+ # returning null means the type is not mergeable
+ typeMerge ? defaultTypeMerge functor,
+ # The type functor.
+ # internal, representation of the type as an attribute set.
+ # name: name of the type
+ # type: type function.
+ # wrapped: the type wrapped in case of compound types.
+ # payload: values of the type, two payloads of the same type must be
+ # combinable with the binOp binary operation.
+ # binOp: binary operation that merge two payloads of the same type.
+ functor ? defaultFunctor name,
+ # The deprecation message to display when this type is used by an option
+ # If null, the type isn't deprecated
+ deprecationMessage ? null,
+ # The types that occur in the definition of this type. This is used to
+ # issue deprecation warnings recursively. Can also be used to reuse
+ # nested types
+ nestedTypes ? { },
+ }:
+ {
+ _type = "option-type";
+ inherit
+ name
+ check
+ merge
+ emptyValue
+ getSubOptions
+ getSubModules
+ substSubModules
+ typeMerge
+ deprecationMessage
+ nestedTypes
+ descriptionClass
+ ;
+ functor =
+ if functor ? wrappedDeprecationMessage then
+ functor
+ // {
+ wrapped = functor.wrappedDeprecationMessage {
+ loc = null;
+ };
+ }
+ else
+ functor;
+ description = if description == null then name else description;
};
- isOptionType = isType "option-type";
- mkOptionType =
- {
- # Human-readable representation of the type, should be equivalent to
- # the type function name.
- name,
- # Description of the type, defined recursively by embedding the wrapped type if any.
- description ? null,
- # A hint for whether or not this description needs parentheses. Possible values:
- # - "noun": a noun phrase
- # Example description: "positive integer",
- # - "conjunction": a phrase with a potentially ambiguous "or" connective
- # Example description: "int or string"
- # - "composite": a phrase with an "of" connective
- # Example description: "list of string"
- # - "nonRestrictiveClause": a noun followed by a comma and a clause
- # Example description: "positive integer, meaning >0"
- # See the `optionDescriptionPhrase` function.
- descriptionClass ? null,
- # DO NOT USE WITHOUT KNOWING WHAT YOU ARE DOING!
- # Function applied to each definition that must return false when a definition
- # does not match the type. It should not check more than the root of the value,
- # because checking nested values reduces laziness, leading to unnecessary
- # infinite recursions in the module system.
- # Further checks of nested values should be performed by throwing in
- # the merge function.
- # Strict and deep type checking can be performed by calling lib.deepSeq on
- # the merged value.
- #
- # See https://github.com/NixOS/nixpkgs/pull/6794 that introduced this change,
- # https://github.com/NixOS/nixpkgs/pull/173568 and
- # https://github.com/NixOS/nixpkgs/pull/168295 that attempted to revert this,
- # https://github.com/NixOS/nixpkgs/issues/191124 and
- # https://github.com/NixOS/nixos-search/issues/391 for what happens if you ignore
- # this disclaimer.
- check ? (x: true),
- # Merge a list of definitions together into a single value.
- # This function is called with two arguments: the location of
- # the option in the configuration as a list of strings
- # (e.g. ["boot" "loader "grub" "enable"]), and a list of
- # 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 attrset of sub-options. Used to generate
- # documentation.
- getSubOptions ? prefix: { },
- # List of modules if any, or null if none.
- getSubModules ? null,
- # Function for building the same option type with a different list of
- # modules.
- substSubModules ? m: null,
- # Function that merge type declarations.
- # internal, takes a functor as argument and returns the merged type.
- # returning null means the type is not mergeable
- typeMerge ? defaultTypeMerge functor,
- # The type functor.
- # internal, representation of the type as an attribute set.
- # name: name of the type
- # type: type function.
- # wrapped: the type wrapped in case of compound types.
- # payload: values of the type, two payloads of the same type must be
- # combinable with the binOp binary operation.
- # binOp: binary operation that merge two payloads of the same type.
- functor ? defaultFunctor name,
- # The deprecation message to display when this type is used by an option
- # If null, the type isn't deprecated
- deprecationMessage ? null,
- # The types that occur in the definition of this type. This is used to
- # issue deprecation warnings recursively. Can also be used to reuse
- # nested types
- nestedTypes ? { },
- }:
- {
- _type = "option-type";
- inherit
- name
- check
- merge
- emptyValue
- getSubOptions
- getSubModules
- substSubModules
- typeMerge
- deprecationMessage
- nestedTypes
- descriptionClass
- ;
- functor =
- if functor ? wrappedDeprecationMessage then
- functor
- // {
- wrapped = functor.wrappedDeprecationMessage {
- loc = null;
- };
- }
- else
- functor;
- description = if description == null then name else description;
- };
+ # optionDescriptionPhrase :: (str -> bool) -> optionType -> str
+ #
+ # Helper function for producing unambiguous but readable natural language
+ # descriptions of types.
+ #
+ # Parameters
+ #
+ # optionDescriptionPhase unparenthesize optionType
+ #
+ # `unparenthesize`: A function from descriptionClass string to boolean.
+ # It must return true when the class of phrase will fit unambiguously into
+ # the description of the caller.
+ #
+ # `optionType`: The option type to parenthesize or not.
+ # The option whose description we're returning.
+ #
+ # Returns value
+ #
+ # The description of the `optionType`, with parentheses if there may be an
+ # ambiguity.
+ optionDescriptionPhrase =
+ unparenthesize: t:
+ if unparenthesize (t.descriptionClass or null) then t.description else "(${t.description})";
- # optionDescriptionPhrase :: (str -> bool) -> optionType -> str
- #
- # Helper function for producing unambiguous but readable natural language
- # descriptions of types.
- #
- # Parameters
- #
- # optionDescriptionPhase unparenthesize optionType
- #
- # `unparenthesize`: A function from descriptionClass string to boolean.
- # It must return true when the class of phrase will fit unambiguously into
- # the description of the caller.
- #
- # `optionType`: The option type to parenthesize or not.
- # The option whose description we're returning.
- #
- # Returns value
- #
- # The description of the `optionType`, with parentheses if there may be an
- # ambiguity.
- optionDescriptionPhrase =
- unparenthesize: t:
- if unparenthesize (t.descriptionClass or null) then t.description else "(${t.description})";
+ noCheckForDocsModule = {
+ # When generating documentation, our goal isn't to check anything.
+ # Quite the opposite in fact. Generating docs is somewhat of a
+ # challenge, evaluating modules in a *lacking* context. Anything
+ # that makes the docs avoid an error is a win.
+ config._module.check = lib.mkForce false;
+ _file = "<built-in module that disables checks for the purpose of documentation generation>";
+ };
- noCheckForDocsModule = {
- # When generating documentation, our goal isn't to check anything.
- # Quite the opposite in fact. Generating docs is somewhat of a
- # challenge, evaluating modules in a *lacking* context. Anything
- # that makes the docs avoid an error is a win.
- config._module.check = lib.mkForce false;
- _file = "<built-in module that disables checks for the purpose of documentation generation>";
- };
+ # When adding new types don't forget to document them in
+ # nixos/doc/manual/development/option-types.section.md!
- # When adding new types don't forget to document them in
- # nixos/doc/manual/development/option-types.section.md!
- types = rec {
+ raw = mkOptionType {
+ name = "raw";
+ description = "raw value";
+ descriptionClass = "noun";
+ check = value: true;
+ merge = mergeOneOption;
+ };
- raw = mkOptionType {
- name = "raw";
- description = "raw value";
- descriptionClass = "noun";
- check = value: true;
- merge = mergeOneOption;
- };
+ anything = mkOptionType {
+ name = "anything";
+ description = "anything";
+ descriptionClass = "noun";
+ check = value: true;
+ merge =
+ loc: defs:
+ let
+ getType =
+ value: if isAttrs value && isStringLike value then "stringCoercibleSet" else builtins.typeOf value;
- anything = mkOptionType {
- name = "anything";
- description = "anything";
- descriptionClass = "noun";
- check = value: true;
- merge =
- loc: defs:
- let
- getType =
- value: if isAttrs value && isStringLike value then "stringCoercibleSet" else builtins.typeOf value;
+ # Returns the common type of all definitions, throws an error if they
+ # don't have the same type
+ commonType = foldl' (
+ type: def:
+ if getType def.value == type then
+ type
+ else
+ throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}"
+ ) (getType (head defs).value) defs;
- # Returns the common type of all definitions, throws an error if they
- # don't have the same type
- commonType = foldl' (
- type: def:
- if getType def.value == type then
- type
- else
- throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}"
- ) (getType (head defs).value) defs;
+ mergeFunction =
+ {
+ # Recursively merge attribute sets
+ set = (attrsOf anything).merge;
+ # This is the type of packages, only accept a single definition
+ stringCoercibleSet = mergeOneOption;
+ lambda =
+ loc: defs: arg:
+ anything.merge (loc ++ [ "<function body>" ]) (
+ map (def: {
+ file = def.file;
+ value = def.value arg;
+ }) defs
+ );
+ # Otherwise fall back to only allowing all equal definitions
+ }
+ .${commonType} or mergeEqualOption;
+ in
+ mergeFunction loc defs;
+ };
- mergeFunction =
- {
- # Recursively merge attribute sets
- set = (attrsOf anything).merge;
- # This is the type of packages, only accept a single definition
- stringCoercibleSet = mergeOneOption;
- lambda =
- loc: defs: arg:
- anything.merge (loc ++ [ "<function body>" ]) (
- map (def: {
- file = def.file;
- value = def.value arg;
- }) defs
- );
- # Otherwise fall back to only allowing all equal definitions
- }
- .${commonType} or mergeEqualOption;
- in
- mergeFunction loc defs;
- };
+ unspecified = mkOptionType {
+ name = "unspecified";
+ description = "unspecified value";
+ descriptionClass = "noun";
+ };
- unspecified = mkOptionType {
- name = "unspecified";
- description = "unspecified value";
- descriptionClass = "noun";
- };
+ bool = mkOptionType {
+ name = "bool";
+ description = "boolean";
+ descriptionClass = "noun";
+ check = isBool;
+ merge = mergeEqualOption;
+ };
- bool = mkOptionType {
- name = "bool";
- description = "boolean";
- descriptionClass = "noun";
- check = isBool;
- merge = mergeEqualOption;
- };
+ boolByOr = mkOptionType {
+ name = "boolByOr";
+ description = "boolean (merged using or)";
+ descriptionClass = "noun";
+ check = isBool;
+ merge =
+ loc: defs:
+ foldl' (
+ result: def:
+ # Under the assumption that .check always runs before merge, we can assume that all defs.*.value
+ # have been forced, and therefore we assume we don't introduce order-dependent strictness here
+ result || def.value
+ ) false defs;
+ };
- boolByOr = mkOptionType {
- name = "boolByOr";
- description = "boolean (merged using or)";
- descriptionClass = "noun";
- check = isBool;
- merge =
- loc: defs:
- foldl' (
- result: def:
- # Under the assumption that .check always runs before merge, we can assume that all defs.*.value
- # have been forced, and therefore we assume we don't introduce order-dependent strictness here
- result || def.value
- ) false defs;
- };
+ int = mkOptionType {
+ name = "int";
+ description = "signed integer";
+ descriptionClass = "noun";
+ check = isInt;
+ merge = mergeEqualOption;
+ };
- int = mkOptionType {
- name = "int";
- description = "signed integer";
- descriptionClass = "noun";
- check = isInt;
- merge = mergeEqualOption;
- };
+ # Specialized subdomains of int
+ ints =
+ let
+ betweenDesc = lowest: highest: "${toString lowest} and ${toString highest} (both inclusive)";
+ between =
+ lowest: highest:
+ assert lib.assertMsg (lowest <= highest) "ints.between: lowest must be smaller than highest";
+ addCheck int (x: x >= lowest && x <= highest)
+ // {
+ name = "intBetween";
+ description = "integer between ${betweenDesc lowest highest}";
+ };
+ ign =
+ lowest: highest: name: docStart:
+ between lowest highest
+ // {
+ inherit name;
+ description = docStart + "; between ${betweenDesc lowest highest}";
+ };
+ unsign =
+ bit: range: ign 0 (range - 1) "unsignedInt${toString bit}" "${toString bit} bit unsigned integer";
+ sign =
+ bit: range:
+ ign (0 - (range / 2)) (
+ range / 2 - 1
+ ) "signedInt${toString bit}" "${toString bit} bit signed integer";
- # Specialized subdomains of int
- ints =
- let
- betweenDesc = lowest: highest: "${toString lowest} and ${toString highest} (both inclusive)";
- between =
- lowest: highest:
- assert lib.assertMsg (lowest <= highest) "ints.between: lowest must be smaller than highest";
- addCheck int (x: x >= lowest && x <= highest)
- // {
- name = "intBetween";
- description = "integer between ${betweenDesc lowest highest}";
- };
- ign =
- lowest: highest: name: docStart:
- between lowest highest
- // {
- inherit name;
- description = docStart + "; between ${betweenDesc lowest highest}";
- };
- unsign =
- bit: range: ign 0 (range - 1) "unsignedInt${toString bit}" "${toString bit} bit unsigned integer";
- sign =
- bit: range:
- ign (0 - (range / 2)) (
- range / 2 - 1
- ) "signedInt${toString bit}" "${toString bit} bit signed integer";
+ in
+ {
+ # TODO: Deduplicate with docs in nixos/doc/manual/development/option-types.section.md
+ /**
+ An int with a fixed range.
- in
- {
- # TODO: Deduplicate with docs in nixos/doc/manual/development/option-types.section.md
- /**
- An int with a fixed range.
+ # Example
+ :::{.example}
+ ## `lib.types.ints.between` usage example
- # Example
- :::{.example}
- ## `lib.types.ints.between` usage example
+ ```nix
+ (ints.between 0 100).check (-1)
+ => false
+ (ints.between 0 100).check (101)
+ => false
+ (ints.between 0 0).check 0
+ => true
+ ```
- ```nix
- (ints.between 0 100).check (-1)
- => false
- (ints.between 0 100).check (101)
- => false
- (ints.between 0 0).check 0
- => true
- ```
+ :::
+ */
+ inherit between;
- :::
- */
- inherit between;
+ unsigned = addCheck lib.types.int (x: x >= 0) // {
+ name = "unsignedInt";
+ description = "unsigned integer, meaning >=0";
+ descriptionClass = "nonRestrictiveClause";
+ };
+ positive = addCheck lib.types.int (x: x > 0) // {
+ name = "positiveInt";
+ description = "positive integer, meaning >0";
+ descriptionClass = "nonRestrictiveClause";
+ };
+ u8 = unsign 8 256;
+ u16 = unsign 16 65536;
+ # the biggest int Nix accepts is 2^63 - 1 (9223372036854775808)
+ # the smallest int Nix accepts is -2^63 (-9223372036854775807)
+ u32 = unsign 32 4294967296;
+ # u64 = unsign 64 18446744073709551616;
- unsigned = addCheck types.int (x: x >= 0) // {
- name = "unsignedInt";
- description = "unsigned integer, meaning >=0";
- descriptionClass = "nonRestrictiveClause";
- };
- positive = addCheck types.int (x: x > 0) // {
- name = "positiveInt";
- description = "positive integer, meaning >0";
- descriptionClass = "nonRestrictiveClause";
- };
- u8 = unsign 8 256;
- u16 = unsign 16 65536;
- # the biggest int Nix accepts is 2^63 - 1 (9223372036854775808)
- # the smallest int Nix accepts is -2^63 (-9223372036854775807)
- u32 = unsign 32 4294967296;
- # u64 = unsign 64 18446744073709551616;
+ s8 = sign 8 256;
+ s16 = sign 16 65536;
+ s32 = sign 32 4294967296;
+ };
- s8 = sign 8 256;
- s16 = sign 16 65536;
- s32 = sign 32 4294967296;
- };
+ # Alias of u16 for a port number
+ port = ints.u16;
- # Alias of u16 for a port number
- port = ints.u16;
+ float = mkOptionType {
+ name = "float";
+ description = "floating point number";
+ descriptionClass = "noun";
+ check = isFloat;
+ merge = mergeEqualOption;
+ };
- float = mkOptionType {
- name = "float";
- description = "floating point number";
- descriptionClass = "noun";
- check = isFloat;
- merge = mergeEqualOption;
+ number = either int float;
+
+ numbers =
+ let
+ betweenDesc =
+ lowest: highest: "${builtins.toJSON lowest} and ${builtins.toJSON highest} (both inclusive)";
+ in
+ {
+ between =
+ lowest: highest:
+ assert lib.assertMsg (lowest <= highest) "numbers.between: lowest must be smaller than highest";
+ addCheck number (x: x >= lowest && x <= highest)
+ // {
+ name = "numberBetween";
+ description = "integer or floating point number between ${betweenDesc lowest highest}";
+ };
+
+ nonnegative = addCheck number (x: x >= 0) // {
+ name = "numberNonnegative";
+ description = "nonnegative integer or floating point number, meaning >=0";
+ descriptionClass = "nonRestrictiveClause";
+ };
+ positive = addCheck number (x: x > 0) // {
+ name = "numberPositive";
+ description = "positive integer or floating point number, meaning >0";
+ descriptionClass = "nonRestrictiveClause";
};
+ };
- number = either int float;
+ str = mkOptionType {
+ name = "str";
+ description = "string";
+ descriptionClass = "noun";
+ check = isString;
+ merge = mergeEqualOption;
+ };
- numbers =
- let
- betweenDesc =
- lowest: highest: "${builtins.toJSON lowest} and ${builtins.toJSON highest} (both inclusive)";
- in
- {
- between =
- lowest: highest:
- assert lib.assertMsg (lowest <= highest) "numbers.between: lowest must be smaller than highest";
- addCheck number (x: x >= lowest && x <= highest)
- // {
- name = "numberBetween";
- description = "integer or floating point number between ${betweenDesc lowest highest}";
- };
+ nonEmptyStr = mkOptionType {
+ name = "nonEmptyStr";
+ description = "non-empty string";
+ descriptionClass = "noun";
+ check = x: str.check x && builtins.match "[ \t\n]*" x == null;
+ inherit (str) merge;
+ };
- nonnegative = addCheck number (x: x >= 0) // {
- name = "numberNonnegative";
- description = "nonnegative integer or floating point number, meaning >=0";
- descriptionClass = "nonRestrictiveClause";
- };
- positive = addCheck number (x: x > 0) // {
- name = "numberPositive";
- description = "positive integer or floating point number, meaning >0";
- descriptionClass = "nonRestrictiveClause";
- };
- };
+ # Allow a newline character at the end and trim it in the merge function.
+ singleLineStr =
+ let
+ inherit (strMatching "[^\n\r]*\n?") check merge;
+ in
+ mkOptionType {
+ name = "singleLineStr";
+ description = "(optionally newline-terminated) single-line string";
+ descriptionClass = "noun";
+ inherit check;
+ merge = loc: defs: lib.removeSuffix "\n" (merge loc defs);
+ };
- str = mkOptionType {
- name = "str";
- description = "string";
- descriptionClass = "noun";
- check = isString;
- merge = mergeEqualOption;
+ strMatching =
+ pattern:
+ mkOptionType {
+ name = "strMatching ${escapeNixString pattern}";
+ description = "string matching the pattern ${pattern}";
+ descriptionClass = "noun";
+ check = x: str.check x && builtins.match pattern x != null;
+ inherit (str) merge;
+ functor = defaultFunctor "strMatching" // {
+ type = payload: strMatching payload.pattern;
+ payload = { inherit pattern; };
+ binOp = lhs: rhs: if lhs == rhs then lhs else null;
};
+ };
- nonEmptyStr = mkOptionType {
- name = "nonEmptyStr";
- description = "non-empty string";
- descriptionClass = "noun";
- check = x: str.check x && builtins.match "[ \t\n]*" x == null;
- inherit (str) merge;
+ # Merge multiple definitions by concatenating them (with the given
+ # separator between the values).
+ separatedString =
+ sep:
+ mkOptionType rec {
+ name = "separatedString";
+ description = "strings concatenated with ${builtins.toJSON sep}";
+ descriptionClass = "noun";
+ check = isString;
+ merge = loc: defs: concatStringsSep sep (getValues defs);
+ functor = (defaultFunctor name) // {
+ payload = { inherit sep; };
+ type = payload: lib.types.separatedString payload.sep;
+ binOp = lhs: rhs: if lhs.sep == rhs.sep then { inherit (lhs) sep; } else null;
};
+ };
- # Allow a newline character at the end and trim it in the merge function.
- singleLineStr =
- let
- inherit (strMatching "[^\n\r]*\n?") check merge;
- in
- mkOptionType {
- name = "singleLineStr";
- description = "(optionally newline-terminated) single-line string";
- descriptionClass = "noun";
- inherit check;
- merge = loc: defs: lib.removeSuffix "\n" (merge loc defs);
- };
+ lines = separatedString "\n";
+ commas = separatedString ",";
+ envVar = separatedString ":";
- strMatching =
- pattern:
- mkOptionType {
- name = "strMatching ${escapeNixString pattern}";
- description = "string matching the pattern ${pattern}";
- descriptionClass = "noun";
- check = x: str.check x && builtins.match pattern x != null;
- inherit (str) merge;
- functor = defaultFunctor "strMatching" // {
- type = payload: strMatching payload.pattern;
- payload = { inherit pattern; };
- binOp = lhs: rhs: if lhs == rhs then lhs else null;
- };
- };
+ passwdEntry =
+ entryType:
+ addCheck entryType (str: !(hasInfix ":" str || hasInfix "\n" str))
+ // {
+ name = "passwdEntry ${entryType.name}";
+ description = "${
+ optionDescriptionPhrase (class: class == "noun") entryType
+ }, not containing newlines or colons";
+ descriptionClass = "nonRestrictiveClause";
+ };
- # Merge multiple definitions by concatenating them (with the given
- # separator between the values).
- separatedString =
- sep:
- mkOptionType rec {
- name = "separatedString";
- description = "strings concatenated with ${builtins.toJSON sep}";
- descriptionClass = "noun";
- check = isString;
- merge = loc: defs: concatStringsSep sep (getValues defs);
- functor = (defaultFunctor name) // {
- payload = { inherit sep; };
- type = payload: types.separatedString payload.sep;
- binOp = lhs: rhs: if lhs.sep == rhs.sep then { inherit (lhs) sep; } else null;
- };
- };
+ attrs = mkOptionType {
+ name = "attrs";
+ description = "attribute set";
+ check = isAttrs;
+ merge = loc: foldl' (res: def: res // def.value) { };
+ emptyValue = {
+ value = { };
+ };
+ };
- lines = separatedString "\n";
- commas = separatedString ",";
- envVar = separatedString ":";
+ fileset = mkOptionType {
+ name = "fileset";
+ description = "fileset";
+ descriptionClass = "noun";
+ check = isFileset;
+ merge = loc: defs: unions (map (x: x.value) defs);
+ emptyValue.value = empty;
+ };
- passwdEntry =
- entryType:
- addCheck entryType (str: !(hasInfix ":" str || hasInfix "\n" str))
- // {
- name = "passwdEntry ${entryType.name}";
- description = "${
- optionDescriptionPhrase (class: class == "noun") entryType
- }, not containing newlines or colons";
- descriptionClass = "nonRestrictiveClause";
- };
+ # A package is a top-level store path (/nix/store/hash-name). This includes:
+ # - derivations
+ # - more generally, attribute sets with an `outPath` or `__toString` attribute
+ # pointing to a store path, e.g. flake inputs
+ # - strings with context, e.g. "${pkgs.foo}" or (toString pkgs.foo)
+ # - hardcoded store path literals (/nix/store/hash-foo) or strings without context
+ # ("/nix/store/hash-foo"). These get a context added to them using builtins.storePath.
+ # If you don't need a *top-level* store path, consider using pathInStore instead.
+ package = mkOptionType {
+ name = "package";
+ descriptionClass = "noun";
+ check = x: isDerivation x || isStorePath x;
+ merge =
+ loc: defs:
+ let
+ res = mergeOneOption loc defs;
+ in
+ if builtins.isPath res || (builtins.isString res && !builtins.hasContext res) then
+ toDerivation res
+ else
+ res;
+ };
- attrs = mkOptionType {
- name = "attrs";
- description = "attribute set";
- check = isAttrs;
- merge = loc: foldl' (res: def: res // def.value) { };
- emptyValue = {
- value = { };
- };
- };
+ shellPackage = package // {
+ check = x: isDerivation x && hasAttr "shellPath" x;
+ };
- fileset = mkOptionType {
- name = "fileset";
- description = "fileset";
- descriptionClass = "noun";
- check = isFileset;
- merge = loc: defs: unions (map (x: x.value) defs);
- emptyValue.value = empty;
- };
+ pkgs = addCheck (
+ unique { message = "A Nixpkgs pkgs set can not be merged with another pkgs set."; } attrs
+ // {
+ name = "pkgs";
+ descriptionClass = "noun";
+ description = "Nixpkgs package set";
+ }
+ ) (x: (x._type or null) == "pkgs");
- # A package is a top-level store path (/nix/store/hash-name). This includes:
- # - derivations
- # - more generally, attribute sets with an `outPath` or `__toString` attribute
- # pointing to a store path, e.g. flake inputs
- # - strings with context, e.g. "${pkgs.foo}" or (toString pkgs.foo)
- # - hardcoded store path literals (/nix/store/hash-foo) or strings without context
- # ("/nix/store/hash-foo"). These get a context added to them using builtins.storePath.
- # If you don't need a *top-level* store path, consider using pathInStore instead.
- package = mkOptionType {
- name = "package";
+ path = pathWith {
+ absolute = true;
+ };
+
+ pathInStore = pathWith {
+ inStore = true;
+ };
+
+ externalPath = pathWith {
+ absolute = true;
+ inStore = false;
+ };
+
+ pathWith =
+ {
+ inStore ? null,
+ absolute ? null,
+ }:
+ throwIf (inStore != null && absolute != null && inStore && !absolute)
+ "In pathWith, inStore means the path must be absolute"
+ mkOptionType
+ {
+ name = "path";
+ description = (
+ (if absolute == null then "" else (if absolute then "absolute " else "relative "))
+ + "path"
+ + (
+ if inStore == null then "" else (if inStore then " in the Nix store" else " not in the Nix store")
+ )
+ );
descriptionClass = "noun";
- check = x: isDerivation x || isStorePath x;
- merge =
- loc: defs:
+
+ merge = mergeEqualOption;
+ functor = defaultFunctor "path" // {
+ type = pathWith;
+ payload = { inherit inStore absolute; };
+ binOp = lhs: rhs: if lhs == rhs then lhs else null;
+ };
+
+ check =
+ x:
let
- res = mergeOneOption loc defs;
+ isInStore = lib.path.hasStorePathPrefix (
+ if builtins.isPath x then
+ x
+ # Discarding string context is necessary to convert the value to
+ # a path and safe as the result is never used in any derivation.
+ else
+ /. + builtins.unsafeDiscardStringContext x
+ );
+ isAbsolute = builtins.substring 0 1 (toString x) == "/";
+ isExpectedType = (
+ if inStore == null || inStore then isStringLike x else isString x # Do not allow a true path, which could be copied to the store later on.
+ );
in
- if builtins.isPath res || (builtins.isString res && !builtins.hasContext res) then
- toDerivation res
- else
- res;
+ isExpectedType
+ && (inStore == null || inStore == isInStore)
+ && (absolute == null || absolute == isAbsolute);
};
- shellPackage = package // {
- check = x: isDerivation x && hasAttr "shellPath" x;
+ listOf =
+ elemType:
+ mkOptionType rec {
+ name = "listOf";
+ description = "list of ${
+ optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType
+ }";
+ descriptionClass = "composite";
+ check = {
+ __functor = _self: isList;
+ isV2MergeCoherent = true;
};
-
- pkgs = addCheck (
- unique { message = "A Nixpkgs pkgs set can not be merged with another pkgs set."; } attrs
- // {
- name = "pkgs";
- descriptionClass = "noun";
- description = "Nixpkgs package set";
- }
- ) (x: (x._type or null) == "pkgs");
-
- path = pathWith {
- absolute = true;
+ merge = {
+ __functor =
+ self: loc: defs:
+ (self.v2 { inherit loc defs; }).value;
+ v2 =
+ { loc, defs }:
+ let
+ evals = filter (x: x.optionalValue ? value) (
+ concatLists (
+ imap1 (
+ n: def:
+ imap1 (
+ m: def':
+ (mergeDefinitions (loc ++ [ "[definition ${toString n}-entry ${toString m}]" ]) elemType [
+ {
+ inherit (def) file;
+ value = def';
+ }
+ ])
+ ) def.value
+ ) defs
+ )
+ );
+ in
+ {
+ headError = checkDefsForError check loc defs;
+ value = map (x: x.optionalValue.value or x.mergedValue) evals;
+ valueMeta.list = map (v: v.checkedAndMerged.valueMeta) evals;
+ };
};
-
- pathInStore = pathWith {
- inStore = true;
+ emptyValue = {
+ value = [ ];
};
-
- externalPath = pathWith {
- absolute = true;
- inStore = false;
+ getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "*" ]);
+ getSubModules = elemType.getSubModules;
+ substSubModules = m: listOf (elemType.substSubModules m);
+ functor = (elemTypeFunctor name { inherit elemType; }) // {
+ type = payload: lib.types.listOf payload.elemType;
};
+ nestedTypes.elemType = elemType;
+ };
- pathWith =
- {
- inStore ? null,
- absolute ? null,
- }:
- throwIf (inStore != null && absolute != null && inStore && !absolute)
- "In pathWith, inStore means the path must be absolute"
- mkOptionType
- {
- name = "path";
- description = (
- (if absolute == null then "" else (if absolute then "absolute " else "relative "))
- + "path"
- + (
- if inStore == null then "" else (if inStore then " in the Nix store" else " not in the Nix store")
- )
- );
- descriptionClass = "noun";
+ nonEmptyListOf =
+ elemType:
+ let
+ list = addCheck (lib.types.listOf elemType) (l: l != [ ]);
+ in
+ list
+ // {
+ description = "non-empty ${optionDescriptionPhrase (class: class == "noun") list}";
+ emptyValue = { }; # no .value attr, meaning unset
+ substSubModules = m: nonEmptyListOf (elemType.substSubModules m);
+ };
- merge = mergeEqualOption;
- functor = defaultFunctor "path" // {
- type = pathWith;
- payload = { inherit inStore absolute; };
- binOp = lhs: rhs: if lhs == rhs then lhs else null;
- };
+ attrsOf = elemType: attrsWith { inherit elemType; };
- check =
- x:
- let
- isInStore = lib.path.hasStorePathPrefix (
- if builtins.isPath x then
- x
- # Discarding string context is necessary to convert the value to
- # a path and safe as the result is never used in any derivation.
- else
- /. + builtins.unsafeDiscardStringContext x
- );
- isAbsolute = builtins.substring 0 1 (toString x) == "/";
- isExpectedType = (
- if inStore == null || inStore then isStringLike x else isString x # Do not allow a true path, which could be copied to the store later on.
- );
- in
- isExpectedType
- && (inStore == null || inStore == isInStore)
- && (absolute == null || absolute == isAbsolute);
- };
+ # 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:
+ attrsWith {
+ inherit elemType;
+ lazy = true;
+ };
- listOf =
- elemType:
- mkOptionType rec {
- name = "listOf";
- description = "list of ${
- optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType
- }";
- descriptionClass = "composite";
- check = {
- __functor = _self: isList;
- isV2MergeCoherent = true;
+ # base type for lazyAttrsOf and attrsOf
+ attrsWith =
+ let
+ # Push down position info.
+ pushPositions = map (
+ def:
+ mapAttrs (n: v: {
+ inherit (def) file;
+ value = v;
+ }) def.value
+ );
+ binOp =
+ lhs: rhs:
+ let
+ elemType = lhs.elemType.typeMerge rhs.elemType.functor;
+ lazy = if lhs.lazy == rhs.lazy then lhs.lazy else null;
+ placeholder =
+ if lhs.placeholder == rhs.placeholder then
+ lhs.placeholder
+ else if lhs.placeholder == "name" then
+ rhs.placeholder
+ else if rhs.placeholder == "name" then
+ lhs.placeholder
+ else
+ null;
+ in
+ if elemType == null || lazy == null || placeholder == null then
+ null
+ else
+ {
+ inherit elemType lazy placeholder;
};
- merge = {
- __functor =
- self: loc: defs:
- (self.v2 { inherit loc defs; }).value;
- v2 =
- { loc, defs }:
- let
- evals = filter (x: x.optionalValue ? value) (
- concatLists (
- imap1 (
- n: def:
- imap1 (
- m: def':
- (mergeDefinitions (loc ++ [ "[definition ${toString n}-entry ${toString m}]" ]) elemType [
- {
- inherit (def) file;
- value = def';
- }
- ])
- ) def.value
- ) defs
- )
+ in
+ {
+ elemType,
+ lazy ? false,
+ placeholder ? "name",
+ }:
+ mkOptionType rec {
+ name = if lazy then "lazyAttrsOf" else "attrsOf";
+ description =
+ (if lazy then "lazy attribute set" else "attribute set")
+ + " of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
+ descriptionClass = "composite";
+ check = {
+ __functor = _self: isAttrs;
+ isV2MergeCoherent = true;
+ };
+ merge = {
+ __functor =
+ self: loc: defs:
+ (self.v2 { inherit loc defs; }).value;
+ v2 =
+ { loc, defs }:
+ let
+ evals =
+ if lazy then
+ zipAttrsWith (name: defs: mergeDefinitions (loc ++ [ name ]) elemType defs) (pushPositions defs)
+ else
+ # Filtering makes the merge function more strict
+ # Meaning it is less lazy
+ filterAttrs (n: v: v.optionalValue ? value) (
+ zipAttrsWith (name: defs: mergeDefinitions (loc ++ [ name ]) elemType defs) (pushPositions defs)
);
- in
- {
- headError = checkDefsForError check loc defs;
- value = map (x: x.optionalValue.value or x.mergedValue) evals;
- valueMeta.list = map (v: v.checkedAndMerged.valueMeta) evals;
- };
- };
- emptyValue = {
- value = [ ];
- };
- getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "*" ]);
- getSubModules = elemType.getSubModules;
- substSubModules = m: listOf (elemType.substSubModules m);
- functor = (elemTypeFunctor name { inherit elemType; }) // {
- type = payload: types.listOf payload.elemType;
+ in
+ {
+ headError = checkDefsForError check loc defs;
+ value = mapAttrs (
+ n: v:
+ if lazy then
+ v.optionalValue.value or elemType.emptyValue.value or v.mergedValue
+ else
+ v.optionalValue.value
+ ) evals;
+ valueMeta.attrs = mapAttrs (n: v: v.checkedAndMerged.valueMeta) evals;
};
- nestedTypes.elemType = elemType;
- };
+ };
- nonEmptyListOf =
- elemType:
- let
- list = addCheck (types.listOf elemType) (l: l != [ ]);
- in
- list
+ emptyValue = {
+ value = { };
+ };
+ getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<${placeholder}>" ]);
+ getSubModules = elemType.getSubModules;
+ substSubModules =
+ m:
+ attrsWith {
+ elemType = elemType.substSubModules m;
+ inherit lazy placeholder;
+ };
+ functor =
+ (elemTypeFunctor "attrsWith" {
+ inherit elemType lazy placeholder;
+ })
// {
- description = "non-empty ${optionDescriptionPhrase (class: class == "noun") list}";
- emptyValue = { }; # no .value attr, meaning unset
- substSubModules = m: nonEmptyListOf (elemType.substSubModules m);
+ # Custom type merging required because of the "placeholder" attribute
+ inherit binOp;
};
+ nestedTypes.elemType = elemType;
+ };
- attrsOf = elemType: attrsWith { inherit 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:
- attrsWith {
- inherit elemType;
- lazy = true;
- };
+ # TODO: deprecate this in the future:
+ loaOf =
+ elemType:
+ lib.types.attrsOf elemType
+ // {
+ name = "loaOf";
+ deprecationMessage =
+ "Mixing lists with attribute values is no longer"
+ + " possible; please use `types.attrsOf` instead. See"
+ + " https://github.com/NixOS/nixpkgs/issues/1800 for the motivation.";
+ nestedTypes.elemType = elemType;
+ };
- # base type for lazyAttrsOf and attrsOf
- attrsWith =
+ attrTag =
+ tags:
+ let
+ tags_ = tags;
+ in
+ let
+ tags = mapAttrs (
+ n: opt:
+ builtins.addErrorContext
+ "while checking that attrTag tag ${lib.strings.escapeNixIdentifier n} is an option with a type${inAttrPosSuffix tags_ n}"
+ (
+ throwIf (opt._type or null != "option")
+ "In attrTag, each tag value must be an option, but tag ${lib.strings.escapeNixIdentifier n} ${
+ if opt ? _type then
+ if opt._type == "option-type" then
+ "was a bare type, not wrapped in mkOption."
+ else
+ "was of type ${lib.strings.escapeNixString opt._type}."
+ else
+ "was not."
+ }"
+ opt
+ // {
+ declarations =
+ opt.declarations or (
+ let
+ pos = builtins.unsafeGetAttrPos n tags_;
+ in
+ if pos == null then [ ] else [ pos.file ]
+ );
+ declarationPositions =
+ opt.declarationPositions or (
+ let
+ pos = builtins.unsafeGetAttrPos n tags_;
+ in
+ if pos == null then [ ] else [ pos ]
+ );
+ }
+ )
+ ) tags_;
+ choicesStr = concatMapStringsSep ", " lib.strings.escapeNixIdentifier (attrNames tags);
+ in
+ mkOptionType {
+ name = "attrTag";
+ description = "attribute-tagged union with choices: ${choicesStr}";
+ descriptionClass = "noun";
+ getSubOptions =
+ prefix: mapAttrs (tagName: tagOption: tagOption // { loc = prefix ++ [ tagName ]; }) tags;
+ check = v: isAttrs v && length (attrNames v) == 1 && tags ? ${head (attrNames v)};
+ merge =
+ loc: defs:
let
- # Push down position info.
- pushPositions = map (
+ choice = head (attrNames (head defs).value);
+ checkedValueDefs = map (
def:
- mapAttrs (n: v: {
- inherit (def) file;
- value = v;
- }) def.value
- );
- binOp =
- lhs: rhs:
- let
- elemType = lhs.elemType.typeMerge rhs.elemType.functor;
- lazy = if lhs.lazy == rhs.lazy then lhs.lazy else null;
- placeholder =
- if lhs.placeholder == rhs.placeholder then
- lhs.placeholder
- else if lhs.placeholder == "name" then
- rhs.placeholder
- else if rhs.placeholder == "name" then
- lhs.placeholder
- else
- null;
- in
- if elemType == null || lazy == null || placeholder == null then
- null
+ assert (length (attrNames def.value)) == 1;
+ if (head (attrNames def.value)) != choice then
+ throw "The option `${showOption loc}` is defined both as `${choice}` and `${head (attrNames def.value)}`, in ${showFiles (getFiles defs)}."
else
{
- inherit elemType lazy placeholder;
- };
+ inherit (def) file;
+ value = def.value.${choice};
+ }
+ ) defs;
in
- {
- elemType,
- lazy ? false,
- placeholder ? "name",
- }:
- mkOptionType rec {
- name = if lazy then "lazyAttrsOf" else "attrsOf";
- description =
- (if lazy then "lazy attribute set" else "attribute set")
- + " of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
- descriptionClass = "composite";
- check = {
- __functor = _self: isAttrs;
- isV2MergeCoherent = true;
- };
- merge = {
- __functor =
- self: loc: defs:
- (self.v2 { inherit loc defs; }).value;
- v2 =
- { loc, defs }:
- let
- evals =
- if lazy then
- zipAttrsWith (name: defs: mergeDefinitions (loc ++ [ name ]) elemType defs) (pushPositions defs)
- else
- # Filtering makes the merge function more strict
- # Meaning it is less lazy
- filterAttrs (n: v: v.optionalValue ? value) (
- zipAttrsWith (name: defs: mergeDefinitions (loc ++ [ name ]) elemType defs) (pushPositions defs)
- );
- in
- {
- headError = checkDefsForError check loc defs;
- value = mapAttrs (
- n: v:
- if lazy then
- v.optionalValue.value or elemType.emptyValue.value or v.mergedValue
- else
- v.optionalValue.value
- ) evals;
- valueMeta.attrs = mapAttrs (n: v: v.checkedAndMerged.valueMeta) evals;
- };
- };
-
- emptyValue = {
- value = { };
- };
- getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<${placeholder}>" ]);
- getSubModules = elemType.getSubModules;
- substSubModules =
- m:
- attrsWith {
- elemType = elemType.substSubModules m;
- inherit lazy placeholder;
- };
- functor =
- (elemTypeFunctor "attrsWith" {
- inherit elemType lazy placeholder;
- })
- // {
- # Custom type merging required because of the "placeholder" attribute
- inherit binOp;
+ if tags ? ${choice} then
+ {
+ ${choice} = (lib.modules.evalOptionValue (loc ++ [ choice ]) tags.${choice} checkedValueDefs).value;
+ }
+ else
+ throw "The option `${showOption loc}` is defined as ${lib.strings.escapeNixIdentifier choice}, but ${lib.strings.escapeNixIdentifier choice} is not among the valid choices (${choicesStr}). Value ${choice} was defined in ${showFiles (getFiles defs)}.";
+ nestedTypes = tags;
+ functor = defaultFunctor "attrTag" // {
+ type = { tags, ... }: lib.types.attrTag tags;
+ payload = { inherit tags; };
+ binOp =
+ let
+ # Add metadata in the format that submodules work with
+ wrapOptionDecl = option: {
+ options = option;
+ _file = "<attrTag {...}>";
+ pos = null;
};
- nestedTypes.elemType = elemType;
- };
-
- # TODO: deprecate this in the future:
- loaOf =
- elemType:
- types.attrsOf elemType
- // {
- name = "loaOf";
- deprecationMessage =
- "Mixing lists with attribute values is no longer"
- + " possible; please use `types.attrsOf` instead. See"
- + " https://github.com/NixOS/nixpkgs/issues/1800 for the motivation.";
- nestedTypes.elemType = elemType;
- };
-
- attrTag =
- tags:
- let
- tags_ = tags;
- in
- let
- tags = mapAttrs (
- n: opt:
- builtins.addErrorContext
- "while checking that attrTag tag ${lib.strings.escapeNixIdentifier n} is an option with a type${inAttrPosSuffix tags_ n}"
- (
- throwIf (opt._type or null != "option")
- "In attrTag, each tag value must be an option, but tag ${lib.strings.escapeNixIdentifier n} ${
- if opt ? _type then
- if opt._type == "option-type" then
- "was a bare type, not wrapped in mkOption."
- else
- "was of type ${lib.strings.escapeNixString opt._type}."
- else
- "was not."
- }"
- opt
+ in
+ a: b: {
+ tags =
+ a.tags
+ // b.tags
+ // mapAttrs (
+ tagName: bOpt:
+ lib.mergeOptionDecls
+ # FIXME: loc is not accurate; should include prefix
+ # Fortunately, it's only used for error messages, where a "relative" location is kinda ok.
+ # It is also returned though, but use of the attribute seems rare?
+ [ tagName ]
+ [
+ (wrapOptionDecl a.tags.${tagName})
+ (wrapOptionDecl bOpt)
+ ]
// {
- declarations =
- opt.declarations or (
- let
- pos = builtins.unsafeGetAttrPos n tags_;
- in
- if pos == null then [ ] else [ pos.file ]
- );
- declarationPositions =
- opt.declarationPositions or (
- let
- pos = builtins.unsafeGetAttrPos n tags_;
- in
- if pos == null then [ ] else [ pos ]
- );
+ # mergeOptionDecls is not idempotent in these attrs:
+ declarations = a.tags.${tagName}.declarations ++ bOpt.declarations;
+ declarationPositions = a.tags.${tagName}.declarationPositions ++ bOpt.declarationPositions;
}
- )
- ) tags_;
- choicesStr = concatMapStringsSep ", " lib.strings.escapeNixIdentifier (attrNames tags);
- in
- mkOptionType {
- name = "attrTag";
- description = "attribute-tagged union with choices: ${choicesStr}";
- descriptionClass = "noun";
- getSubOptions =
- prefix: mapAttrs (tagName: tagOption: tagOption // { loc = prefix ++ [ tagName ]; }) tags;
- check = v: isAttrs v && length (attrNames v) == 1 && tags ? ${head (attrNames v)};
- merge =
- loc: defs:
- let
- choice = head (attrNames (head defs).value);
- checkedValueDefs = map (
- def:
- assert (length (attrNames def.value)) == 1;
- if (head (attrNames def.value)) != choice then
- throw "The option `${showOption loc}` is defined both as `${choice}` and `${head (attrNames def.value)}`, in ${showFiles (getFiles defs)}."
- else
- {
- inherit (def) file;
- value = def.value.${choice};
- }
- ) defs;
- in
- if tags ? ${choice} then
- {
- ${choice} = (lib.modules.evalOptionValue (loc ++ [ choice ]) tags.${choice} checkedValueDefs).value;
- }
- else
- throw "The option `${showOption loc}` is defined as ${lib.strings.escapeNixIdentifier choice}, but ${lib.strings.escapeNixIdentifier choice} is not among the valid choices (${choicesStr}). Value ${choice} was defined in ${showFiles (getFiles defs)}.";
- nestedTypes = tags;
- functor = defaultFunctor "attrTag" // {
- type = { tags, ... }: types.attrTag tags;
- payload = { inherit tags; };
- binOp =
- let
- # Add metadata in the format that submodules work with
- wrapOptionDecl = option: {
- options = option;
- _file = "<attrTag {...}>";
- pos = null;
- };
- in
- a: b: {
- tags =
- a.tags
- // b.tags
- // mapAttrs (
- tagName: bOpt:
- lib.mergeOptionDecls
- # FIXME: loc is not accurate; should include prefix
- # Fortunately, it's only used for error messages, where a "relative" location is kinda ok.
- # It is also returned though, but use of the attribute seems rare?
- [ tagName ]
- [
- (wrapOptionDecl a.tags.${tagName})
- (wrapOptionDecl bOpt)
- ]
- // {
- # mergeOptionDecls is not idempotent in these attrs:
- declarations = a.tags.${tagName}.declarations ++ bOpt.declarations;
- declarationPositions = a.tags.${tagName}.declarationPositions ++ bOpt.declarationPositions;
- }
- ) (builtins.intersectAttrs a.tags b.tags);
- };
+ ) (builtins.intersectAttrs a.tags b.tags);
};
- };
-
- # A value produced by `lib.mkLuaInline`
- luaInline = mkOptionType {
- name = "luaInline";
- description = "inline lua";
- descriptionClass = "noun";
- check = x: x._type or null == "lua-inline";
- merge = mergeEqualOption;
};
+ };
- uniq = unique { message = ""; };
+ # A value produced by `lib.mkLuaInline`
+ luaInline = mkOptionType {
+ name = "luaInline";
+ description = "inline lua";
+ descriptionClass = "noun";
+ check = x: x._type or null == "lua-inline";
+ merge = mergeEqualOption;
+ };
- unique =
- { message }:
- type:
- mkOptionType rec {
- name = "unique";
- inherit (type) description descriptionClass check;
- merge = mergeUniqueOption {
- inherit message;
- inherit (type) merge;
- };
- emptyValue = type.emptyValue;
- getSubOptions = type.getSubOptions;
- getSubModules = type.getSubModules;
- substSubModules = m: uniq (type.substSubModules m);
- functor = elemTypeFunctor name { elemType = type; } // {
- type = payload: types.unique { inherit message; } payload.elemType;
- };
- nestedTypes.elemType = type;
- };
+ uniq = unique { message = ""; };
- # Null or value of ...
- nullOr =
- elemType:
- mkOptionType rec {
- name = "nullOr";
- description = "null or ${
- optionDescriptionPhrase (class: class == "noun" || class == "conjunction") elemType
- }";
- descriptionClass = "conjunction";
- check = x: x == null || elemType.check x;
- merge =
- loc: defs:
- let
- nrNulls = count (def: def.value == null) defs;
- in
- if nrNulls == length defs then
- null
- 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);
- functor = (elemTypeFunctor name { inherit elemType; }) // {
- type = payload: types.nullOr payload.elemType;
- };
- nestedTypes.elemType = elemType;
- };
+ unique =
+ { message }:
+ type:
+ mkOptionType rec {
+ name = "unique";
+ inherit (type) description descriptionClass check;
+ merge = mergeUniqueOption {
+ inherit message;
+ inherit (type) merge;
+ };
+ emptyValue = type.emptyValue;
+ getSubOptions = type.getSubOptions;
+ getSubModules = type.getSubModules;
+ substSubModules = m: uniq (type.substSubModules m);
+ functor = elemTypeFunctor name { elemType = type; } // {
+ type = payload: lib.types.unique { inherit message; } payload.elemType;
+ };
+ nestedTypes.elemType = type;
+ };
- functionTo =
- elemType:
- mkOptionType {
- name = "functionTo";
- description = "function that evaluates to a(n) ${
- optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType
- }";
- descriptionClass = "composite";
- check = isFunction;
- merge = loc: defs: {
- # An argument attribute has a default when it has a default in all definitions
- __functionArgs = lib.zipAttrsWith (_: lib.all (x: x)) (
- lib.map (fn: lib.functionArgs fn.value) defs
- );
- __functor =
- _: callerArgs:
- (mergeDefinitions (loc ++ [ "<function body>" ]) elemType (
- map (fn: {
- inherit (fn) file;
- value = fn.value callerArgs;
- }) defs
- )).mergedValue;
- };
- getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<function body>" ]);
- getSubModules = elemType.getSubModules;
- substSubModules = m: functionTo (elemType.substSubModules m);
- functor = (elemTypeFunctor "functionTo" { inherit elemType; }) // {
- type = payload: types.functionTo payload.elemType;
- };
- nestedTypes.elemType = elemType;
- };
+ # Null or value of ...
+ nullOr =
+ elemType:
+ mkOptionType rec {
+ name = "nullOr";
+ description = "null or ${
+ optionDescriptionPhrase (class: class == "noun" || class == "conjunction") elemType
+ }";
+ descriptionClass = "conjunction";
+ check = x: x == null || elemType.check x;
+ merge =
+ loc: defs:
+ let
+ nrNulls = count (def: def.value == null) defs;
+ in
+ if nrNulls == length defs then
+ null
+ 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);
+ functor = (elemTypeFunctor name { inherit elemType; }) // {
+ type = payload: lib.types.nullOr payload.elemType;
+ };
+ nestedTypes.elemType = elemType;
+ };
- # A submodule (like typed attribute set). See NixOS manual.
- submodule =
- modules:
- submoduleWith {
- shorthandOnlyDefinesConfig = true;
- modules = toList modules;
- };
+ functionTo =
+ elemType:
+ mkOptionType {
+ name = "functionTo";
+ description = "function that evaluates to a(n) ${
+ optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType
+ }";
+ descriptionClass = "composite";
+ check = isFunction;
+ merge = loc: defs: {
+ # An argument attribute has a default when it has a default in all definitions
+ __functionArgs = lib.zipAttrsWith (_: lib.all (x: x)) (
+ lib.map (fn: lib.functionArgs fn.value) defs
+ );
+ __functor =
+ _: callerArgs:
+ (mergeDefinitions (loc ++ [ "<function body>" ]) elemType (
+ map (fn: {
+ inherit (fn) file;
+ value = fn.value callerArgs;
+ }) defs
+ )).mergedValue;
+ };
+ getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<function body>" ]);
+ getSubModules = elemType.getSubModules;
+ substSubModules = m: functionTo (elemType.substSubModules m);
+ functor = (elemTypeFunctor "functionTo" { inherit elemType; }) // {
+ type = payload: lib.types.functionTo payload.elemType;
+ };
+ nestedTypes.elemType = elemType;
+ };
- # A module to be imported in some other part of the configuration.
- deferredModule = deferredModuleWith { };
+ # A submodule (like typed attribute set). See NixOS manual.
+ submodule =
+ modules:
+ submoduleWith {
+ shorthandOnlyDefinesConfig = true;
+ modules = toList modules;
+ };
- # A module to be imported in some other part of the configuration.
- # `staticModules`' options will be added to the documentation, unlike
- # options declared via `config`.
- deferredModuleWith =
- attrs@{
- staticModules ? [ ],
- }:
- mkOptionType {
- name = "deferredModule";
- description = "module";
- descriptionClass = "noun";
- check = x: isAttrs x || isFunction x || path.check x;
- merge = loc: defs: {
- imports =
- staticModules
- ++ map (
- def: lib.setDefaultModuleLocation "${def.file}, via option ${showOption loc}" def.value
- ) defs;
- };
- inherit (submoduleWith { modules = staticModules; })
- getSubOptions
- getSubModules
- ;
- substSubModules =
- m:
- deferredModuleWith (
- attrs
- // {
- staticModules = m;
- }
- );
- functor = defaultFunctor "deferredModuleWith" // {
- type = types.deferredModuleWith;
- payload = {
- inherit staticModules;
- };
- binOp = lhs: rhs: {
- staticModules = lhs.staticModules ++ rhs.staticModules;
- };
- };
- };
+ # A module to be imported in some other part of the configuration.
+ deferredModule = deferredModuleWith { };
- # The type of a type!
- optionType = mkOptionType {
- name = "optionType";
- description = "optionType";
- descriptionClass = "noun";
- check = value: value._type or null == "option-type";
- merge =
- loc: defs:
- if length defs == 1 then
- (head defs).value
- else
- let
- # Prepares the type definitions for mergeOptionDecls, which
- # annotates submodules types with file locations
- optionModules = map (
- { value, file }:
- {
- _file = file;
- # There's no way to merge types directly from the module system,
- # but we can cheat a bit by just declaring an option with the type
- options = lib.mkOption {
- type = value;
- };
- }
- ) defs;
- # Merges all the types into a single one, including submodule merging.
- # This also propagates file information to all submodules
- mergedOption = fixupOptionType loc (mergeOptionDecls loc optionModules);
- in
- mergedOption.type;
+ # A module to be imported in some other part of the configuration.
+ # `staticModules`' options will be added to the documentation, unlike
+ # options declared via `config`.
+ deferredModuleWith =
+ attrs@{
+ staticModules ? [ ],
+ }:
+ mkOptionType {
+ name = "deferredModule";
+ description = "module";
+ descriptionClass = "noun";
+ check = x: isAttrs x || isFunction x || path.check x;
+ merge = loc: defs: {
+ imports =
+ staticModules
+ ++ map (
+ def: lib.setDefaultModuleLocation "${def.file}, via option ${showOption loc}" def.value
+ ) defs;
+ };
+ inherit (submoduleWith { modules = staticModules; })
+ getSubOptions
+ getSubModules
+ ;
+ substSubModules =
+ m:
+ deferredModuleWith (
+ attrs
+ // {
+ staticModules = m;
+ }
+ );
+ functor = defaultFunctor "deferredModuleWith" // {
+ type = lib.types.deferredModuleWith;
+ payload = {
+ inherit staticModules;
+ };
+ binOp = lhs: rhs: {
+ staticModules = lhs.staticModules ++ rhs.staticModules;
+ };
};
+ };
- submoduleWith =
- {
- modules,
- specialArgs ? { },
- shorthandOnlyDefinesConfig ? false,
- description ? null,
- class ? null,
- }@attrs:
+ # The type of a type!
+ optionType = mkOptionType {
+ name = "optionType";
+ description = "optionType";
+ descriptionClass = "noun";
+ check = value: value._type or null == "option-type";
+ merge =
+ loc: defs:
+ if length defs == 1 then
+ (head defs).value
+ else
let
- inherit (lib.modules) evalModules;
+ # Prepares the type definitions for mergeOptionDecls, which
+ # annotates submodules types with file locations
+ optionModules = map (
+ { value, file }:
+ {
+ _file = file;
+ # There's no way to merge types directly from the module system,
+ # but we can cheat a bit by just declaring an option with the type
+ options = lib.mkOption {
+ type = value;
+ };
+ }
+ ) defs;
+ # Merges all the types into a single one, including submodule merging.
+ # This also propagates file information to all submodules
+ mergedOption = fixupOptionType loc (mergeOptionDecls loc optionModules);
+ in
+ mergedOption.type;
+ };
- allModules =
- defs:
- map (
- { value, file }:
- if isAttrs value && shorthandOnlyDefinesConfig then
- {
- _file = file;
- config = value;
- }
- else
- {
- _file = file;
- imports = [ value ];
- }
- ) defs;
+ submoduleWith =
+ {
+ modules,
+ specialArgs ? { },
+ shorthandOnlyDefinesConfig ? false,
+ description ? null,
+ class ? null,
+ }@attrs:
+ let
+ inherit (lib.modules) evalModules;
- base = evalModules {
- inherit class specialArgs;
- modules = [
- {
- # This is a work-around for the fact that some sub-modules,
- # such as the one included in an attribute set, expects an "args"
- # attribute to be given to the sub-module. As the option
- # evaluation does not have any specific attribute name yet, we
- # provide a default for the documentation and the freeform type.
- #
- # This is necessary as some option declaration might use the
- # "name" attribute given as argument of the submodule and use it
- # as the default of option declarations.
- #
- # We use lookalike unicode single angle quotation marks because
- # of the docbook transformation the options receive. In all uses
- # &gt; and &lt; wouldn't be encoded correctly so the encoded values
- # would be used, and use of `<` and `>` would break the XML document.
- # It shouldn't cause an issue since this is cosmetic for the manual.
- _module.args.name = lib.mkOptionDefault "‹name›";
- }
- ]
- ++ modules;
- };
+ allModules =
+ defs:
+ map (
+ { value, file }:
+ if isAttrs value && shorthandOnlyDefinesConfig then
+ {
+ _file = file;
+ config = value;
+ }
+ else
+ {
+ _file = file;
+ imports = [ value ];
+ }
+ ) defs;
+
+ base = evalModules {
+ inherit class specialArgs;
+ modules = [
+ {
+ # This is a work-around for the fact that some sub-modules,
+ # such as the one included in an attribute set, expects an "args"
+ # attribute to be given to the sub-module. As the option
+ # evaluation does not have any specific attribute name yet, we
+ # provide a default for the documentation and the freeform type.
+ #
+ # This is necessary as some option declaration might use the
+ # "name" attribute given as argument of the submodule and use it
+ # as the default of option declarations.
+ #
+ # We use lookalike unicode single angle quotation marks because
+ # of the docbook transformation the options receive. In all uses
+ # &gt; and &lt; wouldn't be encoded correctly so the encoded values
+ # would be used, and use of `<` and `>` would break the XML document.
+ # It shouldn't cause an issue since this is cosmetic for the manual.
+ _module.args.name = lib.mkOptionDefault "‹name›";
+ }
+ ]
+ ++ modules;
+ };
- freeformType = base._module.freeformType;
+ freeformType = base._module.freeformType;
- name = "submodule";
+ name = "submodule";
- check = {
- __functor = _self: x: isAttrs x || isFunction x || path.check x;
- isV2MergeCoherent = true;
+ check = {
+ __functor = _self: x: isAttrs x || isFunction x || path.check x;
+ isV2MergeCoherent = true;
+ };
+ in
+ mkOptionType {
+ inherit name;
+ description =
+ if description != null then
+ description
+ else
+ let
+ docsEval = base.extendModules { modules = [ noCheckForDocsModule ]; };
+ in
+ if docsEval._module.freeformType ? description then
+ "open ${name} of ${
+ optionDescriptionPhrase (
+ class: class == "noun" || class == "composite"
+ ) docsEval._module.freeformType
+ }"
+ else
+ name;
+ inherit check;
+ merge = {
+ __functor =
+ self: loc: defs:
+ (self.v2 { inherit loc defs; }).value;
+ v2 =
+ { loc, defs }:
+ let
+ configuration = base.extendModules {
+ modules = [ { _module.args.name = last loc; } ] ++ allModules defs;
+ prefix = loc;
+ };
+ in
+ {
+ headError = checkDefsForError check loc defs;
+ value = configuration.config;
+ valueMeta = { inherit configuration; };
};
+ };
+ emptyValue = {
+ value = { };
+ };
+ getSubOptions =
+ prefix:
+ let
+ docsEval = (
+ base.extendModules {
+ inherit prefix;
+ modules = [ noCheckForDocsModule ];
+ }
+ );
+ # Intentionally shadow the freeformType from the possibly *checked*
+ # configuration. See `noCheckForDocsModule` comment.
+ inherit (docsEval._module) freeformType;
in
- mkOptionType {
- inherit name;
- description =
- if description != null then
- description
+ docsEval.options
+ // optionalAttrs (freeformType != null) {
+ # Expose the sub options of the freeform type. Note that the option
+ # discovery doesn't care about the attribute name used here, so this
+ # is just to avoid conflicts with potential options from the submodule
+ _freeformOptions = freeformType.getSubOptions prefix;
+ };
+ getSubModules = modules;
+ substSubModules =
+ m:
+ submoduleWith (
+ attrs
+ // {
+ modules = m;
+ }
+ );
+ nestedTypes = lib.optionalAttrs (freeformType != null) {
+ freeformType = freeformType;
+ };
+ functor = defaultFunctor name // {
+ type = lib.types.submoduleWith;
+ payload = {
+ inherit
+ modules
+ class
+ specialArgs
+ shorthandOnlyDefinesConfig
+ description
+ ;
+ };
+ binOp = lhs: rhs: {
+ class =
+ # `or null` was added for backwards compatibility only. `class` is
+ # always set in the current version of the module system.
+ if lhs.class or null == null then
+ rhs.class or null
+ else if rhs.class or null == null then
+ lhs.class or null
+ else if lhs.class or null == rhs.class then
+ lhs.class or null
else
- let
- docsEval = base.extendModules { modules = [ noCheckForDocsModule ]; };
- in
- if docsEval._module.freeformType ? description then
- "open ${name} of ${
- optionDescriptionPhrase (
- class: class == "noun" || class == "composite"
- ) docsEval._module.freeformType
- }"
- else
- name;
- inherit check;
- merge = {
- __functor =
- self: loc: defs:
- (self.v2 { inherit loc defs; }).value;
- v2 =
- { loc, defs }:
- let
- configuration = base.extendModules {
- modules = [ { _module.args.name = last loc; } ] ++ allModules defs;
- prefix = loc;
- };
- in
- {
- headError = checkDefsForError check loc defs;
- value = configuration.config;
- valueMeta = { inherit configuration; };
- };
- };
- emptyValue = {
- value = { };
- };
- getSubOptions =
- prefix:
+ throw "A submoduleWith option is declared multiple times with conflicting class values \"${toString lhs.class}\" and \"${toString rhs.class}\".";
+ modules = lhs.modules ++ rhs.modules;
+ specialArgs =
let
- docsEval = (
- base.extendModules {
- inherit prefix;
- modules = [ noCheckForDocsModule ];
- }
- );
- # Intentionally shadow the freeformType from the possibly *checked*
- # configuration. See `noCheckForDocsModule` comment.
- inherit (docsEval._module) freeformType;
+ intersecting = builtins.intersectAttrs lhs.specialArgs rhs.specialArgs;
in
- docsEval.options
- // optionalAttrs (freeformType != null) {
- # Expose the sub options of the freeform type. Note that the option
- # discovery doesn't care about the attribute name used here, so this
- # is just to avoid conflicts with potential options from the submodule
- _freeformOptions = freeformType.getSubOptions prefix;
- };
- getSubModules = modules;
- substSubModules =
- m:
- submoduleWith (
- attrs
- // {
- modules = m;
- }
- );
- nestedTypes = lib.optionalAttrs (freeformType != null) {
- freeformType = freeformType;
- };
- functor = defaultFunctor name // {
- type = types.submoduleWith;
- payload = {
- inherit
- modules
- class
- specialArgs
- shorthandOnlyDefinesConfig
- description
- ;
- };
- binOp = lhs: rhs: {
- class =
- # `or null` was added for backwards compatibility only. `class` is
- # always set in the current version of the module system.
- if lhs.class or null == null then
- rhs.class or null
- else if rhs.class or null == null then
- lhs.class or null
- else if lhs.class or null == rhs.class then
- lhs.class or null
- else
- throw "A submoduleWith option is declared multiple times with conflicting class values \"${toString lhs.class}\" and \"${toString rhs.class}\".";
- modules = lhs.modules ++ rhs.modules;
- specialArgs =
- let
- intersecting = builtins.intersectAttrs lhs.specialArgs rhs.specialArgs;
- in
- if intersecting == { } then
- lhs.specialArgs // rhs.specialArgs
- else
- throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\"";
- 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
- else
- throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values";
- description =
- if lhs.description == null then
- rhs.description
- else if rhs.description == null then
- lhs.description
- else if lhs.description == rhs.description then
- lhs.description
- else
- throw "A submoduleWith option is declared multiple times with conflicting descriptions";
- };
- };
- };
-
- # A value from a set of allowed ones.
- enum =
- values:
- let
- inherit (lib.lists) unique;
- show =
- v:
- if builtins.isString v then
- ''"${v}"''
- else if builtins.isInt v then
- toString v
- else if builtins.isBool v then
- boolToString v
+ if intersecting == { } then
+ lhs.specialArgs // rhs.specialArgs
else
- ''<${builtins.typeOf v}>'';
- in
- mkOptionType rec {
- name = "enum";
- description =
- # Length 0 or 1 enums may occur in a design pattern with type merging
- # where an "interface" module declares an empty enum and other modules
- # provide implementations, each extending the enum with their own
- # identifier.
- if values == [ ] then
- "impossible (empty enum)"
- else if builtins.length values == 1 then
- "value ${show (builtins.head values)} (singular enum)"
+ throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\"";
+ 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
else
- "one of ${concatMapStringsSep ", " show values}";
- descriptionClass = if builtins.length values < 2 then "noun" else "conjunction";
- check = flip elem values;
- merge = mergeEqualOption;
- functor = (defaultFunctor name) // {
- payload = { inherit values; };
- type = payload: types.enum payload.values;
- binOp = a: b: { values = unique (a.values ++ b.values); };
- };
- };
-
- # Either value of type `t1` or `t2`.
- either =
- t1: t2:
- mkOptionType rec {
- name = "either";
+ throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values";
description =
- if t1.descriptionClass or null == "nonRestrictiveClause" then
- # Plain, but add comma
- "${t1.description}, or ${
- optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t2
- }"
+ if lhs.description == null then
+ rhs.description
+ else if rhs.description == null then
+ lhs.description
+ else if lhs.description == rhs.description then
+ lhs.description
else
- "${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t1} or ${
- optionDescriptionPhrase (
- class: class == "noun" || class == "conjunction" || class == "composite"
- ) t2
- }";
- descriptionClass = "conjunction";
- check = {
- __functor = _self: x: t1.check x || t2.check x;
- isV2MergeCoherent = true;
- };
- merge = {
- __functor =
- self: loc: defs:
- (self.v2 { inherit loc defs; }).value;
- v2 =
- { loc, defs }:
- let
- t1CheckedAndMerged =
- if t1.merge ? v2 then
- checkV2MergeCoherence loc t1 (t1.merge.v2 { inherit loc defs; })
- else
- {
- value = t1.merge loc defs;
- headError = checkDefsForError t1.check loc defs;
- valueMeta = { };
- };
- t2CheckedAndMerged =
- if t2.merge ? v2 then
- checkV2MergeCoherence loc t2 (t2.merge.v2 { inherit loc defs; })
- else
- {
- value = t2.merge loc defs;
- headError = checkDefsForError t2.check loc defs;
- valueMeta = { };
- };
-
- checkedAndMerged =
- if t1CheckedAndMerged.headError == null then
- t1CheckedAndMerged
- else if t2CheckedAndMerged.headError == null then
- t2CheckedAndMerged
- else
- rec {
- valueMeta = {
- inherit headError;
- };
- headError = {
- message = "The option `${showOption loc}` is neither a value of type `${t1.description}` nor `${t2.description}`, Definition values: ${showDefs defs}";
- };
- value = lib.warn ''
- One or more definitions did not pass the type-check of the 'either' type.
- ${headError.message}
- If `either`, `oneOf` or similar is used in freeformType, ensure that it is preceded by an 'attrsOf' such as: `freeformType = types.attrsOf (types.either t1 t2)`.
- Otherwise consider using the correct type for the option `${showOption loc}`. This will be an error in Nixpkgs 26.05.
- '' (mergeOneOption loc defs);
- };
- in
- checkedAndMerged;
- };
- typeMerge =
- f':
- let
- mt1 = t1.typeMerge (elemAt f'.payload.elemType 0).functor;
- mt2 = t2.typeMerge (elemAt f'.payload.elemType 1).functor;
- in
- if (name == f'.name) && (mt1 != null) && (mt2 != null) then functor.type mt1 mt2 else null;
- functor = elemTypeFunctor name {
- elemType = [
- t1
- t2
- ];
- };
- nestedTypes.left = t1;
- nestedTypes.right = t2;
+ throw "A submoduleWith option is declared multiple times with conflicting descriptions";
};
+ };
+ };
- # Any of the types in the given list
- oneOf =
- ts:
- let
- head' =
- if ts == [ ] then throw "types.oneOf needs to get at least one type in its argument" else head ts;
- tail' = tail ts;
- in
- foldl' either head' tail';
+ # A value from a set of allowed ones.
+ enum =
+ values:
+ let
+ inherit (lib.lists) unique;
+ show =
+ v:
+ if builtins.isString v then
+ ''"${v}"''
+ else if builtins.isInt v then
+ toString v
+ else if builtins.isBool v then
+ boolToString v
+ else
+ ''<${builtins.typeOf v}>'';
+ in
+ mkOptionType rec {
+ name = "enum";
+ description =
+ # Length 0 or 1 enums may occur in a design pattern with type merging
+ # where an "interface" module declares an empty enum and other modules
+ # provide implementations, each extending the enum with their own
+ # identifier.
+ if values == [ ] then
+ "impossible (empty enum)"
+ else if builtins.length values == 1 then
+ "value ${show (builtins.head values)} (singular enum)"
+ else
+ "one of ${concatMapStringsSep ", " show values}";
+ descriptionClass = if builtins.length values < 2 then "noun" else "conjunction";
+ check = flip elem values;
+ merge = mergeEqualOption;
+ functor = (defaultFunctor name) // {
+ payload = { inherit values; };
+ type = payload: lib.types.enum payload.values;
+ binOp = a: b: { values = unique (a.values ++ b.values); };
+ };
+ };
- # 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
- ) "coercedTo: coercedType must not have submodules (it’s a ${coercedType.description})";
- mkOptionType rec {
- name = "coercedTo";
- description = "${optionDescriptionPhrase (class: class == "noun") finalType} or ${
- optionDescriptionPhrase (class: class == "noun") coercedType
- } convertible to it";
- check = {
- __functor = _self: x: (coercedType.check x && finalType.check (coerceFunc x)) || finalType.check x;
- isV2MergeCoherent = true;
- };
- merge = {
- __functor =
- self: loc: defs:
- (self.v2 { inherit loc defs; }).value;
- v2 =
- { loc, defs }:
- let
- finalDefs = (
- map (
- def:
- def
- // {
- value =
- let
- merged =
- if coercedType.merge ? v2 then
- checkV2MergeCoherence loc coercedType (
- coercedType.merge.v2 {
- inherit loc;
- defs = [ def ];
- }
- )
- else
- null;
- in
- if coercedType.merge ? v2 then
- if merged.headError == null then coerceFunc def.value else def.value
- else if coercedType.check def.value then
- coerceFunc def.value
- else
- def.value;
- }
- ) defs
- );
- in
- if finalType.merge ? v2 then
- checkV2MergeCoherence loc finalType (
- finalType.merge.v2 {
- inherit loc;
- defs = finalDefs;
- }
- )
+ # Either value of type `t1` or `t2`.
+ either =
+ t1: t2:
+ mkOptionType rec {
+ name = "either";
+ description =
+ if t1.descriptionClass or null == "nonRestrictiveClause" then
+ # Plain, but add comma
+ "${t1.description}, or ${
+ optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t2
+ }"
+ else
+ "${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t1} or ${
+ optionDescriptionPhrase (
+ class: class == "noun" || class == "conjunction" || class == "composite"
+ ) t2
+ }";
+ descriptionClass = "conjunction";
+ check = {
+ __functor = _self: x: t1.check x || t2.check x;
+ isV2MergeCoherent = true;
+ };
+ merge = {
+ __functor =
+ self: loc: defs:
+ (self.v2 { inherit loc defs; }).value;
+ v2 =
+ { loc, defs }:
+ let
+ t1CheckedAndMerged =
+ if t1.merge ? v2 then
+ checkV2MergeCoherence loc t1 (t1.merge.v2 { inherit loc defs; })
else
{
- value = finalType.merge loc finalDefs;
+ value = t1.merge loc defs;
+ headError = checkDefsForError t1.check loc defs;
+ valueMeta = { };
+ };
+ t2CheckedAndMerged =
+ if t2.merge ? v2 then
+ checkV2MergeCoherence loc t2 (t2.merge.v2 { inherit loc defs; })
+ else
+ {
+ value = t2.merge loc defs;
+ headError = checkDefsForError t2.check loc defs;
valueMeta = { };
- headError = checkDefsForError check loc defs;
};
- };
- emptyValue = finalType.emptyValue;
- getSubOptions = finalType.getSubOptions;
- getSubModules = finalType.getSubModules;
- substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
- typeMerge = t: null;
- functor = (defaultFunctor name) // {
- wrappedDeprecationMessage = makeWrappedDeprecationMessage { elemType = finalType; };
- };
- nestedTypes.coercedType = coercedType;
- nestedTypes.finalType = finalType;
- };
- /**
- Augment the given type with an additional type check function.
+ checkedAndMerged =
+ if t1CheckedAndMerged.headError == null then
+ t1CheckedAndMerged
+ else if t2CheckedAndMerged.headError == null then
+ t2CheckedAndMerged
+ else
+ rec {
+ valueMeta = {
+ inherit headError;
+ };
+ headError = {
+ message = "The option `${showOption loc}` is neither a value of type `${t1.description}` nor `${t2.description}`, Definition values: ${showDefs defs}";
+ };
+ value = lib.warn ''
+ One or more definitions did not pass the type-check of the 'either' type.
+ ${headError.message}
+ If `either`, `oneOf` or similar is used in freeformType, ensure that it is preceded by an 'attrsOf' such as: `freeformType = types.attrsOf (types.either t1 t2)`.
+ Otherwise consider using the correct type for the option `${showOption loc}`. This will be an error in Nixpkgs 26.05.
+ '' (mergeOneOption loc defs);
+ };
+ in
+ checkedAndMerged;
+ };
+ typeMerge =
+ f':
+ let
+ mt1 = t1.typeMerge (elemAt f'.payload.elemType 0).functor;
+ mt2 = t2.typeMerge (elemAt f'.payload.elemType 1).functor;
+ in
+ if (name == f'.name) && (mt1 != null) && (mt2 != null) then functor.type mt1 mt2 else null;
+ functor = elemTypeFunctor name {
+ elemType = [
+ t1
+ t2
+ ];
+ };
+ nestedTypes.left = t1;
+ nestedTypes.right = t2;
+ };
- :::{.warning}
- This function has some broken behavior see: [#396021](https://github.com/NixOS/nixpkgs/issues/396021)
- Fixing is not trivial, we appreciate any help!
- :::
- */
- addCheck =
- elemType: check:
- if elemType.merge ? v2 then
- elemType
- // {
- check = {
- __functor = _self: x: elemType.check x && check x;
- isV2MergeCoherent = true;
- };
- merge = {
- __functor =
- self: loc: defs:
- (self.v2 { inherit loc defs; }).value;
- v2 =
- { loc, defs }:
- let
- orig = checkV2MergeCoherence loc elemType (elemType.merge.v2 { inherit loc defs; });
- headError' = if orig.headError != null then orig.headError else checkDefsForError check loc defs;
- in
- orig
+ # Any of the types in the given list
+ oneOf =
+ ts:
+ let
+ head' =
+ if ts == [ ] then throw "types.oneOf needs to get at least one type in its argument" else head ts;
+ tail' = tail ts;
+ in
+ foldl' either head' tail';
+
+ # 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
+ ) "coercedTo: coercedType must not have submodules (it’s a ${coercedType.description})";
+ mkOptionType rec {
+ name = "coercedTo";
+ description = "${optionDescriptionPhrase (class: class == "noun") finalType} or ${
+ optionDescriptionPhrase (class: class == "noun") coercedType
+ } convertible to it";
+ check = {
+ __functor = _self: x: (coercedType.check x && finalType.check (coerceFunc x)) || finalType.check x;
+ isV2MergeCoherent = true;
+ };
+ merge = {
+ __functor =
+ self: loc: defs:
+ (self.v2 { inherit loc defs; }).value;
+ v2 =
+ { loc, defs }:
+ let
+ finalDefs = (
+ map (
+ def:
+ def
// {
- headError = headError';
- };
+ value =
+ let
+ merged =
+ if coercedType.merge ? v2 then
+ checkV2MergeCoherence loc coercedType (
+ coercedType.merge.v2 {
+ inherit loc;
+ defs = [ def ];
+ }
+ )
+ else
+ null;
+ in
+ if coercedType.merge ? v2 then
+ if merged.headError == null then coerceFunc def.value else def.value
+ else if coercedType.check def.value then
+ coerceFunc def.value
+ else
+ def.value;
+ }
+ ) defs
+ );
+ in
+ if finalType.merge ? v2 then
+ checkV2MergeCoherence loc finalType (
+ finalType.merge.v2 {
+ inherit loc;
+ defs = finalDefs;
+ }
+ )
+ else
+ {
+ value = finalType.merge loc finalDefs;
+ valueMeta = { };
+ headError = checkDefsForError check loc defs;
};
- }
- else
- elemType
- // {
- check = x: elemType.check x && check x;
- };
+ };
+ emptyValue = finalType.emptyValue;
+ getSubOptions = finalType.getSubOptions;
+ getSubModules = finalType.getSubModules;
+ substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
+ typeMerge = t: null;
+ functor = (defaultFunctor name) // {
+ wrappedDeprecationMessage = makeWrappedDeprecationMessage { elemType = finalType; };
+ };
+ nestedTypes.coercedType = coercedType;
+ nestedTypes.finalType = finalType;
};
- /**
- Merges two option types together.
+ /**
+ Augment the given type with an additional type check function.
+
+ :::{.warning}
+ This function has some broken behavior see: [#396021](https://github.com/NixOS/nixpkgs/issues/396021)
+ Fixing is not trivial, we appreciate any help!
+ :::
+ */
+ addCheck =
+ elemType: check:
+ if elemType.merge ? v2 then
+ elemType
+ // {
+ check = {
+ __functor = _self: x: elemType.check x && check x;
+ isV2MergeCoherent = true;
+ };
+ merge = {
+ __functor =
+ self: loc: defs:
+ (self.v2 { inherit loc defs; }).value;
+ v2 =
+ { loc, defs }:
+ let
+ orig = checkV2MergeCoherence loc elemType (elemType.merge.v2 { inherit loc defs; });
+ headError' = if orig.headError != null then orig.headError else checkDefsForError check loc defs;
+ in
+ orig
+ // {
+ headError = headError';
+ };
+ };
+ }
+ else
+ elemType
+ // {
+ check = x: elemType.check x && check x;
+ };
+
+ /**
+ Merges two option types together.
- :::{.note}
- Uses the type merge function of the first type, to merge it with the second type.
+ :::{.note}
+ Uses the type merge function of the first type, to merge it with the second type.
- Usually types can only be merged if they are of the same type
- :::
+ Usually types can only be merged if they are of the same type
+ :::
- # Inputs
+ # Inputs
- : `a` (option type): The first option type.
- : `b` (option type): The second option type.
+ : `a` (option type): The first option type.
+ : `b` (option type): The second option type.
- # Returns
+ # Returns
- - The merged option type.
- - `{ _type = "merge-error"; error = "Cannot merge types"; }` if the types can't be merged.
+ - The merged option type.
+ - `{ _type = "merge-error"; error = "Cannot merge types"; }` if the types can't be merged.
- # Examples
- :::{.example}
- ## `lib.types.mergeTypes` usage example
- ```nix
- let
- enumAB = lib.types.enum ["A" "B"];
- enumXY = lib.types.enum ["X" "Y"];
- # This operation could be notated as: [ A ] | [ B ] -> [ A B ]
- merged = lib.types.mergeTypes enumAB enumXY; # -> enum [ "A" "B" "X" "Y" ]
- in
- assert merged.check "A"; # true
- assert merged.check "B"; # true
- assert merged.check "X"; # true
- assert merged.check "Y"; # true
- merged.check "C" # false
- ```
- :::
- */
- mergeTypes =
- a: b:
- assert isOptionType a && isOptionType b;
- let
- merged = a.typeMerge b.functor;
- in
- if merged == null then setType "merge-error" { error = "Cannot merge types"; } else merged;
- };
+ # Examples
+ :::{.example}
+ ## `lib.types.mergeTypes` usage example
+ ```nix
+ let
+ enumAB = lib.types.enum ["A" "B"];
+ enumXY = lib.types.enum ["X" "Y"];
+ # This operation could be notated as: [ A ] | [ B ] -> [ A B ]
+ merged = lib.types.mergeTypes enumAB enumXY; # -> enum [ "A" "B" "X" "Y" ]
+ in
+ assert merged.check "A"; # true
+ assert merged.check "B"; # true
+ assert merged.check "X"; # true
+ assert merged.check "Y"; # true
+ merged.check "C" # false
+ ```
+ :::
+ */
+ mergeTypes =
+ a: b:
+ assert isOptionType a && isOptionType b;
+ let
+ merged = a.typeMerge b.functor;
+ in
+ if merged == null then setType "merge-error" { error = "Cannot merge types"; } else merged;
-in
-outer_types // outer_types.types
+ # TODO: Migrate usage of lib.types.types in nixpkgs
+ # Then add a deprecation warning
+ types = lib.types;
+}