2017-07-29 00:05:35 +00:00
|
|
|
|
{ lib }:
|
|
|
|
|
|
2020-10-20 11:47:24 +00:00
|
|
|
|
let
|
2020-10-20 14:10:21 +00:00
|
|
|
|
inherit (lib)
|
|
|
|
|
all
|
|
|
|
|
any
|
2020-10-20 11:47:24 +00:00
|
|
|
|
attrByPath
|
|
|
|
|
attrNames
|
|
|
|
|
catAttrs
|
2020-10-20 14:10:21 +00:00
|
|
|
|
concatLists
|
|
|
|
|
concatMap
|
|
|
|
|
count
|
|
|
|
|
elem
|
|
|
|
|
filter
|
|
|
|
|
findFirst
|
|
|
|
|
flip
|
|
|
|
|
foldl
|
|
|
|
|
foldl'
|
2020-10-20 11:47:24 +00:00
|
|
|
|
getAttrFromPath
|
2020-10-20 14:10:21 +00:00
|
|
|
|
head
|
|
|
|
|
id
|
|
|
|
|
imap1
|
|
|
|
|
isAttrs
|
|
|
|
|
isBool
|
|
|
|
|
isFunction
|
|
|
|
|
isString
|
|
|
|
|
length
|
2020-10-20 11:47:24 +00:00
|
|
|
|
mapAttrs
|
|
|
|
|
mapAttrsToList
|
2020-10-20 14:10:21 +00:00
|
|
|
|
mapAttrsRecursiveCond
|
|
|
|
|
min
|
|
|
|
|
optional
|
2020-10-20 11:47:24 +00:00
|
|
|
|
optionalAttrs
|
2020-10-20 14:10:21 +00:00
|
|
|
|
optionalString
|
2020-10-20 11:47:24 +00:00
|
|
|
|
recursiveUpdate
|
2020-10-20 14:10:21 +00:00
|
|
|
|
reverseList sort
|
2020-10-20 11:47:24 +00:00
|
|
|
|
setAttrByPath
|
|
|
|
|
toList
|
|
|
|
|
types
|
|
|
|
|
warn
|
|
|
|
|
;
|
2020-10-20 14:10:21 +00:00
|
|
|
|
inherit (lib.options)
|
|
|
|
|
isOption
|
|
|
|
|
mkOption
|
|
|
|
|
showDefs
|
|
|
|
|
showFiles
|
|
|
|
|
showOption
|
|
|
|
|
unknownModule
|
2020-09-01 21:32:57 +00:00
|
|
|
|
literalExample
|
2020-10-20 11:47:24 +00:00
|
|
|
|
;
|
|
|
|
|
in
|
2009-07-13 16:18:52 +00:00
|
|
|
|
|
|
|
|
|
rec {
|
|
|
|
|
|
2013-10-27 23:56:22 +00:00
|
|
|
|
/* Evaluate a set of modules. The result is a set of two
|
|
|
|
|
attributes: ‘options’: the nested set of all option declarations,
|
2014-05-05 20:30:51 +00:00
|
|
|
|
and ‘config’: the nested set of all option values.
|
|
|
|
|
!!! Please think twice before adding to this argument list! The more
|
|
|
|
|
that is specified here instead of in the modules themselves the harder
|
|
|
|
|
it is to transparently move a set of modules to be a submodule of another
|
|
|
|
|
config (as the proper arguments need to be replicated at each call to
|
|
|
|
|
evalModules) and the less declarative the module set is. */
|
|
|
|
|
evalModules = { modules
|
|
|
|
|
, prefix ? []
|
2015-06-06 22:38:08 +00:00
|
|
|
|
, # This should only be used for special arguments that need to be evaluated
|
|
|
|
|
# when resolving module structure (like in imports). For everything else,
|
2017-02-14 22:18:44 +00:00
|
|
|
|
# there's _module.args. If specialArgs.modulesPath is defined it will be
|
|
|
|
|
# used as the base path for disabledModules.
|
2015-06-06 22:38:08 +00:00
|
|
|
|
specialArgs ? {}
|
2015-03-12 22:19:23 +00:00
|
|
|
|
, # This would be remove in the future, Prefer _module.args option instead.
|
2014-05-05 20:30:51 +00:00
|
|
|
|
args ? {}
|
2016-03-01 19:47:08 +00:00
|
|
|
|
, # This would be remove in the future, Prefer _module.check option instead.
|
|
|
|
|
check ? true
|
2014-05-05 20:30:51 +00:00
|
|
|
|
}:
|
2009-07-13 16:18:52 +00:00
|
|
|
|
let
|
2015-03-12 22:19:23 +00:00
|
|
|
|
# This internal module declare internal options under the `_module'
|
|
|
|
|
# attribute. These options are fragile, as they are used by the
|
|
|
|
|
# module system to change the interpretation of modules.
|
2014-05-05 19:52:33 +00:00
|
|
|
|
internalModule = rec {
|
2020-09-02 16:01:10 +00:00
|
|
|
|
# FIXME: Using ./modules.nix directly breaks the doc for some reason
|
|
|
|
|
_file = "lib/modules.nix";
|
2014-05-05 19:18:53 +00:00
|
|
|
|
|
2014-05-05 19:52:33 +00:00
|
|
|
|
key = _file;
|
2014-05-05 19:18:53 +00:00
|
|
|
|
|
2020-09-02 16:01:10 +00:00
|
|
|
|
# These options are set to be internal only for prefix != [], aka it's
|
|
|
|
|
# a submodule evaluation. This way their docs are displayed only once
|
|
|
|
|
# as a top-level NixOS option, but will be hidden for all submodules,
|
|
|
|
|
# even though they are available there too
|
2014-05-05 19:18:53 +00:00
|
|
|
|
options = {
|
2015-03-12 22:19:23 +00:00
|
|
|
|
_module.args = mkOption {
|
2019-10-02 22:12:47 +00:00
|
|
|
|
# Because things like `mkIf` are entirely useless for
|
|
|
|
|
# `_module.args` (because there's no way modules can check which
|
|
|
|
|
# arguments were passed), we'll use `lazyAttrsOf` which drops
|
|
|
|
|
# support for that, in turn it's lazy in its values. This means e.g.
|
|
|
|
|
# a `_module.args.pkgs = import (fetchTarball { ... }) {}` won't
|
|
|
|
|
# start a download when `pkgs` wasn't evaluated.
|
|
|
|
|
type = types.lazyAttrsOf types.unspecified;
|
2020-09-02 16:01:10 +00:00
|
|
|
|
internal = prefix != [];
|
2015-03-12 22:19:23 +00:00
|
|
|
|
description = "Arguments passed to each module.";
|
2014-05-05 19:18:53 +00:00
|
|
|
|
};
|
2014-05-05 20:23:57 +00:00
|
|
|
|
|
2015-03-12 22:19:23 +00:00
|
|
|
|
_module.check = mkOption {
|
2015-06-15 16:10:26 +00:00
|
|
|
|
type = types.bool;
|
2020-09-02 16:01:10 +00:00
|
|
|
|
internal = prefix != [];
|
2016-03-01 19:47:08 +00:00
|
|
|
|
default = check;
|
2015-03-12 22:19:23 +00:00
|
|
|
|
description = "Whether to check whether all option definitions have matching declarations.";
|
2014-05-05 20:23:57 +00:00
|
|
|
|
};
|
lib/modules: Implement freeform modules
For programs that have a lot of (Nix-representable) configuration options,
a simple way to represent this in a NixOS module is to declare an
option of a type like `attrsOf str`, representing a key-value mapping
which then gets generated into a config file. However with such a type,
there's no way to add type checking for only some key values.
On the other end of the spectrum, one can declare a single separate
option for every key value that the program supports, ending up with a module
with potentially 100s of options. This has the benefit that every value
gets type checked, catching mistakes at evaluation time already. However
the disadvantage is that the module becomes big, becomes coupled to the
program version and takes a lot of effort to write and maintain.
Previously there was a middle ground between these two
extremes: Declare an option of a type like `attrsOf str`, but declare
additional separate options for the values you wish to have type
checked, and assign their values to the `attrsOf str` option. While this
works decently, it has the problem of duplicated options, since now both
the additional options and the `attrsOf str` option can be used to set a
key value. This leads to confusion about what should happen if both are
set, which defaults should apply, and more.
Now with this change, a middle ground becomes available that solves above
problems: The module system now supports setting a freeform type, which
gets used for all definitions that don't have an associated option. This
means that you can now declare all options you wish to have type
checked, while for the rest a freeform type like `attrsOf str` can be
used.
2020-03-19 23:17:41 +00:00
|
|
|
|
|
|
|
|
|
_module.freeformType = mkOption {
|
|
|
|
|
# Disallow merging for now, but could be implemented nicely with a `types.optionType`
|
|
|
|
|
type = types.nullOr (types.uniq types.attrs);
|
2020-09-02 16:01:10 +00:00
|
|
|
|
internal = prefix != [];
|
lib/modules: Implement freeform modules
For programs that have a lot of (Nix-representable) configuration options,
a simple way to represent this in a NixOS module is to declare an
option of a type like `attrsOf str`, representing a key-value mapping
which then gets generated into a config file. However with such a type,
there's no way to add type checking for only some key values.
On the other end of the spectrum, one can declare a single separate
option for every key value that the program supports, ending up with a module
with potentially 100s of options. This has the benefit that every value
gets type checked, catching mistakes at evaluation time already. However
the disadvantage is that the module becomes big, becomes coupled to the
program version and takes a lot of effort to write and maintain.
Previously there was a middle ground between these two
extremes: Declare an option of a type like `attrsOf str`, but declare
additional separate options for the values you wish to have type
checked, and assign their values to the `attrsOf str` option. While this
works decently, it has the problem of duplicated options, since now both
the additional options and the `attrsOf str` option can be used to set a
key value. This leads to confusion about what should happen if both are
set, which defaults should apply, and more.
Now with this change, a middle ground becomes available that solves above
problems: The module system now supports setting a freeform type, which
gets used for all definitions that don't have an associated option. This
means that you can now declare all options you wish to have type
checked, while for the rest a freeform type like `attrsOf str` can be
used.
2020-03-19 23:17:41 +00:00
|
|
|
|
default = null;
|
|
|
|
|
description = ''
|
|
|
|
|
If set, merge all definitions that don't have an associated option
|
|
|
|
|
together using this type. The result then gets combined with the
|
|
|
|
|
values of all declared options to produce the final <literal>
|
|
|
|
|
config</literal> value.
|
|
|
|
|
|
|
|
|
|
If this is <literal>null</literal>, definitions without an option
|
|
|
|
|
will throw an error unless <option>_module.check</option> is
|
|
|
|
|
turned off.
|
|
|
|
|
'';
|
|
|
|
|
};
|
2020-09-01 21:32:57 +00:00
|
|
|
|
|
|
|
|
|
_module.assertions = mkOption {
|
|
|
|
|
description = ''
|
|
|
|
|
Assertions and warnings to trigger during module evaluation. The
|
|
|
|
|
attribute name will be displayed when it is triggered, allowing
|
|
|
|
|
users to disable/change these assertions again if necessary. See
|
|
|
|
|
the section on Warnings and Assertions in the manual for more
|
|
|
|
|
information.
|
|
|
|
|
'';
|
|
|
|
|
example = literalExample ''
|
|
|
|
|
{
|
|
|
|
|
gpgSshAgent = {
|
|
|
|
|
enable = config.programs.gnupg.agent.enableSSHSupport && config.programs.ssh.startAgent;
|
|
|
|
|
message = "You can't use ssh-agent and GnuPG agent with SSH support enabled at the same time!";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
grafanaPassword = {
|
|
|
|
|
enable = config.services.grafana.database.password != "";
|
|
|
|
|
message = "Grafana passwords will be stored as plaintext in the Nix store!";
|
|
|
|
|
type = "warning";
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
'';
|
|
|
|
|
default = {};
|
2020-09-02 16:01:10 +00:00
|
|
|
|
internal = prefix != [];
|
2020-09-01 21:32:57 +00:00
|
|
|
|
type = types.attrsOf (types.submodule {
|
|
|
|
|
# TODO: Rename to assertion? Or allow also setting assertion?
|
|
|
|
|
options.enable = mkOption {
|
|
|
|
|
description = ''
|
|
|
|
|
Whether to enable this assertion.
|
|
|
|
|
<note><para>
|
|
|
|
|
This is the inverse of asserting a condition: If a certain
|
|
|
|
|
condition should be <literal>true</literal>, then this
|
|
|
|
|
option should be set to <literal>false</literal> when that
|
|
|
|
|
case occurs
|
|
|
|
|
</para></note>
|
|
|
|
|
'';
|
|
|
|
|
type = types.bool;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
options.type = mkOption {
|
|
|
|
|
description = ''
|
|
|
|
|
The type of the assertion. The default
|
|
|
|
|
<literal>"error"</literal> type will cause evaluation to fail,
|
|
|
|
|
while the <literal>"warning"</literal> type will only show a
|
|
|
|
|
warning.
|
|
|
|
|
'';
|
|
|
|
|
type = types.enum [ "error" "warning" ];
|
|
|
|
|
default = "error";
|
|
|
|
|
example = "warning";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
options.message = mkOption {
|
|
|
|
|
description = ''
|
|
|
|
|
The assertion message to display if this assertion triggers.
|
|
|
|
|
To display option names in the message, add
|
|
|
|
|
<literal>options</literal> to the module function arguments
|
|
|
|
|
and use <literal>''${options.path.to.option}</literal>.
|
|
|
|
|
'';
|
|
|
|
|
type = types.str;
|
|
|
|
|
example = literalExample ''
|
|
|
|
|
Enabling both ''${options.services.foo.enable} and ''${options.services.bar.enable} is not possible.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
options.triggerPath = mkOption {
|
|
|
|
|
description = ''
|
|
|
|
|
The <literal>config</literal> path which when evaluated should
|
|
|
|
|
trigger this assertion. By default this is
|
|
|
|
|
<literal>[]</literal>, meaning evaluating
|
|
|
|
|
<literal>config</literal> at all will trigger the assertion.
|
|
|
|
|
On NixOS this default is changed to
|
|
|
|
|
<literal>[ "system" "build" "toplevel"</literal> such that
|
|
|
|
|
only a system evaluation triggers the assertions.
|
|
|
|
|
<warning><para>
|
|
|
|
|
Evaluating <literal>config</literal> from within the current
|
|
|
|
|
module evaluation doesn't cause a trigger. Only accessing it
|
|
|
|
|
from outside will do that. This means it's easy to miss
|
|
|
|
|
assertions if this option doesn't have an externally-accessed
|
|
|
|
|
value.
|
|
|
|
|
</para></warning>
|
|
|
|
|
'';
|
|
|
|
|
# Mark as internal as it's easy to misuse it
|
|
|
|
|
internal = true;
|
|
|
|
|
type = types.uniq (types.listOf types.str);
|
|
|
|
|
# Default to [], causing assertions to be triggered when
|
|
|
|
|
# anything is evaluated. This is a safe and convenient default.
|
|
|
|
|
default = [];
|
|
|
|
|
example = [ "system" "build" "vm" ];
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
};
|
2014-05-05 19:18:53 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
config = {
|
2015-03-12 22:19:23 +00:00
|
|
|
|
_module.args = args;
|
2014-05-05 19:18:53 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
2015-03-12 22:19:23 +00:00
|
|
|
|
|
2020-03-18 19:09:09 +00:00
|
|
|
|
merged =
|
2020-03-19 22:45:23 +00:00
|
|
|
|
let collected = collectModules
|
|
|
|
|
(specialArgs.modulesPath or "")
|
|
|
|
|
(modules ++ [ internalModule ])
|
|
|
|
|
({ inherit lib options config; } // specialArgs);
|
|
|
|
|
in mergeModules prefix (reverseList collected);
|
2015-03-12 22:19:23 +00:00
|
|
|
|
|
2020-03-18 19:09:09 +00:00
|
|
|
|
options = merged.matchedOptions;
|
|
|
|
|
|
lib/modules: Implement freeform modules
For programs that have a lot of (Nix-representable) configuration options,
a simple way to represent this in a NixOS module is to declare an
option of a type like `attrsOf str`, representing a key-value mapping
which then gets generated into a config file. However with such a type,
there's no way to add type checking for only some key values.
On the other end of the spectrum, one can declare a single separate
option for every key value that the program supports, ending up with a module
with potentially 100s of options. This has the benefit that every value
gets type checked, catching mistakes at evaluation time already. However
the disadvantage is that the module becomes big, becomes coupled to the
program version and takes a lot of effort to write and maintain.
Previously there was a middle ground between these two
extremes: Declare an option of a type like `attrsOf str`, but declare
additional separate options for the values you wish to have type
checked, and assign their values to the `attrsOf str` option. While this
works decently, it has the problem of duplicated options, since now both
the additional options and the `attrsOf str` option can be used to set a
key value. This leads to confusion about what should happen if both are
set, which defaults should apply, and more.
Now with this change, a middle ground becomes available that solves above
problems: The module system now supports setting a freeform type, which
gets used for all definitions that don't have an associated option. This
means that you can now declare all options you wish to have type
checked, while for the rest a freeform type like `attrsOf str` can be
used.
2020-03-19 23:17:41 +00:00
|
|
|
|
config =
|
|
|
|
|
let
|
|
|
|
|
|
|
|
|
|
# For definitions that have an associated option
|
|
|
|
|
declaredConfig = mapAttrsRecursiveCond (v: ! isOption v) (_: v: v.value) options;
|
|
|
|
|
|
|
|
|
|
# If freeformType is set, this is for definitions that don't have an associated option
|
|
|
|
|
freeformConfig =
|
|
|
|
|
let
|
|
|
|
|
defs = map (def: {
|
|
|
|
|
file = def.file;
|
|
|
|
|
value = setAttrByPath def.prefix def.value;
|
|
|
|
|
}) merged.unmatchedDefns;
|
2020-08-03 22:08:43 +00:00
|
|
|
|
in if defs == [] then {}
|
|
|
|
|
else declaredConfig._module.freeformType.merge prefix defs;
|
lib/modules: Implement freeform modules
For programs that have a lot of (Nix-representable) configuration options,
a simple way to represent this in a NixOS module is to declare an
option of a type like `attrsOf str`, representing a key-value mapping
which then gets generated into a config file. However with such a type,
there's no way to add type checking for only some key values.
On the other end of the spectrum, one can declare a single separate
option for every key value that the program supports, ending up with a module
with potentially 100s of options. This has the benefit that every value
gets type checked, catching mistakes at evaluation time already. However
the disadvantage is that the module becomes big, becomes coupled to the
program version and takes a lot of effort to write and maintain.
Previously there was a middle ground between these two
extremes: Declare an option of a type like `attrsOf str`, but declare
additional separate options for the values you wish to have type
checked, and assign their values to the `attrsOf str` option. While this
works decently, it has the problem of duplicated options, since now both
the additional options and the `attrsOf str` option can be used to set a
key value. This leads to confusion about what should happen if both are
set, which defaults should apply, and more.
Now with this change, a middle ground becomes available that solves above
problems: The module system now supports setting a freeform type, which
gets used for all definitions that don't have an associated option. This
means that you can now declare all options you wish to have type
checked, while for the rest a freeform type like `attrsOf str` can be
used.
2020-03-19 23:17:41 +00:00
|
|
|
|
|
|
|
|
|
in if declaredConfig._module.freeformType == null then declaredConfig
|
|
|
|
|
# Because all definitions that had an associated option ended in
|
|
|
|
|
# declaredConfig, freeformConfig can only contain the non-option
|
|
|
|
|
# paths, meaning recursiveUpdate will never override any value
|
|
|
|
|
else recursiveUpdate freeformConfig declaredConfig;
|
2015-03-12 22:19:23 +00:00
|
|
|
|
|
2020-09-01 21:32:57 +00:00
|
|
|
|
/*
|
|
|
|
|
Inject a list of assertions into a config value, corresponding to their
|
|
|
|
|
triggerPath (meaning when that path is accessed from the result of this
|
|
|
|
|
function, the assertion triggers).
|
|
|
|
|
*/
|
|
|
|
|
injectAssertions = assertions: config: let
|
|
|
|
|
# Partition into assertions that are triggered on this level and ones that aren't
|
2020-09-01 22:17:26 +00:00
|
|
|
|
parted = lib.partition (a: length a.triggerPath == 0) assertions;
|
2020-09-01 21:32:57 +00:00
|
|
|
|
|
|
|
|
|
# From the ones that are triggered, filter out ones that aren't enabled
|
|
|
|
|
# and group into warnings/errors
|
2020-09-01 22:17:26 +00:00
|
|
|
|
byType = lib.groupBy (a: a.type) (filter (a: a.enable) parted.right);
|
2020-09-01 21:32:57 +00:00
|
|
|
|
|
|
|
|
|
# Triggers semantically are just lib.id, but they print warning cause errors in addition
|
|
|
|
|
warningTrigger = value: lib.foldr (w: warn w.show) value (byType.warning or []);
|
|
|
|
|
errorTrigger = value:
|
|
|
|
|
if byType.error or [] == [] then value else
|
|
|
|
|
throw ''
|
|
|
|
|
Failed assertions:
|
2020-09-01 22:17:26 +00:00
|
|
|
|
${lib.concatMapStringsSep "\n" (a: "- ${a.show}") byType.error}
|
2020-09-01 21:32:57 +00:00
|
|
|
|
'';
|
|
|
|
|
# Trigger for both warnings and errors
|
|
|
|
|
trigger = value: warningTrigger (errorTrigger value);
|
|
|
|
|
|
|
|
|
|
# From the non-triggered assertions, split off the first element of triggerPath
|
|
|
|
|
# to get a mapping from nested attributes to a list of assertions for that attribute
|
2020-09-01 22:17:26 +00:00
|
|
|
|
nested = lib.zipAttrs (map (a: {
|
2020-09-01 21:32:57 +00:00
|
|
|
|
${head a.triggerPath} = a // {
|
2020-09-01 22:17:26 +00:00
|
|
|
|
triggerPath = lib.tail a.triggerPath;
|
2020-09-01 21:32:57 +00:00
|
|
|
|
};
|
|
|
|
|
}) parted.wrong);
|
|
|
|
|
|
|
|
|
|
# Recursively inject assertions if config is an attribute set and we
|
|
|
|
|
# have assertions under its attributes
|
|
|
|
|
result =
|
|
|
|
|
if isAttrs config
|
|
|
|
|
then
|
|
|
|
|
mapAttrs (name: value:
|
|
|
|
|
if nested ? ${name}
|
|
|
|
|
then injectAssertions nested.${name} value
|
|
|
|
|
else value
|
|
|
|
|
) config
|
|
|
|
|
else config;
|
|
|
|
|
in trigger result;
|
|
|
|
|
|
|
|
|
|
# List of assertions for this module evaluation, where each assertion also
|
|
|
|
|
# has a `show` attribute for how to show it if triggered
|
|
|
|
|
assertions = mapAttrsToList (name: value:
|
|
|
|
|
let id =
|
2020-09-01 22:17:26 +00:00
|
|
|
|
if lib.hasPrefix "_" name then ""
|
2020-09-01 21:32:57 +00:00
|
|
|
|
else "[${showOption prefix}${optionalString (prefix != []) "/"}${name}] ";
|
|
|
|
|
in value // {
|
|
|
|
|
show = "${id}${value.message}";
|
|
|
|
|
}
|
|
|
|
|
) config._module.assertions;
|
|
|
|
|
|
|
|
|
|
finalConfig = injectAssertions assertions (removeAttrs config [ "_module" ]);
|
|
|
|
|
|
2020-03-18 19:09:09 +00:00
|
|
|
|
checkUnmatched =
|
lib/modules: Implement freeform modules
For programs that have a lot of (Nix-representable) configuration options,
a simple way to represent this in a NixOS module is to declare an
option of a type like `attrsOf str`, representing a key-value mapping
which then gets generated into a config file. However with such a type,
there's no way to add type checking for only some key values.
On the other end of the spectrum, one can declare a single separate
option for every key value that the program supports, ending up with a module
with potentially 100s of options. This has the benefit that every value
gets type checked, catching mistakes at evaluation time already. However
the disadvantage is that the module becomes big, becomes coupled to the
program version and takes a lot of effort to write and maintain.
Previously there was a middle ground between these two
extremes: Declare an option of a type like `attrsOf str`, but declare
additional separate options for the values you wish to have type
checked, and assign their values to the `attrsOf str` option. While this
works decently, it has the problem of duplicated options, since now both
the additional options and the `attrsOf str` option can be used to set a
key value. This leads to confusion about what should happen if both are
set, which defaults should apply, and more.
Now with this change, a middle ground becomes available that solves above
problems: The module system now supports setting a freeform type, which
gets used for all definitions that don't have an associated option. This
means that you can now declare all options you wish to have type
checked, while for the rest a freeform type like `attrsOf str` can be
used.
2020-03-19 23:17:41 +00:00
|
|
|
|
if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [] then
|
2020-08-14 17:26:18 +00:00
|
|
|
|
let
|
|
|
|
|
firstDef = head merged.unmatchedDefns;
|
2020-09-16 18:05:53 +00:00
|
|
|
|
baseMsg = "The option `${showOption (prefix ++ firstDef.prefix)}' does not exist. Definition values:${showDefs [ firstDef ]}";
|
2020-08-14 17:26:18 +00:00
|
|
|
|
in
|
|
|
|
|
if attrNames options == [ "_module" ]
|
|
|
|
|
then throw ''
|
|
|
|
|
${baseMsg}
|
|
|
|
|
|
|
|
|
|
However there are no options defined in `${showOption prefix}'. Are you sure you've
|
|
|
|
|
declared your options properly? This can happen if you e.g. declared your options in `types.submodule'
|
|
|
|
|
under `config' rather than `options'.
|
|
|
|
|
''
|
|
|
|
|
else throw baseMsg
|
2020-03-18 19:09:09 +00:00
|
|
|
|
else null;
|
|
|
|
|
|
|
|
|
|
result = builtins.seq checkUnmatched {
|
2020-03-16 20:05:52 +00:00
|
|
|
|
inherit options;
|
2020-09-01 21:32:57 +00:00
|
|
|
|
config = finalConfig;
|
2020-03-16 20:05:52 +00:00
|
|
|
|
inherit (config) _module;
|
|
|
|
|
};
|
2013-10-27 23:56:22 +00:00
|
|
|
|
in result;
|
|
|
|
|
|
2020-01-02 21:09:31 +00:00
|
|
|
|
# collectModules :: (modulesPath: String) -> (modules: [ Module ]) -> (args: Attrs) -> [ Module ]
|
|
|
|
|
#
|
|
|
|
|
# Collects all modules recursively through `import` statements, filtering out
|
|
|
|
|
# all modules in disabledModules.
|
|
|
|
|
collectModules = let
|
2017-02-14 22:18:44 +00:00
|
|
|
|
|
2020-01-02 21:09:31 +00:00
|
|
|
|
# Like unifyModuleSyntax, but also imports paths and calls functions if necessary
|
|
|
|
|
loadModule = args: fallbackFile: fallbackKey: m:
|
|
|
|
|
if isFunction m || isAttrs m then
|
|
|
|
|
unifyModuleSyntax fallbackFile fallbackKey (applyIfFunction fallbackKey m args)
|
|
|
|
|
else unifyModuleSyntax (toString m) (toString m) (applyIfFunction (toString m) (import m) args);
|
2017-02-14 22:18:44 +00:00
|
|
|
|
|
2020-01-02 21:09:31 +00:00
|
|
|
|
/*
|
|
|
|
|
Collects all modules recursively into the form
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
disabled = [ <list of disabled modules> ];
|
|
|
|
|
# All modules of the main module list
|
|
|
|
|
modules = [
|
|
|
|
|
{
|
|
|
|
|
key = <key1>;
|
|
|
|
|
module = <module for key1>;
|
|
|
|
|
# All modules imported by the module for key1
|
|
|
|
|
modules = [
|
|
|
|
|
{
|
|
|
|
|
key = <key1-1>;
|
|
|
|
|
module = <module for key1-1>;
|
|
|
|
|
# All modules imported by the module for key1-1
|
|
|
|
|
modules = [ ... ];
|
|
|
|
|
}
|
|
|
|
|
...
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
...
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
collectStructuredModules =
|
|
|
|
|
let
|
|
|
|
|
collectResults = modules: {
|
|
|
|
|
disabled = concatLists (catAttrs "disabled" modules);
|
|
|
|
|
inherit modules;
|
|
|
|
|
};
|
|
|
|
|
in parentFile: parentKey: initialModules: args: collectResults (imap1 (n: x:
|
|
|
|
|
let
|
|
|
|
|
module = loadModule args parentFile "${parentKey}:anon-${toString n}" x;
|
|
|
|
|
collectedImports = collectStructuredModules module._file module.key module.imports args;
|
|
|
|
|
in {
|
|
|
|
|
key = module.key;
|
|
|
|
|
module = module;
|
|
|
|
|
modules = collectedImports.modules;
|
|
|
|
|
disabled = module.disabledModules ++ collectedImports.disabled;
|
|
|
|
|
}) initialModules);
|
|
|
|
|
|
|
|
|
|
# filterModules :: String -> { disabled, modules } -> [ Module ]
|
|
|
|
|
#
|
|
|
|
|
# Filters a structure as emitted by collectStructuredModules by removing all disabled
|
|
|
|
|
# modules recursively. It returns the final list of unique-by-key modules
|
|
|
|
|
filterModules = modulesPath: { disabled, modules }:
|
|
|
|
|
let
|
|
|
|
|
moduleKey = m: if isString m then toString modulesPath + "/" + m else toString m;
|
2020-01-10 03:00:33 +00:00
|
|
|
|
disabledKeys = map moduleKey disabled;
|
|
|
|
|
keyFilter = filter (attrs: ! elem attrs.key disabledKeys);
|
2020-01-02 21:09:31 +00:00
|
|
|
|
in map (attrs: attrs.module) (builtins.genericClosure {
|
|
|
|
|
startSet = keyFilter modules;
|
|
|
|
|
operator = attrs: keyFilter attrs.modules;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
in modulesPath: initialModules: args:
|
|
|
|
|
filterModules modulesPath (collectStructuredModules unknownModule "" initialModules args);
|
2009-10-09 18:11:24 +00:00
|
|
|
|
|
2013-10-27 23:56:22 +00:00
|
|
|
|
/* Massage a module into canonical form, that is, a set consisting
|
|
|
|
|
of ‘options’, ‘config’ and ‘imports’ attributes. */
|
2013-10-28 03:46:36 +00:00
|
|
|
|
unifyModuleSyntax = file: key: m:
|
2020-08-10 15:25:46 +00:00
|
|
|
|
let
|
|
|
|
|
addMeta = config: if m ? meta
|
|
|
|
|
then mkMerge [ config { meta = m.meta; } ]
|
|
|
|
|
else config;
|
|
|
|
|
addFreeformType = config: if m ? freeformType
|
|
|
|
|
then mkMerge [ config { _module.freeformType = m.freeformType; } ]
|
|
|
|
|
else config;
|
2016-05-09 05:53:27 +00:00
|
|
|
|
in
|
2013-10-28 16:40:36 +00:00
|
|
|
|
if m ? config || m ? options then
|
2020-08-10 15:25:46 +00:00
|
|
|
|
let badAttrs = removeAttrs m ["_file" "key" "disabledModules" "imports" "options" "config" "meta" "freeformType"]; in
|
2013-10-27 23:56:22 +00:00
|
|
|
|
if badAttrs != {} then
|
2019-12-30 15:08:03 +00:00
|
|
|
|
throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by introducing a top-level `config' or `options' attribute. Add configuration attributes immediately on the top level instead, or move all of them (namely: ${toString (attrNames badAttrs)}) into the explicit `config' attribute."
|
2013-10-27 23:56:22 +00:00
|
|
|
|
else
|
2020-09-29 20:34:44 +00:00
|
|
|
|
{ _file = toString m._file or file;
|
2013-10-29 13:15:33 +00:00
|
|
|
|
key = toString m.key or key;
|
2017-02-14 22:18:44 +00:00
|
|
|
|
disabledModules = m.disabledModules or [];
|
2013-10-27 23:56:22 +00:00
|
|
|
|
imports = m.imports or [];
|
|
|
|
|
options = m.options or {};
|
2020-08-10 15:25:46 +00:00
|
|
|
|
config = addFreeformType (addMeta (m.config or {}));
|
2013-10-27 23:56:22 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
2020-09-29 20:34:44 +00:00
|
|
|
|
{ _file = toString m._file or file;
|
2013-10-29 13:15:33 +00:00
|
|
|
|
key = toString m.key or key;
|
2017-02-14 22:18:44 +00:00
|
|
|
|
disabledModules = m.disabledModules or [];
|
2013-10-28 16:40:36 +00:00
|
|
|
|
imports = m.require or [] ++ m.imports or [];
|
2013-10-27 23:56:22 +00:00
|
|
|
|
options = {};
|
2020-08-10 15:25:46 +00:00
|
|
|
|
config = addFreeformType (addMeta (removeAttrs m ["_file" "key" "disabledModules" "require" "imports" "freeformType"]));
|
2013-10-27 23:56:22 +00:00
|
|
|
|
};
|
2009-10-09 18:11:24 +00:00
|
|
|
|
|
2015-05-13 20:44:04 +00:00
|
|
|
|
applyIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then
|
2014-05-05 19:18:53 +00:00
|
|
|
|
let
|
2015-03-11 22:30:30 +00:00
|
|
|
|
# Module arguments are resolved in a strict manner when attribute set
|
|
|
|
|
# deconstruction is used. As the arguments are now defined with the
|
2015-03-12 22:19:23 +00:00
|
|
|
|
# config._module.args option, the strictness used on the attribute
|
2015-03-11 22:30:30 +00:00
|
|
|
|
# set argument would cause an infinite loop, if the result of the
|
|
|
|
|
# option is given as argument.
|
|
|
|
|
#
|
|
|
|
|
# To work-around the strictness issue on the deconstruction of the
|
|
|
|
|
# attributes set argument, we create a new attribute set which is
|
|
|
|
|
# constructed to satisfy the expected set of attributes. Thus calling
|
|
|
|
|
# a module will resolve strictly the attributes used as argument but
|
|
|
|
|
# not their values. The values are forwarding the result of the
|
|
|
|
|
# evaluation of the option.
|
2018-01-31 19:02:19 +00:00
|
|
|
|
requiredArgs = builtins.attrNames (lib.functionArgs f);
|
2015-05-13 20:44:04 +00:00
|
|
|
|
context = name: ''while evaluating the module argument `${name}' in "${key}":'';
|
2014-05-05 19:18:53 +00:00
|
|
|
|
extraArgs = builtins.listToAttrs (map (name: {
|
|
|
|
|
inherit name;
|
2018-04-02 19:00:47 +00:00
|
|
|
|
value = builtins.addErrorContext (context name)
|
2015-05-13 20:44:04 +00:00
|
|
|
|
(args.${name} or config._module.args.${name});
|
2014-05-05 19:18:53 +00:00
|
|
|
|
}) requiredArgs);
|
2015-05-13 20:44:04 +00:00
|
|
|
|
|
|
|
|
|
# Note: we append in the opposite order such that we can add an error
|
|
|
|
|
# context on the explicited arguments of "args" too. This update
|
|
|
|
|
# operator is used to make the "args@{ ... }: with args.lib;" notation
|
|
|
|
|
# works.
|
|
|
|
|
in f (args // extraArgs)
|
2014-05-05 19:18:53 +00:00
|
|
|
|
else
|
|
|
|
|
f;
|
2013-10-27 23:56:22 +00:00
|
|
|
|
|
|
|
|
|
/* Merge a list of modules. This will recurse over the option
|
|
|
|
|
declarations in all modules, combining them into a single set.
|
|
|
|
|
At the same time, for each option declaration, it will merge the
|
|
|
|
|
corresponding option definitions in all machines, returning them
|
2020-03-18 19:09:09 +00:00
|
|
|
|
in the ‘value’ attribute of each option.
|
|
|
|
|
|
|
|
|
|
This returns a set like
|
|
|
|
|
{
|
|
|
|
|
# A recursive set of options along with their final values
|
|
|
|
|
matchedOptions = {
|
|
|
|
|
foo = { _type = "option"; value = "option value of foo"; ... };
|
|
|
|
|
bar.baz = { _type = "option"; value = "option value of bar.baz"; ... };
|
|
|
|
|
...
|
|
|
|
|
};
|
|
|
|
|
# A list of definitions that weren't matched by any option
|
|
|
|
|
unmatchedDefns = [
|
|
|
|
|
{ file = "file.nix"; prefix = [ "qux" ]; value = "qux"; }
|
|
|
|
|
...
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
*/
|
2016-03-01 19:47:08 +00:00
|
|
|
|
mergeModules = prefix: modules:
|
|
|
|
|
mergeModules' prefix modules
|
2019-12-04 23:00:47 +00:00
|
|
|
|
(concatMap (m: map (config: { file = m._file; inherit config; }) (pushDownProperties m.config)) modules);
|
2013-10-27 23:56:22 +00:00
|
|
|
|
|
2016-03-01 19:47:08 +00:00
|
|
|
|
mergeModules' = prefix: options: configs:
|
2018-08-13 23:01:08 +00:00
|
|
|
|
let
|
|
|
|
|
/* byName is like foldAttrs, but will look for attributes to merge in the
|
|
|
|
|
specified attribute name.
|
|
|
|
|
|
|
|
|
|
byName "foo" (module: value: ["module.hidden=${module.hidden},value=${value}"])
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
hidden="baz";
|
|
|
|
|
foo={qux="bar"; gla="flop";};
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
hidden="fli";
|
|
|
|
|
foo={qux="gne"; gli="flip";};
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
===>
|
|
|
|
|
{
|
|
|
|
|
gla = [ "module.hidden=baz,value=flop" ];
|
|
|
|
|
gli = [ "module.hidden=fli,value=flip" ];
|
|
|
|
|
qux = [ "module.hidden=baz,value=bar" "module.hidden=fli,value=gne" ];
|
|
|
|
|
}
|
|
|
|
|
*/
|
2019-01-26 19:43:11 +00:00
|
|
|
|
byName = attr: f: modules:
|
|
|
|
|
foldl' (acc: module:
|
|
|
|
|
acc // (mapAttrs (n: v:
|
|
|
|
|
(acc.${n} or []) ++ f module v
|
|
|
|
|
) module.${attr}
|
|
|
|
|
)
|
|
|
|
|
) {} modules;
|
2018-08-13 23:01:08 +00:00
|
|
|
|
# an attrset 'name' => list of submodules that declare ‘name’.
|
2019-01-26 19:43:11 +00:00
|
|
|
|
declsByName = byName "options" (module: option:
|
2019-12-04 23:00:47 +00:00
|
|
|
|
[{ inherit (module) _file; options = option; }]
|
2019-01-26 19:43:11 +00:00
|
|
|
|
) options;
|
2018-08-13 23:01:08 +00:00
|
|
|
|
# an attrset 'name' => list of submodules that define ‘name’.
|
|
|
|
|
defnsByName = byName "config" (module: value:
|
2019-01-26 19:43:11 +00:00
|
|
|
|
map (config: { inherit (module) file; inherit config; }) (pushDownProperties value)
|
2018-08-13 23:01:08 +00:00
|
|
|
|
) configs;
|
|
|
|
|
# extract the definitions for each loc
|
2019-01-26 19:43:11 +00:00
|
|
|
|
defnsByName' = byName "config" (module: value:
|
|
|
|
|
[{ inherit (module) file; inherit value; }]
|
|
|
|
|
) configs;
|
2020-03-18 19:09:09 +00:00
|
|
|
|
|
|
|
|
|
resultsByName = flip mapAttrs declsByName (name: decls:
|
|
|
|
|
# We're descending into attribute ‘name’.
|
2013-10-28 03:46:36 +00:00
|
|
|
|
let
|
2013-10-28 13:25:58 +00:00
|
|
|
|
loc = prefix ++ [name];
|
2018-08-13 23:01:08 +00:00
|
|
|
|
defns = defnsByName.${name} or [];
|
|
|
|
|
defns' = defnsByName'.${name} or [];
|
2013-10-28 03:46:36 +00:00
|
|
|
|
nrOptions = count (m: isOption m.options) decls;
|
|
|
|
|
in
|
|
|
|
|
if nrOptions == length decls then
|
2016-03-01 19:47:08 +00:00
|
|
|
|
let opt = fixupOptionType loc (mergeOptionDecls loc decls);
|
2020-03-18 19:09:09 +00:00
|
|
|
|
in {
|
|
|
|
|
matchedOptions = evalOptionValue loc opt defns';
|
|
|
|
|
unmatchedDefns = [];
|
|
|
|
|
}
|
2013-10-28 03:46:36 +00:00
|
|
|
|
else if nrOptions != 0 then
|
|
|
|
|
let
|
|
|
|
|
firstOption = findFirst (m: isOption m.options) "" decls;
|
|
|
|
|
firstNonOption = findFirst (m: !isOption m.options) "" decls;
|
|
|
|
|
in
|
2019-12-04 23:00:47 +00:00
|
|
|
|
throw "The option `${showOption loc}' in `${firstOption._file}' is a prefix of options in `${firstNonOption._file}'."
|
2013-10-28 03:46:36 +00:00
|
|
|
|
else
|
2020-03-18 19:09:09 +00:00
|
|
|
|
mergeModules' loc decls defns);
|
|
|
|
|
|
|
|
|
|
matchedOptions = mapAttrs (n: v: v.matchedOptions) resultsByName;
|
|
|
|
|
|
|
|
|
|
# an attrset 'name' => list of unmatched definitions for 'name'
|
|
|
|
|
unmatchedDefnsByName =
|
|
|
|
|
# Propagate all unmatched definitions from nested option sets
|
|
|
|
|
mapAttrs (n: v: v.unmatchedDefns) resultsByName
|
|
|
|
|
# Plus the definitions for the current prefix that don't have a matching option
|
|
|
|
|
// removeAttrs defnsByName' (attrNames matchedOptions);
|
|
|
|
|
in {
|
|
|
|
|
inherit matchedOptions;
|
|
|
|
|
|
|
|
|
|
# Transforms unmatchedDefnsByName into a list of definitions
|
|
|
|
|
unmatchedDefns = concatLists (mapAttrsToList (name: defs:
|
|
|
|
|
map (def: def // {
|
|
|
|
|
# Set this so we know when the definition first left unmatched territory
|
|
|
|
|
prefix = [name] ++ (def.prefix or []);
|
|
|
|
|
}) defs
|
|
|
|
|
) unmatchedDefnsByName);
|
|
|
|
|
};
|
2013-10-27 23:56:22 +00:00
|
|
|
|
|
|
|
|
|
/* Merge multiple option declarations into a single declaration. In
|
|
|
|
|
general, there should be only one declaration of each option.
|
|
|
|
|
The exception is the ‘options’ attribute, which specifies
|
|
|
|
|
sub-options. These can be specified multiple times to allow one
|
|
|
|
|
module to add sub-options to an option declared somewhere else
|
2014-08-29 12:38:19 +00:00
|
|
|
|
(e.g. multiple modules define sub-options for ‘fileSystems’).
|
|
|
|
|
|
|
|
|
|
'loc' is the list of attribute names where the option is located.
|
|
|
|
|
|
|
|
|
|
'opts' is a list of modules. Each module has an options attribute which
|
|
|
|
|
correspond to the definition of 'loc' in 'opt.file'. */
|
2019-12-05 02:29:51 +00:00
|
|
|
|
mergeOptionDecls =
|
|
|
|
|
let
|
|
|
|
|
packSubmodule = file: m:
|
|
|
|
|
{ _file = file; imports = [ m ]; };
|
|
|
|
|
coerceOption = file: opt:
|
|
|
|
|
if isFunction opt then packSubmodule file opt
|
|
|
|
|
else packSubmodule file { options = opt; };
|
|
|
|
|
in loc: opts:
|
2015-07-23 15:19:21 +00:00
|
|
|
|
foldl' (res: opt:
|
2016-09-07 01:03:32 +00:00
|
|
|
|
let t = res.type;
|
|
|
|
|
t' = opt.options.type;
|
|
|
|
|
mergedType = t.typeMerge t'.functor;
|
|
|
|
|
typesMergeable = mergedType != null;
|
|
|
|
|
typeSet = if (bothHave "type") && typesMergeable
|
|
|
|
|
then { type = mergedType; }
|
|
|
|
|
else {};
|
|
|
|
|
bothHave = k: opt.options ? ${k} && res ? ${k};
|
|
|
|
|
in
|
|
|
|
|
if bothHave "default" ||
|
|
|
|
|
bothHave "example" ||
|
|
|
|
|
bothHave "description" ||
|
|
|
|
|
bothHave "apply" ||
|
|
|
|
|
(bothHave "type" && (! typesMergeable))
|
2013-10-27 23:56:22 +00:00
|
|
|
|
then
|
2019-12-04 23:00:47 +00:00
|
|
|
|
throw "The option `${showOption loc}' in `${opt._file}' is already declared in ${showFiles res.declarations}."
|
2013-10-27 23:56:22 +00:00
|
|
|
|
else
|
2014-08-29 12:38:19 +00:00
|
|
|
|
let
|
|
|
|
|
/* Add the modules of the current option to the list of modules
|
|
|
|
|
already collected. The options attribute except either a list of
|
|
|
|
|
submodules or a submodule. For each submodule, we add the file of the
|
|
|
|
|
current option declaration as the file use for the submodule. If the
|
|
|
|
|
submodule defines any filename, then we ignore the enclosing option file. */
|
|
|
|
|
options' = toList opt.options.options;
|
2019-12-05 02:29:51 +00:00
|
|
|
|
|
2014-08-29 14:42:44 +00:00
|
|
|
|
getSubModules = opt.options.type.getSubModules or null;
|
2014-08-29 12:38:19 +00:00
|
|
|
|
submodules =
|
2019-12-04 23:00:47 +00:00
|
|
|
|
if getSubModules != null then map (packSubmodule opt._file) getSubModules ++ res.options
|
|
|
|
|
else if opt.options ? options then map (coerceOption opt._file) options' ++ res.options
|
2014-08-29 12:38:19 +00:00
|
|
|
|
else res.options;
|
|
|
|
|
in opt.options // res //
|
2019-12-04 23:00:47 +00:00
|
|
|
|
{ declarations = res.declarations ++ [opt._file];
|
2014-08-29 12:38:19 +00:00
|
|
|
|
options = submodules;
|
2016-09-07 01:03:32 +00:00
|
|
|
|
} // typeSet
|
2013-10-28 13:25:58 +00:00
|
|
|
|
) { inherit loc; declarations = []; options = []; } opts;
|
2013-10-27 23:56:22 +00:00
|
|
|
|
|
|
|
|
|
/* Merge all the definitions of an option to produce the final
|
|
|
|
|
config value. */
|
2016-03-01 19:47:08 +00:00
|
|
|
|
evalOptionValue = loc: opt: defs:
|
2009-09-14 13:19:00 +00:00
|
|
|
|
let
|
2014-05-01 21:29:30 +00:00
|
|
|
|
# Add in the default value for this option, if any.
|
2015-07-30 11:36:57 +00:00
|
|
|
|
defs' =
|
|
|
|
|
(optional (opt ? default)
|
|
|
|
|
{ file = head opt.declarations; value = mkOptionDefault opt.default; }) ++ defs;
|
2015-03-12 22:01:47 +00:00
|
|
|
|
|
2015-07-28 12:41:36 +00:00
|
|
|
|
# Handle properties, check types, and merge everything together.
|
2015-07-30 11:36:57 +00:00
|
|
|
|
res =
|
|
|
|
|
if opt.readOnly or false && length defs' > 1 then
|
2020-09-21 16:10:06 +00:00
|
|
|
|
let
|
|
|
|
|
# For a better error message, evaluate all readOnly definitions as
|
|
|
|
|
# if they were the only definition.
|
|
|
|
|
separateDefs = map (def: def // {
|
|
|
|
|
value = (mergeDefinitions loc opt.type [ def ]).mergedValue;
|
|
|
|
|
}) defs';
|
|
|
|
|
in throw "The option `${showOption loc}' is read-only, but it's set multiple times. Definition values:${showDefs separateDefs}"
|
2015-07-30 11:36:57 +00:00
|
|
|
|
else
|
2016-03-01 19:47:08 +00:00
|
|
|
|
mergeDefinitions loc opt.type defs';
|
2015-07-28 12:41:36 +00:00
|
|
|
|
|
2019-08-09 22:04:07 +00:00
|
|
|
|
# Apply the 'apply' function to the merged value. This allows options to
|
|
|
|
|
# yield a value computed from the definitions
|
2019-10-12 17:32:59 +00:00
|
|
|
|
value = if opt ? apply then opt.apply res.mergedValue else res.mergedValue;
|
2015-07-28 12:41:36 +00:00
|
|
|
|
|
2020-09-04 11:39:27 +00:00
|
|
|
|
warnDeprecation =
|
|
|
|
|
if opt.type.deprecationMessage == null then id
|
|
|
|
|
else warn "The type `types.${opt.type.name}' of option `${showOption loc}' defined in ${showFiles opt.declarations} is deprecated. ${opt.type.deprecationMessage}";
|
|
|
|
|
|
|
|
|
|
in warnDeprecation opt //
|
2018-04-02 19:00:47 +00:00
|
|
|
|
{ value = builtins.addErrorContext "while evaluating the option `${showOption loc}':" value;
|
2018-02-22 21:31:38 +00:00
|
|
|
|
inherit (res.defsFinal') highestPrio;
|
2015-08-05 12:29:38 +00:00
|
|
|
|
definitions = map (def: def.value) res.defsFinal;
|
2015-07-28 12:41:36 +00:00
|
|
|
|
files = map (def: def.file) res.defsFinal;
|
|
|
|
|
inherit (res) isDefined;
|
2020-09-02 14:10:17 +00:00
|
|
|
|
# This allows options to be correctly displayed using `${options.path.to.it}`
|
|
|
|
|
__toString = _: showOption loc;
|
2011-04-27 18:41:37 +00:00
|
|
|
|
};
|
|
|
|
|
|
2015-07-28 12:29:29 +00:00
|
|
|
|
# Merge definitions of a value of a given type.
|
2016-03-01 19:47:08 +00:00
|
|
|
|
mergeDefinitions = loc: type: defs: rec {
|
2018-02-22 21:31:38 +00:00
|
|
|
|
defsFinal' =
|
2015-07-28 12:29:29 +00:00
|
|
|
|
let
|
2015-07-28 12:41:36 +00:00
|
|
|
|
# Process mkMerge and mkIf properties.
|
|
|
|
|
defs' = concatMap (m:
|
2020-02-23 23:15:36 +00:00
|
|
|
|
map (value: { inherit (m) file; inherit value; }) (builtins.addErrorContext "while evaluating definitions from `${m.file}':" (dischargeProperties m.value))
|
2015-07-28 12:29:29 +00:00
|
|
|
|
) defs;
|
|
|
|
|
|
2015-07-28 12:41:36 +00:00
|
|
|
|
# Process mkOverride properties.
|
2018-02-22 21:31:38 +00:00
|
|
|
|
defs'' = filterOverrides' defs';
|
2015-07-28 12:29:29 +00:00
|
|
|
|
|
2015-07-28 12:41:36 +00:00
|
|
|
|
# Sort mkOrder properties.
|
|
|
|
|
defs''' =
|
2015-07-28 12:29:29 +00:00
|
|
|
|
# Avoid sorting if we don't have to.
|
2018-02-22 21:31:38 +00:00
|
|
|
|
if any (def: def.value._type or "" == "order") defs''.values
|
|
|
|
|
then sortProperties defs''.values
|
|
|
|
|
else defs''.values;
|
|
|
|
|
in {
|
|
|
|
|
values = defs''';
|
|
|
|
|
inherit (defs'') highestPrio;
|
|
|
|
|
};
|
|
|
|
|
defsFinal = defsFinal'.values;
|
2015-07-28 12:29:29 +00:00
|
|
|
|
|
2019-10-12 17:32:59 +00:00
|
|
|
|
# Type-check the remaining definitions, and merge them. Or throw if no definitions.
|
|
|
|
|
mergedValue =
|
|
|
|
|
if isDefined then
|
2020-03-16 20:10:05 +00:00
|
|
|
|
if all (def: type.check def.value) defsFinal then type.merge loc defsFinal
|
2020-09-16 18:05:53 +00:00
|
|
|
|
else let allInvalid = filter (def: ! type.check def.value) defsFinal;
|
|
|
|
|
in throw "A definition for option `${showOption loc}' is not of type `${type.description}'. Definition values:${showDefs allInvalid}"
|
2019-10-12 17:32:59 +00:00
|
|
|
|
else
|
|
|
|
|
# (nixos-option detects this specific error message and gives it special
|
|
|
|
|
# handling. If changed here, please change it there too.)
|
|
|
|
|
throw "The option `${showOption loc}' is used but not defined.";
|
2015-07-28 12:29:29 +00:00
|
|
|
|
|
|
|
|
|
isDefined = defsFinal != [];
|
|
|
|
|
|
|
|
|
|
optionalValue =
|
|
|
|
|
if isDefined then { value = mergedValue; }
|
|
|
|
|
else {};
|
|
|
|
|
};
|
2014-05-01 21:29:30 +00:00
|
|
|
|
|
2013-10-27 23:56:22 +00:00
|
|
|
|
/* Given a config set, expand mkMerge properties, and push down the
|
2014-03-30 18:35:25 +00:00
|
|
|
|
other properties into the children. The result is a list of
|
2013-10-27 23:56:22 +00:00
|
|
|
|
config sets that do not have properties at top-level. For
|
|
|
|
|
example,
|
2009-07-13 16:18:52 +00:00
|
|
|
|
|
2013-10-27 23:56:22 +00:00
|
|
|
|
mkMerge [ { boot = set1; } (mkIf cond { boot = set2; services = set3; }) ]
|
2009-09-15 13:36:30 +00:00
|
|
|
|
|
2013-10-27 23:56:22 +00:00
|
|
|
|
is transformed into
|
2009-09-15 13:36:30 +00:00
|
|
|
|
|
2014-02-17 20:29:08 +00:00
|
|
|
|
[ { boot = set1; } { boot = mkIf cond set2; services = mkIf cond set3; } ].
|
2012-11-30 11:56:18 +00:00
|
|
|
|
|
2013-10-27 23:56:22 +00:00
|
|
|
|
This transform is the critical step that allows mkIf conditions
|
|
|
|
|
to refer to the full configuration without creating an infinite
|
|
|
|
|
recursion.
|
|
|
|
|
*/
|
|
|
|
|
pushDownProperties = cfg:
|
|
|
|
|
if cfg._type or "" == "merge" then
|
|
|
|
|
concatMap pushDownProperties cfg.contents
|
|
|
|
|
else if cfg._type or "" == "if" then
|
|
|
|
|
map (mapAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content)
|
2013-10-28 16:46:45 +00:00
|
|
|
|
else if cfg._type or "" == "override" then
|
|
|
|
|
map (mapAttrs (n: v: mkOverride cfg.priority v)) (pushDownProperties cfg.content)
|
2014-03-30 18:35:25 +00:00
|
|
|
|
else # FIXME: handle mkOrder?
|
2013-10-27 23:56:22 +00:00
|
|
|
|
[ cfg ];
|
|
|
|
|
|
|
|
|
|
/* Given a config value, expand mkMerge properties, and discharge
|
|
|
|
|
any mkIf conditions. That is, this is the place where mkIf
|
|
|
|
|
conditions are actually evaluated. The result is a list of
|
|
|
|
|
config values. For example, ‘mkIf false x’ yields ‘[]’,
|
|
|
|
|
‘mkIf true x’ yields ‘[x]’, and
|
|
|
|
|
|
|
|
|
|
mkMerge [ 1 (mkIf true 2) (mkIf true (mkIf false 3)) ]
|
|
|
|
|
|
|
|
|
|
yields ‘[ 1 2 ]’.
|
|
|
|
|
*/
|
|
|
|
|
dischargeProperties = def:
|
|
|
|
|
if def._type or "" == "merge" then
|
|
|
|
|
concatMap dischargeProperties def.contents
|
|
|
|
|
else if def._type or "" == "if" then
|
2016-11-21 13:51:57 +00:00
|
|
|
|
if isBool def.condition then
|
|
|
|
|
if def.condition then
|
|
|
|
|
dischargeProperties def.content
|
|
|
|
|
else
|
|
|
|
|
[ ]
|
2013-10-27 23:56:22 +00:00
|
|
|
|
else
|
2016-11-21 13:51:57 +00:00
|
|
|
|
throw "‘mkIf’ called with a non-Boolean condition"
|
2009-10-09 23:03:24 +00:00
|
|
|
|
else
|
2013-10-27 23:56:22 +00:00
|
|
|
|
[ def ];
|
2009-09-15 13:36:30 +00:00
|
|
|
|
|
2013-10-28 06:52:24 +00:00
|
|
|
|
/* Given a list of config values, process the mkOverride properties,
|
|
|
|
|
that is, return the values that have the highest (that is,
|
|
|
|
|
numerically lowest) priority, and strip the mkOverride
|
2013-10-27 23:56:22 +00:00
|
|
|
|
properties. For example,
|
2012-11-30 11:56:18 +00:00
|
|
|
|
|
2013-10-28 06:52:24 +00:00
|
|
|
|
[ { file = "/1"; value = mkOverride 10 "a"; }
|
|
|
|
|
{ file = "/2"; value = mkOverride 20 "b"; }
|
|
|
|
|
{ file = "/3"; value = "z"; }
|
|
|
|
|
{ file = "/4"; value = mkOverride 10 "d"; }
|
2013-10-28 03:46:36 +00:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
yields
|
|
|
|
|
|
2013-10-28 06:52:24 +00:00
|
|
|
|
[ { file = "/1"; value = "a"; }
|
|
|
|
|
{ file = "/4"; value = "d"; }
|
2013-10-28 03:46:36 +00:00
|
|
|
|
]
|
2009-09-15 13:36:30 +00:00
|
|
|
|
|
2013-10-28 03:46:36 +00:00
|
|
|
|
Note that "z" has the default priority 100.
|
2013-10-27 23:56:22 +00:00
|
|
|
|
*/
|
2018-02-22 21:31:38 +00:00
|
|
|
|
filterOverrides = defs: (filterOverrides' defs).values;
|
|
|
|
|
|
|
|
|
|
filterOverrides' = defs:
|
2009-09-15 13:36:30 +00:00
|
|
|
|
let
|
2019-01-06 08:48:37 +00:00
|
|
|
|
getPrio = def: if def.value._type or "" == "override" then def.value.priority else defaultPriority;
|
2015-07-23 15:19:21 +00:00
|
|
|
|
highestPrio = foldl' (prio: def: min (getPrio def) prio) 9999 defs;
|
2013-10-28 04:23:10 +00:00
|
|
|
|
strip = def: if def.value._type or "" == "override" then def // { value = def.value.content; } else def;
|
2018-02-22 21:31:38 +00:00
|
|
|
|
in {
|
|
|
|
|
values = concatMap (def: if getPrio def == highestPrio then [(strip def)] else []) defs;
|
|
|
|
|
inherit highestPrio;
|
|
|
|
|
};
|
2013-10-27 23:56:22 +00:00
|
|
|
|
|
2014-03-30 18:35:25 +00:00
|
|
|
|
/* Sort a list of properties. The sort priority of a property is
|
2017-04-19 19:41:28 +00:00
|
|
|
|
1000 by default, but can be overridden by wrapping the property
|
2014-03-30 18:35:25 +00:00
|
|
|
|
using mkOrder. */
|
|
|
|
|
sortProperties = defs:
|
|
|
|
|
let
|
|
|
|
|
strip = def:
|
|
|
|
|
if def.value._type or "" == "order"
|
|
|
|
|
then def // { value = def.value.content; inherit (def.value) priority; }
|
|
|
|
|
else def;
|
|
|
|
|
defs' = map strip defs;
|
|
|
|
|
compare = a: b: (a.priority or 1000) < (b.priority or 1000);
|
|
|
|
|
in sort compare defs';
|
|
|
|
|
|
2013-10-27 23:56:22 +00:00
|
|
|
|
/* Hack for backward compatibility: convert options of type
|
2014-08-29 14:42:44 +00:00
|
|
|
|
optionSet to options of type submodule. FIXME: remove
|
|
|
|
|
eventually. */
|
2016-03-01 19:47:08 +00:00
|
|
|
|
fixupOptionType = loc: opt:
|
2019-03-07 19:28:09 +00:00
|
|
|
|
let
|
|
|
|
|
options = opt.options or
|
2020-10-20 11:47:24 +00:00
|
|
|
|
(throw "Option `${showOption loc}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}.");
|
2019-03-07 19:28:09 +00:00
|
|
|
|
f = tp:
|
|
|
|
|
let optionSetIn = type: (tp.name == type) && (tp.functor.wrapped.name == "optionSet");
|
|
|
|
|
in
|
|
|
|
|
if tp.name == "option set" || tp.name == "submodule" then
|
|
|
|
|
throw "The option ${showOption loc} uses submodules without a wrapping type, in ${showFiles opt.declarations}."
|
|
|
|
|
else if optionSetIn "attrsOf" then types.attrsOf (types.submodule options)
|
|
|
|
|
else if optionSetIn "listOf" then types.listOf (types.submodule options)
|
|
|
|
|
else if optionSetIn "nullOr" then types.nullOr (types.submodule options)
|
|
|
|
|
else tp;
|
|
|
|
|
in
|
|
|
|
|
if opt.type.getSubModules or null == null
|
|
|
|
|
then opt // { type = f (opt.type or types.unspecified); }
|
2014-08-29 14:42:44 +00:00
|
|
|
|
else opt // { type = opt.type.substSubModules opt.options; options = []; };
|
2013-10-27 23:56:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Properties. */
|
|
|
|
|
|
|
|
|
|
mkIf = condition: content:
|
|
|
|
|
{ _type = "if";
|
|
|
|
|
inherit condition content;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mkAssert = assertion: message: content:
|
|
|
|
|
mkIf
|
|
|
|
|
(if assertion then true else throw "\nFailed assertion: ${message}")
|
|
|
|
|
content;
|
|
|
|
|
|
|
|
|
|
mkMerge = contents:
|
|
|
|
|
{ _type = "merge";
|
|
|
|
|
inherit contents;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mkOverride = priority: content:
|
|
|
|
|
{ _type = "override";
|
|
|
|
|
inherit priority content;
|
|
|
|
|
};
|
|
|
|
|
|
2018-06-17 20:29:16 +00:00
|
|
|
|
mkOptionDefault = mkOverride 1500; # priority of option defaults
|
2013-10-29 12:04:52 +00:00
|
|
|
|
mkDefault = mkOverride 1000; # used in config sections of non-user modules to set a default
|
2013-10-27 23:56:22 +00:00
|
|
|
|
mkForce = mkOverride 50;
|
2013-10-29 12:04:52 +00:00
|
|
|
|
mkVMOverride = mkOverride 10; # used by ‘nixos-rebuild build-vm’
|
2013-10-27 23:56:22 +00:00
|
|
|
|
|
2014-05-05 10:29:38 +00:00
|
|
|
|
mkStrict = builtins.trace "`mkStrict' is obsolete; use `mkOverride 0' instead." (mkOverride 0);
|
|
|
|
|
|
2013-10-27 23:56:22 +00:00
|
|
|
|
mkFixStrictness = id; # obsolete, no-op
|
|
|
|
|
|
2014-03-30 18:35:25 +00:00
|
|
|
|
mkOrder = priority: content:
|
|
|
|
|
{ _type = "order";
|
|
|
|
|
inherit priority content;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mkBefore = mkOrder 500;
|
|
|
|
|
mkAfter = mkOrder 1500;
|
2009-11-07 01:59:50 +00:00
|
|
|
|
|
2019-01-06 08:48:37 +00:00
|
|
|
|
# The default priority for things that don't have a priority specified.
|
|
|
|
|
defaultPriority = 100;
|
2015-10-14 16:05:50 +00:00
|
|
|
|
|
2014-12-22 21:38:38 +00:00
|
|
|
|
# Convenient property used to transfer all definitions and their
|
|
|
|
|
# properties from one option to another. This property is useful for
|
|
|
|
|
# renaming options, and also for including properties from another module
|
|
|
|
|
# system, including sub-modules.
|
|
|
|
|
#
|
|
|
|
|
# { config, options, ... }:
|
|
|
|
|
#
|
|
|
|
|
# {
|
|
|
|
|
# # 'bar' might not always be defined in the current module-set.
|
|
|
|
|
# config.foo.enable = mkAliasDefinitions (options.bar.enable or {});
|
|
|
|
|
#
|
|
|
|
|
# # 'barbaz' has to be defined in the current module-set.
|
|
|
|
|
# config.foobar.paths = mkAliasDefinitions options.barbaz.paths;
|
|
|
|
|
# }
|
|
|
|
|
#
|
|
|
|
|
# Note, this is different than taking the value of the option and using it
|
|
|
|
|
# as a definition, as the new definition will not keep the mkOverride /
|
|
|
|
|
# mkDefault properties of the previous option.
|
|
|
|
|
#
|
|
|
|
|
mkAliasDefinitions = mkAliasAndWrapDefinitions id;
|
|
|
|
|
mkAliasAndWrapDefinitions = wrap: option:
|
2019-01-03 14:15:01 +00:00
|
|
|
|
mkAliasIfDef option (wrap (mkMerge option.definitions));
|
2014-12-22 21:38:38 +00:00
|
|
|
|
|
2019-01-03 14:15:01 +00:00
|
|
|
|
# Similar to mkAliasAndWrapDefinitions but copies over the priority from the
|
|
|
|
|
# option as well.
|
|
|
|
|
#
|
2019-01-06 08:48:37 +00:00
|
|
|
|
# If a priority is not set, it assumes a priority of defaultPriority.
|
2019-01-03 14:15:01 +00:00
|
|
|
|
mkAliasAndWrapDefsWithPriority = wrap: option:
|
|
|
|
|
let
|
2019-01-06 08:48:37 +00:00
|
|
|
|
prio = option.highestPrio or defaultPriority;
|
2019-01-03 14:15:01 +00:00
|
|
|
|
defsWithPrio = map (mkOverride prio) option.definitions;
|
|
|
|
|
in mkAliasIfDef option (wrap (mkMerge defsWithPrio));
|
|
|
|
|
|
|
|
|
|
mkAliasIfDef = option:
|
|
|
|
|
mkIf (isOption option && option.isDefined);
|
2013-10-29 13:23:10 +00:00
|
|
|
|
|
|
|
|
|
/* Compatibility. */
|
2016-03-01 19:47:08 +00:00
|
|
|
|
fixMergeModules = modules: args: evalModules { inherit modules args; check = false; };
|
2013-10-29 13:23:10 +00:00
|
|
|
|
|
2015-10-14 16:05:50 +00:00
|
|
|
|
|
|
|
|
|
/* Return a module that causes a warning to be shown if the
|
|
|
|
|
specified option is defined. For example,
|
|
|
|
|
|
2016-03-26 23:01:43 +00:00
|
|
|
|
mkRemovedOptionModule [ "boot" "loader" "grub" "bootDevice" ] "<replacement instructions>"
|
2015-10-14 16:05:50 +00:00
|
|
|
|
|
2020-10-22 18:28:22 +00:00
|
|
|
|
causes a assertion if the user defines boot.loader.grub.bootDevice.
|
2016-03-26 23:01:43 +00:00
|
|
|
|
|
|
|
|
|
replacementInstructions is a string that provides instructions on
|
|
|
|
|
how to achieve the same functionality without the removed option,
|
|
|
|
|
or alternatively a reasoning why the functionality is not needed.
|
|
|
|
|
replacementInstructions SHOULD be provided!
|
2015-10-14 16:05:50 +00:00
|
|
|
|
*/
|
2016-03-26 23:01:43 +00:00
|
|
|
|
mkRemovedOptionModule = optionName: replacementInstructions:
|
2015-10-14 16:05:50 +00:00
|
|
|
|
{ options, ... }:
|
|
|
|
|
{ options = setAttrByPath optionName (mkOption {
|
|
|
|
|
visible = false;
|
2019-09-28 01:52:01 +00:00
|
|
|
|
apply = x: throw "The option `${showOption optionName}' can no longer be used since it's been removed. ${replacementInstructions}";
|
2015-10-14 16:05:50 +00:00
|
|
|
|
});
|
2020-09-02 13:43:16 +00:00
|
|
|
|
config._module.assertions =
|
|
|
|
|
let opt = getAttrFromPath optionName options; in {
|
|
|
|
|
${showOption optionName} = {
|
|
|
|
|
enable = mkDefault opt.isDefined;
|
2019-09-25 15:13:29 +00:00
|
|
|
|
message = ''
|
2016-03-26 23:01:43 +00:00
|
|
|
|
The option definition `${showOption optionName}' in ${showFiles opt.files} no longer has any effect; please remove it.
|
2019-09-25 15:13:29 +00:00
|
|
|
|
${replacementInstructions}
|
|
|
|
|
'';
|
2020-09-02 13:43:16 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
2015-10-14 16:05:50 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* Return a module that causes a warning to be shown if the
|
|
|
|
|
specified "from" option is defined; the defined value is however
|
|
|
|
|
forwarded to the "to" option. This can be used to rename options
|
|
|
|
|
while providing backward compatibility. For example,
|
|
|
|
|
|
|
|
|
|
mkRenamedOptionModule [ "boot" "copyKernels" ] [ "boot" "loader" "grub" "copyKernels" ]
|
|
|
|
|
|
|
|
|
|
forwards any definitions of boot.copyKernels to
|
|
|
|
|
boot.loader.grub.copyKernels while printing a warning.
|
2019-01-24 03:58:33 +00:00
|
|
|
|
|
|
|
|
|
This also copies over the priority from the aliased option to the
|
|
|
|
|
non-aliased option.
|
2015-10-14 16:05:50 +00:00
|
|
|
|
*/
|
|
|
|
|
mkRenamedOptionModule = from: to: doRename {
|
|
|
|
|
inherit from to;
|
|
|
|
|
visible = false;
|
|
|
|
|
warn = true;
|
|
|
|
|
use = builtins.trace "Obsolete option `${showOption from}' is used. It was renamed to `${showOption to}'.";
|
|
|
|
|
};
|
|
|
|
|
|
2016-09-24 12:09:52 +00:00
|
|
|
|
/* Return a module that causes a warning to be shown if any of the "from"
|
|
|
|
|
option is defined; the defined values can be used in the "mergeFn" to set
|
|
|
|
|
the "to" value.
|
|
|
|
|
This function can be used to merge multiple options into one that has a
|
|
|
|
|
different type.
|
|
|
|
|
|
|
|
|
|
"mergeFn" takes the module "config" as a parameter and must return a value
|
|
|
|
|
of "to" option type.
|
|
|
|
|
|
|
|
|
|
mkMergedOptionModule
|
|
|
|
|
[ [ "a" "b" "c" ]
|
|
|
|
|
[ "d" "e" "f" ] ]
|
|
|
|
|
[ "x" "y" "z" ]
|
|
|
|
|
(config:
|
|
|
|
|
let value = p: getAttrFromPath p config;
|
|
|
|
|
in
|
|
|
|
|
if (value [ "a" "b" "c" ]) == true then "foo"
|
|
|
|
|
else if (value [ "d" "e" "f" ]) == true then "bar"
|
|
|
|
|
else "baz")
|
|
|
|
|
|
|
|
|
|
- options.a.b.c is a removed boolean option
|
|
|
|
|
- options.d.e.f is a removed boolean option
|
|
|
|
|
- options.x.y.z is a new str option that combines a.b.c and d.e.f
|
|
|
|
|
functionality
|
|
|
|
|
|
|
|
|
|
This show a warning if any a.b.c or d.e.f is set, and set the value of
|
2017-03-11 22:39:40 +00:00
|
|
|
|
x.y.z to the result of the merge function
|
2016-09-24 12:09:52 +00:00
|
|
|
|
*/
|
|
|
|
|
mkMergedOptionModule = from: to: mergeFn:
|
|
|
|
|
{ config, options, ... }:
|
|
|
|
|
{
|
|
|
|
|
options = foldl recursiveUpdate {} (map (path: setAttrByPath path (mkOption {
|
|
|
|
|
visible = false;
|
|
|
|
|
# To use the value in mergeFn without triggering errors
|
|
|
|
|
default = "_mkMergedOptionModule";
|
|
|
|
|
})) from);
|
|
|
|
|
|
|
|
|
|
config = {
|
2020-09-02 13:43:16 +00:00
|
|
|
|
_module.assertions =
|
|
|
|
|
let warningMessages = map (f:
|
|
|
|
|
let val = getAttrFromPath f config;
|
|
|
|
|
opt = getAttrFromPath f options;
|
|
|
|
|
in {
|
|
|
|
|
${showOption f} = {
|
|
|
|
|
enable = mkDefault (val != "_mkMergedOptionModule");
|
|
|
|
|
type = "warning";
|
|
|
|
|
message = "The option `${showOption f}' defined in ${showFiles opt.files} has been changed to `${showOption to}' that has a different type. Please read `${showOption to}' documentation and update your configuration accordingly.";
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
) from;
|
|
|
|
|
in mkMerge warningMessages;
|
2016-09-24 12:09:52 +00:00
|
|
|
|
} // setAttrByPath to (mkMerge
|
2017-03-11 22:39:40 +00:00
|
|
|
|
(optional
|
2016-09-24 12:09:52 +00:00
|
|
|
|
(any (f: (getAttrFromPath f config) != "_mkMergedOptionModule") from)
|
|
|
|
|
(mergeFn config)));
|
|
|
|
|
};
|
|
|
|
|
|
2016-09-24 12:10:29 +00:00
|
|
|
|
/* Single "from" version of mkMergedOptionModule.
|
|
|
|
|
Return a module that causes a warning to be shown if the "from" option is
|
|
|
|
|
defined; the defined value can be used in the "mergeFn" to set the "to"
|
|
|
|
|
value.
|
|
|
|
|
This function can be used to change an option into another that has a
|
|
|
|
|
different type.
|
|
|
|
|
|
|
|
|
|
"mergeFn" takes the module "config" as a parameter and must return a value of
|
|
|
|
|
"to" option type.
|
|
|
|
|
|
|
|
|
|
mkChangedOptionModule [ "a" "b" "c" ] [ "x" "y" "z" ]
|
|
|
|
|
(config:
|
|
|
|
|
let value = getAttrFromPath [ "a" "b" "c" ] config;
|
|
|
|
|
in
|
|
|
|
|
if value > 100 then "high"
|
|
|
|
|
else "normal")
|
|
|
|
|
|
|
|
|
|
- options.a.b.c is a removed int option
|
|
|
|
|
- options.x.y.z is a new str option that supersedes a.b.c
|
|
|
|
|
|
|
|
|
|
This show a warning if a.b.c is set, and set the value of x.y.z to the
|
|
|
|
|
result of the change function
|
|
|
|
|
*/
|
|
|
|
|
mkChangedOptionModule = from: to: changeFn:
|
|
|
|
|
mkMergedOptionModule [ from ] to changeFn;
|
|
|
|
|
|
2015-10-14 16:05:50 +00:00
|
|
|
|
/* Like ‘mkRenamedOptionModule’, but doesn't show a warning. */
|
|
|
|
|
mkAliasOptionModule = from: to: doRename {
|
|
|
|
|
inherit from to;
|
|
|
|
|
visible = true;
|
|
|
|
|
warn = false;
|
|
|
|
|
use = id;
|
|
|
|
|
};
|
|
|
|
|
|
2019-01-24 03:58:33 +00:00
|
|
|
|
doRename = { from, to, visible, warn, use, withPriority ? true }:
|
2018-04-28 20:16:37 +00:00
|
|
|
|
{ config, options, ... }:
|
2015-10-14 16:05:50 +00:00
|
|
|
|
let
|
2018-04-28 20:16:37 +00:00
|
|
|
|
fromOpt = getAttrFromPath from options;
|
2015-10-14 16:05:50 +00:00
|
|
|
|
toOf = attrByPath to
|
2018-03-27 23:02:40 +00:00
|
|
|
|
(abort "Renaming error: option `${showOption to}' does not exist.");
|
2019-12-29 23:49:44 +00:00
|
|
|
|
toType = let opt = attrByPath to {} options; in opt.type or null;
|
2015-10-14 16:05:50 +00:00
|
|
|
|
in
|
2018-04-28 20:16:37 +00:00
|
|
|
|
{
|
|
|
|
|
options = setAttrByPath from (mkOption {
|
|
|
|
|
inherit visible;
|
|
|
|
|
description = "Alias of <option>${showOption to}</option>.";
|
|
|
|
|
apply = x: use (toOf config);
|
2019-12-29 23:49:44 +00:00
|
|
|
|
} // optionalAttrs (toType != null) {
|
|
|
|
|
type = toType;
|
2018-04-28 20:16:37 +00:00
|
|
|
|
});
|
|
|
|
|
config = mkMerge [
|
|
|
|
|
{
|
2020-09-02 13:43:16 +00:00
|
|
|
|
_module.assertions.${showOption from} = {
|
|
|
|
|
enable = mkDefault (warn && fromOpt.isDefined);
|
|
|
|
|
type = "warning";
|
|
|
|
|
message = "The option `${showOption from}' defined in ${showFiles fromOpt.files} has been renamed to `${showOption to}'.";
|
|
|
|
|
};
|
2018-04-28 20:16:37 +00:00
|
|
|
|
}
|
2019-01-03 14:15:01 +00:00
|
|
|
|
(if withPriority
|
|
|
|
|
then mkAliasAndWrapDefsWithPriority (setAttrByPath to) fromOpt
|
|
|
|
|
else mkAliasAndWrapDefinitions (setAttrByPath to) fromOpt)
|
2018-04-28 20:16:37 +00:00
|
|
|
|
];
|
|
|
|
|
};
|
2015-10-14 16:05:50 +00:00
|
|
|
|
|
2020-09-12 14:33:56 +00:00
|
|
|
|
/* Use this function to import a JSON file as NixOS configuration.
|
|
|
|
|
|
|
|
|
|
importJSON -> path -> attrs
|
|
|
|
|
*/
|
|
|
|
|
importJSON = file: {
|
|
|
|
|
_file = file;
|
|
|
|
|
config = lib.importJSON file;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* Use this function to import a TOML file as NixOS configuration.
|
|
|
|
|
|
|
|
|
|
importTOML -> path -> attrs
|
|
|
|
|
*/
|
|
|
|
|
importTOML = file: {
|
|
|
|
|
_file = file;
|
|
|
|
|
config = lib.importTOML file;
|
|
|
|
|
};
|
2009-09-14 20:10:41 +00:00
|
|
|
|
}
|