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
2021-03-03 05:59:52 +00:00
isList
2020-10-20 14:10:21 +00:00
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
2021-04-27 13:52:15 +00:00
warnIf
2020-10-20 11:47:24 +00:00
;
2020-10-20 14:10:21 +00:00
inherit ( lib . options )
isOption
mkOption
showDefs
showFiles
showOption
unknownModule
2020-10-20 11:47:24 +00:00
;
in
2009-07-13 16:18:52 +00:00
rec {
2021-10-29 12:58:28 +00:00
/*
Evaluate a set of modules . The result is a set with the attributes :
‘ options ’ : The nested set of all option declarations ,
‘ config ’ : The nested set of all option values .
‘ type ’ : A module system type representing the module set as a submodule ,
to be extended by configuration from the containing module set .
2021-11-22 13:39:10 +00:00
This is also available as the module argument ‘ moduleType ’ .
2021-10-29 12:58:28 +00:00
‘ extendModules ’ : A function similar to ‘ evalModules ’ but building on top
of the module set . Its arguments , ‘ modules ’ and ‘ specialArgs ’ are
added to the existing values .
Using ‘ extendModules ’ a few times has no performance impact as long
as you only reference the final ‘ options ’ and ‘ config ’ .
If you do reference multiple ‘ config ’ ( or ‘ options ’ ) from before and
after ‘ extendModules ’ , performance is the same as with multiple
‘ evalModules ’ invocations , because the new modules' ability to
override existing configuration fundamentally requires a new
fixpoint to be constructed .
2021-11-22 13:39:44 +00:00
This is also available as a module argument .
2021-10-29 12:58:28 +00:00
‘ _module ’ : A portion of the configuration tree which is elided from
‘ config ’ . It contains some values that are mostly internal to the
module system implementation .
2014-05-05 20:30:51 +00:00
! ! ! 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 . * /
2021-10-27 18:42:05 +00:00
evalModules = evalModulesArgs @
{ modules
2014-05-05 20:30:51 +00:00
, 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
2021-12-02 17:47:17 +00:00
withWarnings = x :
lib . warnIf ( evalModulesArgs ? args ) " T h e a r g s a r g u m e n t t o e v a l M o d u l e s i s d e p r e c a t e d . P l e a s e s e t c o n f i g . _ m o d u l e . a r g s i n s t e a d . "
lib . warnIf ( evalModulesArgs ? check ) " T h e c h e c k a r g u m e n t t o e v a l M o d u l e s i s d e p r e c a t e d . P l e a s e s e t c o n f i g . _ m o d u l e . c h e c k i n s t e a d . "
x ;
2021-12-02 17:45:48 +00:00
legacyModules =
optional ( evalModulesArgs ? args ) {
config = {
_module . args = args ;
} ;
}
++ optional ( evalModulesArgs ? check ) {
config = {
_module . check = mkDefault check ;
} ;
} ;
regularModules = modules ++ legacyModules ;
2020-12-18 15:42:42 +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.
2021-12-02 17:45:48 +00:00
#
# When extended with extendModules or moduleType, a fresh instance of
# this module is used, to avoid conflicts and allow chaining of
# extendModules.
2014-05-05 19:52:33 +00:00
internalModule = rec {
2020-12-18 15:42:42 +00:00
_file = ./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
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-12-18 15:42:42 +00:00
internal = true ;
2015-03-12 22:19:23 +00:00
description = " A r g u m e n t s p a s s e d t o e a c h m o d u l e . " ;
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-11-30 21:10:48 +00:00
internal = true ;
2021-12-02 17:45:48 +00:00
default = true ;
2020-12-18 15:42:42 +00:00
description = " W h e t h e r t o c h e c k w h e t h e r a l l o p t i o n d e f i n i t i o n s h a v e m a t c h i n g d e c l a r a t i o n s . " ;
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-12-18 15:42:42 +00:00
internal = true ;
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 .
'' ;
} ;
2014-05-05 19:18:53 +00:00
} ;
config = {
2021-10-29 11:15:38 +00:00
_module . args = {
inherit extendModules ;
2021-11-22 13:39:10 +00:00
moduleType = type ;
2021-12-02 17:45:48 +00:00
} ;
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 " " )
2021-12-02 17:45:48 +00:00
( regularModules ++ [ internalModule ] )
2021-05-06 01:42:52 +00:00
( { inherit lib options config specialArgs ; } // specialArgs ) ;
2020-03-19 22:45:23 +00:00
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-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 = " T h e o p t i o n ` ${ showOption ( prefix ++ firstDef . prefix ) } ' d o e s n o t e x i s t . D e f i n i t i o n v a l u e s : ${ showDefs [ firstDef ] } " ;
2020-08-14 17:26:18 +00:00
in
if attrNames options == [ " _ m o d u l e " ]
2021-07-23 08:56:24 +00:00
then
let
optionName = showOption prefix ;
in
if optionName == " "
then throw ''
$ { baseMsg }
2021-08-25 22:37:33 +00:00
It seems as if you're trying to declare an option by placing it into ` config' rather than ` options' !
2021-07-23 08:56:24 +00:00
''
else
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' .
''
2020-08-14 17:26:18 +00:00
else throw baseMsg
2020-03-18 19:09:09 +00:00
else null ;
2021-10-27 18:42:05 +00:00
checked = builtins . seq checkUnmatched ;
2021-10-29 11:15:38 +00:00
extendModules = extendArgs @ {
modules ? [ ] ,
specialArgs ? { } ,
prefix ? [ ] ,
} :
evalModules ( evalModulesArgs // {
2021-12-02 17:45:48 +00:00
modules = regularModules ++ modules ;
2021-10-29 11:15:38 +00:00
specialArgs = evalModulesArgs . specialArgs or { } // specialArgs ;
prefix = extendArgs . prefix or evalModulesArgs . prefix ;
} ) ;
type = lib . types . submoduleWith {
inherit modules specialArgs ;
} ;
2021-12-02 17:47:17 +00:00
result = withWarnings {
2021-10-27 18:42:05 +00:00
options = checked options ;
config = checked ( removeAttrs config [ " _ m o d u l e " ] ) ;
_module = checked ( config . _module ) ;
2021-10-29 11:15:38 +00:00
inherit extendModules type ;
2020-03-16 20:05:52 +00:00
} ;
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 )
2021-03-03 05:59:52 +00:00
else if isList m then
let defs = [ { file = fallbackFile ; value = m ; } ] ; in
throw " M o d u l e i m p o r t s c a n ' t b e n e s t e d l i s t s . P e r h a p s y o u m e a n t t o r e m o v e o n e l e v e l o f l i s t s ? D e f i n i t i o n s : ${ showDefs defs } "
2020-01-02 21:09:31 +00:00
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 " d i s a b l e d " modules ) ;
inherit modules ;
} ;
in parentFile : parentKey : initialModules : args : collectResults ( imap1 ( n : x :
let
module = loadModule args parentFile " ${ parentKey } : a n o n - ${ 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
/* M a s s a g e a m o d u l e i n t o c a n o n i c a l f o r m , t h a t i s , a s e t c o n s i s t i n g
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 [ " _ f i l e " " k e y " " d i s a b l e d M o d u l e s " " i m p o r t s " " o p t i o n s " " c o n f i g " " m e t a " " f r e e f o r m T y p e " ] ; in
2013-10-27 23:56:22 +00:00
if badAttrs != { } then
2019-12-30 15:08:03 +00:00
throw " M o d u l e ` ${ key } ' h a s a n u n s u p p o r t e d a t t r i b u t e ` ${ head ( attrNames badAttrs ) } ' . T h i s i s c a u s e d b y i n t r o d u c i n g a t o p - l e v e l ` c o n f i g ' o r ` o p t i o n s ' a t t r i b u t e . A d d c o n f i g u r a t i o n a t t r i b u t e s i m m e d i a t e l y o n t h e t o p l e v e l i n s t e a d , o r m o v e a l l o f t h e m ( n a m e l y : ${ toString ( attrNames badAttrs ) } ) i n t o t h e e x p l i c i t ` c o n f i g ' a t t r i b u t e . "
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 [ " _ f i l e " " k e y " " d i s a b l e d M o d u l e s " " r e q u i r e " " i m p o r t s " " f r e e f o r m T y p e " ] ) ) ;
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.
2015-05-13 20:44:04 +00:00
context = name : '' w h i l e e v a l u a t i n g t h e m o d u l e a r g u m e n t ` ${ name } ' i n " ${ key } " : '' ;
2021-05-06 02:59:27 +00:00
extraArgs = builtins . mapAttrs ( name : _ :
builtins . addErrorContext ( context name )
( args . ${ name } or config . _module . args . ${ name } )
) ( lib . functionArgs f ) ;
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
/* M e r g e a l i s t o f m o d u l e s . T h i s w i l l r e c u r s e o v e r t h e o p t i o n
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 = " o p t i o n " ; value = " o p t i o n v a l u e o f f o o " ; . . . } ;
bar . baz = { _type = " o p t i o n " ; value = " o p t i o n v a l u e o f b a r . b a z " ; . . . } ;
. . .
} ;
# A list of definitions that weren't matched by any option
unmatchedDefns = [
{ file = " f i l e . n i x " ; prefix = [ " q u x " ] ; value = " q u x " ; }
. . .
] ;
}
* /
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
/* b y N a m e i s l i k e f o l d A t t r s , b u t w i l l l o o k f o r a t t r i b u t e s t o m e r g e i n t h e
specified attribute name .
byName " f o o " ( module : value : [ " m o d u l e . h i d d e n = ${ module . hidden } , v a l u e = ${ value } " ] )
[
{
hidden = " b a z " ;
foo = { qux = " b a r " ; gla = " f l o p " ; } ;
}
{
hidden = " f l i " ;
foo = { qux = " g n e " ; gli = " f l i p " ; } ;
}
]
== = >
{
gla = [ " m o d u l e . h i d d e n = b a z , v a l u e = f l o p " ] ;
gli = [ " m o d u l e . h i d d e n = f l i , v a l u e = f l i p " ] ;
qux = [ " m o d u l e . h i d d e n = b a z , v a l u e = b a r " " m o d u l e . h i d d e n = f l i , v a l u e = g n e " ] ;
}
* /
2019-01-26 19:43:11 +00:00
byName = attr : f : modules :
foldl' ( acc : module :
2021-03-11 10:57:38 +00:00
if ! ( builtins . isAttrs module . ${ attr } ) then
throw ''
You're trying to declare a value of type ` $ { builtins . typeOf module . ${ attr } } '
rather than an attribute-set for the option
` $ { builtins . concatStringsSep " . " prefix } ' !
This usually happens if ` $ { builtins . concatStringsSep " . " prefix } ' has option
definitions inside that are not matched . Please check how to properly define
this option by e . g . referring to ` man 5 configuration . nix' !
''
else
2019-01-26 19:43:11 +00:00
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 " o p t i o n s " ( 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 " c o n f i g " ( 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 " c o n f i g " ( 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 " T h e o p t i o n ` ${ showOption loc } ' i n ` ${ firstOption . _file } ' i s a p r e f i x o f o p t i o n s i n ` ${ 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
/* M e r g e m u l t i p l e o p t i o n d e c l a r a t i o n s i n t o a s i n g l e d e c l a r a t i o n . I n
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 " t y p e " ) && typesMergeable
then { type = mergedType ; }
else { } ;
bothHave = k : opt . options ? ${ k } && res ? ${ k } ;
in
if bothHave " d e f a u l t " ||
bothHave " e x a m p l e " ||
bothHave " d e s c r i p t i o n " ||
bothHave " a p p l y " ||
( bothHave " t y p e " && ( ! typesMergeable ) )
2013-10-27 23:56:22 +00:00
then
2019-12-04 23:00:47 +00:00
throw " T h e o p t i o n ` ${ showOption loc } ' i n ` ${ opt . _file } ' i s a l r e a d y d e c l a r e d i n ${ showFiles res . declarations } . "
2013-10-27 23:56:22 +00:00
else
2014-08-29 12:38:19 +00:00
let
/* A d d t h e m o d u l e s o f t h e c u r r e n t o p t i o n t o t h e l i s t o f m o d u l e s
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
/* M e r g e a l l t h e d e f i n i t i o n s o f a n o p t i o n t o p r o d u c e t h e f i n a l
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 " T h e o p t i o n ` ${ showOption loc } ' i s r e a d - o n l y , b u t i t ' s s e t m u l t i p l e t i m e s . D e f i n i t i o n v a l u e s : ${ 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
2021-05-05 16:53:34 +00:00
warnDeprecation =
warnIf ( opt . type . deprecationMessage != null )
" T h e t y p e ` t y p e s . ${ opt . type . name } ' o f o p t i o n ` ${ showOption loc } ' d e f i n e d i n ${ showFiles opt . declarations } i s d e p r e c a t e d . ${ opt . type . deprecationMessage } " ;
2020-09-04 11:39:27 +00:00
2021-05-05 16:53:34 +00:00
in warnDeprecation opt //
2018-04-02 19:00:47 +00:00
{ value = builtins . addErrorContext " w h i l e e v a l u a t i n g t h e o p t i o n ` ${ 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 ;
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 " w h i l e e v a l u a t i n g d e f i n i t i o n s f r o m ` ${ 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 " " == " o r d e r " ) 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 d e f i n i t i o n f o r o p t i o n ` ${ showOption loc } ' i s n o t o f t y p e ` ${ type . description } ' . D e f i n i t i o n v a l u e s : ${ 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 " T h e o p t i o n ` ${ showOption loc } ' i s u s e d b u t n o t d e f i n e d . " ;
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
/* G i v e n a c o n f i g s e t , e x p a n d m k M e r g e p r o p e r t i e s , a n d p u s h d o w n t h e
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 " " == " m e r g e " then
concatMap pushDownProperties cfg . contents
else if cfg . _type or " " == " i f " 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 " " == " o v e r r i d e " 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 ] ;
/* G i v e n a c o n f i g v a l u e , e x p a n d m k M e r g e p r o p e r t i e s , a n d d i s c h a r g e
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 " " == " m e r g e " then
concatMap dischargeProperties def . contents
else if def . _type or " " == " i f " 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 " ‘ m k I f ’ c a l l e d w i t h a n o n - B o o l e a n c o n d i t i o n "
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
/* G i v e n a l i s t o f c o n f i g v a l u e s , p r o c e s s t h e m k O v e r r i d e p r o p e r t i e s ,
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 " " == " o v e r r i d e " 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 " " == " o v e r r i d e " 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
/* S o r t a l i s t o f p r o p e r t i e s . T h e s o r t p r i o r i t y o f a p r o p e r t y i s
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 " " == " o r d e r "
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
/* H a c k f o r b a c k w a r d c o m p a t i b i l i t y : c o n v e r t o p t i o n s o f t y p e
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 " O p t i o n ` ${ showOption loc } ' h a s t y p e o p t i o n S e t b u t h a s n o o p t i o n a t t r i b u t e , i n ${ showFiles opt . declarations } . " ) ;
2019-03-07 19:28:09 +00:00
f = tp :
let optionSetIn = type : ( tp . name == type ) && ( tp . functor . wrapped . name == " o p t i o n S e t " ) ;
in
if tp . name == " o p t i o n s e t " || tp . name == " s u b m o d u l e " then
throw " T h e o p t i o n ${ showOption loc } u s e s s u b m o d u l e s w i t h o u t a w r a p p i n g t y p e , i n ${ showFiles opt . declarations } . "
else if optionSetIn " a t t r s O f " then types . attrsOf ( types . submodule options )
else if optionSetIn " l i s t O f " then types . listOf ( types . submodule options )
else if optionSetIn " n u l l O r " 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
/* P r o p e r t i e s . */
mkIf = condition : content :
{ _type = " i f " ;
inherit condition content ;
} ;
mkAssert = assertion : message : content :
mkIf
( if assertion then true else throw " \n F a i l e d a s s e r t i o n : ${ message } " )
content ;
mkMerge = contents :
{ _type = " m e r g e " ;
inherit contents ;
} ;
mkOverride = priority : content :
{ _type = " o v e r r i d e " ;
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
2021-08-03 22:12:03 +00:00
mkImageMediaOverride = mkOverride 60 ; # image media profiles can be derived by inclusion into host config, hence needing to override host config, but do allow user to mkForce
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
2021-07-12 05:31:20 +00:00
mkFixStrictness = lib . warn " l i b . m k F i x S t r i c t n e s s h a s n o e f f e c t a n d w i l l b e r e m o v e d . I t r e t u r n s i t s a r g u m e n t u n m o d i f i e d , s o y o u c a n j u s t r e m o v e a n y c a l l s . " id ;
2021-07-12 05:23:45 +00:00
2014-03-30 18:35:25 +00:00
mkOrder = priority : content :
{ _type = " o r d e r " ;
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
/* C o m p a t i b i l i t y . */
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
/* R e t u r n a m o d u l e t h a t c a u s e s a w a r n i n g t o b e s h o w n i f t h e
specified option is defined . For example ,
2016-03-26 23:01:43 +00:00
mkRemovedOptionModule [ " b o o t " " l o a d e r " " g r u b " " b o o t D e v i c e " ] " < r e p l a c e m e n t i n s t r u c t i o n s > "
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 " T h e o p t i o n ` ${ showOption optionName } ' c a n n o l o n g e r b e u s e d s i n c e i t ' s b e e n r e m o v e d . ${ replacementInstructions } " ;
2015-10-14 16:05:50 +00:00
} ) ;
2020-12-18 15:42:42 +00:00
config . assertions =
let opt = getAttrFromPath optionName options ; in [ {
assertion = ! 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-12-18 15:42:42 +00:00
} ] ;
2015-10-14 16:05:50 +00:00
} ;
/* R e t u r n a m o d u l e t h a t c a u s e s a w a r n i n g t o b e s h o w n i f t h e
specified " f r o m " option is defined ; the defined value is however
forwarded to the " t o " option . This can be used to rename options
while providing backward compatibility . For example ,
mkRenamedOptionModule [ " b o o t " " c o p y K e r n e l s " ] [ " b o o t " " l o a d e r " " g r u b " " c o p y K e r n e l s " ]
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 " O b s o l e t e o p t i o n ` ${ showOption from } ' i s u s e d . I t w a s r e n a m e d t o ` ${ showOption to } ' . " ;
} ;
2016-09-24 12:09:52 +00:00
/* R e t u r n a m o d u l e t h a t c a u s e s a w a r n i n g t o b e s h o w n i f a n y o f t h e " f r o m "
option is defined ; the defined values can be used in the " m e r g e F n " to set
the " t o " value .
This function can be used to merge multiple options into one that has a
different type .
" m e r g e F n " takes the module " c o n f i g " as a parameter and must return a value
of " t o " 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 " f o o "
else if ( value [ " d " " e " " f " ] ) == true then " b a r "
else " b a z " )
- 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 = " _ m k M e r g e d O p t i o n M o d u l e " ;
} ) ) from ) ;
config = {
2020-12-18 15:42:42 +00:00
warnings = filter ( x : x != " " ) ( map ( f :
let val = getAttrFromPath f config ;
opt = getAttrFromPath f options ;
in
optionalString
( val != " _ m k M e r g e d O p t i o n M o d u l e " )
" T h e o p t i o n ` ${ showOption f } ' d e f i n e d i n ${ showFiles opt . files } h a s b e e n c h a n g e d t o ` ${ showOption to } ' t h a t h a s a d i f f e r e n t t y p e . P l e a s e r e a d ` ${ showOption to } ' d o c u m e n t a t i o n a n d u p d a t e y o u r c o n f i g u r a t i o n a c c o r d i n g l y . "
) from ) ;
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 ) != " _ m k M e r g e d O p t i o n M o d u l e " ) from )
( mergeFn config ) ) ) ;
} ;
2016-09-24 12:10:29 +00:00
/* S i n g l e " f r o m " v e r s i o n o f m k M e r g e d O p t i o n M o d u l e .
Return a module that causes a warning to be shown if the " f r o m " option is
defined ; the defined value can be used in the " m e r g e F n " to set the " t o "
value .
This function can be used to change an option into another that has a
different type .
" m e r g e F n " takes the module " c o n f i g " as a parameter and must return a value of
" t o " option type .
mkChangedOptionModule [ " a " " b " " c " ] [ " x " " y " " z " ]
( config :
let value = getAttrFromPath [ " a " " b " " c " ] config ;
in
if value > 100 then " h i g h "
else " n o r m a l " )
- 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
/* L i k e ‘ m k R e n a m e d O p t i o n M o d u l e ’ , b u t d o e s n ' t s h o w a w a r n i n g . */
mkAliasOptionModule = from : to : doRename {
inherit from to ;
visible = true ;
warn = false ;
use = id ;
} ;
2021-11-14 14:57:54 +00:00
/* m k D e r i v e d C o n f i g : O p t i o n a - > ( a - > D e f i n i t i o n b ) - > D e f i n i t i o n b
Create config definitions with the same priority as the definition of another option .
This should be used for option definitions where one option sets the value of another as a convenience .
For instance a config file could be set with a ` text ` or ` source ` option , where text translates to a ` source `
value using ` mkDerivedConfig options . text ( pkgs . writeText " f i l e n a m e . c o n f " ) ` .
It takes care of setting the right priority using ` mkOverride ` .
* /
# TODO: make the module system error message include information about `opt` in
# error messages about conflicts. E.g. introduce a variation of `mkOverride` which
# adds extra location context to the definition object. This will allow context to be added
# to all messages that report option locations "this value was derived from <full option name>
# which was defined in <locations>". It can provide a trace of options that contributed
# to definitions.
mkDerivedConfig = opt : f :
mkOverride
( opt . highestPrio or defaultPriority )
( f opt . value ) ;
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 " R e n a m i n g e r r o r : o p t i o n ` ${ showOption to } ' d o e s n o t e x i s t . " ) ;
2021-01-21 20:57:48 +00:00
toType = let opt = attrByPath to { } options ; in opt . type or ( types . submodule { } ) ;
2015-10-14 16:05:50 +00:00
in
2018-04-28 20:16:37 +00:00
{
options = setAttrByPath from ( mkOption {
inherit visible ;
description = " A l i a s o f < o p t i o n > ${ showOption to } < / o p t i o n > . " ;
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-12-18 15:42:42 +00:00
warnings = optional ( warn && fromOpt . isDefined )
" T h e o p t i o n ` ${ showOption from } ' d e f i n e d i n ${ showFiles fromOpt . files } h a s b e e n r e n a m e d t o ` ${ 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
/* U s e t h i s f u n c t i o n t o i m p o r t a J S O N f i l e a s N i x O S c o n f i g u r a t i o n .
importJSON -> path -> attrs
* /
importJSON = file : {
_file = file ;
config = lib . importJSON file ;
} ;
/* U s e t h i s f u n c t i o n t o i m p o r t a T O M L f i l e a s N i x O S c o n f i g u r a t i o n .
importTOML -> path -> attrs
* /
importTOML = file : {
_file = file ;
config = lib . importTOML file ;
} ;
2009-09-14 20:10:41 +00:00
}