2024-05-01 22:14:04 +00:00
{ lib }:
let
inherit ( lib )
all
any
attrByPath
attrNames
catAttrs
concatLists
concatMap
concatStringsSep
elem
filter
foldl'
getAttrFromPath
head
id
imap1
isAttrs
isBool
isFunction
isList
isString
length
mapAttrs
mapAttrsToList
mapAttrsRecursiveCond
min
optional
optionalAttrs
optionalString
recursiveUpdate
2024-06-30 08:16:52 +00:00
reverseList
sort
2024-05-01 22:14:04 +00:00
setAttrByPath
types
warnIf
zipAttrsWith
;
inherit ( lib . options )
isOption
mkOption
showDefs
showFiles
showOption
unknownModule
;
2024-06-30 08:16:52 +00:00
inherit ( lib . strings ) isConvertibleWithToString ;
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
showDeclPrefix =
loc : decl : prefix :
" - o p t i o n ( s ) w i t h p r e f i x ` ${ showOption ( loc ++ [ prefix ] ) } ' i n m o d u l e ` ${ decl . _file } ' " ;
showRawDecls =
loc : decls :
concatStringsSep " \n " (
sort ( a : b : a < b ) ( concatMap ( decl : map ( showDeclPrefix loc decl ) ( attrNames decl . options ) ) decls )
) ;
/*
See https://nixos.org/manual/nixpkgs/unstable/ #module-system-lib-evalModules
or file://./../doc/module-system/module-system.chapter.md
! ! ! 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 =
evalModulesArgs @ {
modules ,
prefix ? [ ] ,
# This should only be used for special arguments that need to be evaluated
# when resolving module structure (like in imports). For everything else,
# there's _module.args. If specialArgs.modulesPath is defined it will be
# used as the base path for disabledModules.
specialArgs ? { } ,
# `class`:
# A nominal type for modules. When set and non-null, this adds a check to
# make sure that only compatible modules are imported.
class ? null ,
# This would be remove in the future, Prefer _module.args option instead.
args ? { } ,
# This would be remove in the future, Prefer _module.check option instead.
check ? true ,
} :
2024-05-01 22:14:04 +00:00
let
2024-06-30 08:16:52 +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 ;
2024-05-01 22:14:04 +00:00
legacyModules =
2024-06-30 08:16:52 +00:00
optional ( evalModulesArgs ? args ) {
2024-05-01 22:14:04 +00:00
config = {
_module . args = args ;
} ;
}
2024-06-30 08:16:52 +00:00
++ optional ( evalModulesArgs ? check ) {
2024-05-01 22:14:04 +00:00
config = {
_module . check = mkDefault check ;
} ;
} ;
regularModules = modules ++ legacyModules ;
# 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.
#
# When extended with extendModules or moduleType, a fresh instance of
# this module is used, to avoid conflicts and allow chaining of
# extendModules.
internalModule = rec {
_file = " l i b / m o d u l e s . n i x " ;
key = _file ;
options = {
_module . args = mkOption {
# 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 . raw ;
# Only render documentation once at the root of the option tree,
# not for all individual submodules.
# Allow merging option decls to make this internal regardless.
2024-06-30 08:16:52 +00:00
$ {
if prefix == [ ] then
null # unset => visible
else
" i n t e r n a l "
} = true ;
2024-05-01 22:14:04 +00:00
# TODO: Change the type of this option to a submodule with a
# freeformType, so that individual arguments can be documented
# separately
description = ''
Additional arguments passed to each module in addition to ones
like ` lib ` , ` config ` ,
and ` pkgs ` , ` modulesPath ` .
This option is also available to all submodules . Submodules do not
inherit args from their parent module , nor do they provide args to
their parent module or sibling submodules . The sole exception to
this is the argument ` name ` which is provided by
parent modules to a submodule and contains the attribute name
the submodule is bound to , or a unique generated name if it is
not bound to an attribute .
Some arguments are already passed by default , of which the
following * cannot * be changed with this option :
- { var } ` lib ` : The nixpkgs library .
- { var } ` config ` : The results of all options after merging the values from all modules together .
- { var } ` options ` : The options declared in all modules .
- { var } ` specialArgs ` : The ` specialArgs ` argument passed to ` evalModules ` .
- All attributes of { var } ` specialArgs `
Whereas option values can generally depend on other option values
thanks to laziness , this does not apply to ` imports ` , which
must be computed statically before anything else .
For this reason , callers of the module system can provide ` specialArgs `
which are available during import resolution .
For NixOS , ` specialArgs ` includes
{ var } ` modulesPath ` , which allows you to import
extra modules from the nixpkgs package tree without having to
somehow make the module aware of the location of the
` nixpkgs ` or NixOS directories .
` ` `
{ modulesPath , . . . }: {
imports = [
( modulesPath + " / p r o f i l e s / m i n i m a l . n i x " )
] ;
}
` ` `
For NixOS , the default value for this option includes at least this argument :
- { var } ` pkgs ` : The nixpkgs package set according to
the { option } ` nixpkgs . pkgs ` option .
'' ;
} ;
_module . check = mkOption {
type = types . bool ;
internal = true ;
default = true ;
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 . " ;
} ;
_module . freeformType = mkOption {
type = types . nullOr types . optionType ;
internal = true ;
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 `
config ` value .
If this is ` null ` , definitions without an option
will throw an error unless { option } ` _module . check ` is
turned off .
'' ;
} ;
_module . specialArgs = mkOption {
readOnly = true ;
internal = true ;
description = ''
Externally provided module arguments that can't be modified from
within a configuration , but can be used in module imports .
'' ;
} ;
} ;
config = {
_module . args = {
inherit extendModules ;
moduleType = type ;
} ;
_module . specialArgs = specialArgs ;
} ;
} ;
merged =
2024-06-30 08:16:52 +00:00
let
collected =
collectModules class ( specialArgs . modulesPath or " " ) ( regularModules ++ [ internalModule ] )
(
{
inherit
lib
options
config
specialArgs
;
}
// specialArgs
) ;
in
mergeModules prefix ( reverseList collected ) ;
2024-05-01 22:14:04 +00:00
options = merged . matchedOptions ;
config =
let
# For definitions that have an associated option
2024-06-30 08:16:52 +00:00
declaredConfig = mapAttrsRecursiveCond ( v : ! isOption v ) ( _ : v : v . value ) options ;
2024-05-01 22:14:04 +00:00
# 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 ;
2024-06-30 08:16:52 +00:00
in
if defs == [ ] then { } else declaredConfig . _module . freeformType . merge prefix defs ;
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +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 ;
2024-05-01 22:14:04 +00:00
checkUnmatched =
2024-06-30 08:16:52 +00:00
if config . _module . check && config . _module . freeformType == null && merged . unmatchedDefns != [ ] then
2024-05-01 22:14:04 +00:00
let
firstDef = head merged . unmatchedDefns ;
baseMsg =
let
optText = showOption ( prefix ++ firstDef . prefix ) ;
defText =
builtins . addErrorContext
" w h i l e e v a l u a t i n g t h e e r r o r m e s s a g e f o r d e f i n i t i o n s f o r ` ${ optText } ' , w h i c h i s a n o p t i o n t h a t d o e s n o t e x i s t "
2024-06-30 08:16:52 +00:00
(
builtins . addErrorContext " w h i l e e v a l u a t i n g a d e f i n i t i o n f r o m ` ${ firstDef . file } ' " ( showDefs [
firstDef
] )
2024-05-01 22:14:04 +00:00
) ;
in
2024-06-30 08:16:52 +00:00
" T h e o p t i o n ` ${ optText } ' 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 : ${ defText } " ;
2024-05-01 22:14:04 +00:00
in
2024-06-30 08:16:52 +00:00
if
attrNames options == [ " _ m o d u l e " ]
# No options were declared at all (`_module` is built in)
# but we do have unmatched definitions, and no freeformType (earlier conditions)
then
let
optionName = showOption prefix ;
in
if optionName == " " then
throw ''
$ { baseMsg }
It seems as if you're trying to declare an option by placing it into ` config' rather than ` options' !
''
else
throw ''
$ { baseMsg }
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
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
else
null ;
2024-05-01 22:14:04 +00:00
checked = builtins . seq checkUnmatched ;
2024-06-30 08:16:52 +00:00
extendModules =
extendArgs @ {
modules ? [ ] ,
specialArgs ? { } ,
prefix ? [ ] ,
2024-05-01 22:14:04 +00:00
} :
2024-06-30 08:16:52 +00:00
evalModules (
evalModulesArgs
// {
2024-05-01 22:14:04 +00:00
inherit class ;
modules = regularModules ++ modules ;
2024-06-30 08:16:52 +00:00
specialArgs = evalModulesArgs . specialArgs or { } // specialArgs ;
prefix = extendArgs . prefix or evalModulesArgs . prefix or [ ] ;
}
) ;
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
type = lib . types . submoduleWith { inherit modules specialArgs class ; } ;
2024-05-01 22:14:04 +00:00
result = withWarnings {
_type = " c o n f i g u r a t i o n " ;
options = checked options ;
config = checked ( removeAttrs config [ " _ m o d u l e " ] ) ;
_module = checked ( config . _module ) ;
inherit extendModules type ;
class = class ;
} ;
2024-06-30 08:16:52 +00:00
in
result ;
2024-05-01 22:14:04 +00:00
# collectModules :: (class: String) -> (modulesPath: String) -> (modules: [ Module ]) -> (args: Attrs) -> [ Module ]
#
# Collects all modules recursively through `import` statements, filtering out
# all modules in disabledModules.
2024-06-30 08:16:52 +00:00
collectModules =
class :
let
2024-05-01 22:14:04 +00:00
# Like unifyModuleSyntax, but also imports paths and calls functions if necessary
2024-06-30 08:16:52 +00:00
loadModule =
args : fallbackFile : fallbackKey : m :
2024-05-01 22:14:04 +00:00
if isFunction m then
unifyModuleSyntax fallbackFile fallbackKey ( applyModuleArgs fallbackKey m args )
else if isAttrs m then
if m . _type or " m o d u l e " == " m o d u l e " then
unifyModuleSyntax fallbackFile fallbackKey m
else if m . _type == " i f " || m . _type == " o v e r r i d e " then
loadModule args fallbackFile fallbackKey { config = m ; }
else
throw (
" C o u l d n o t l o a d a v a l u e a s a m o d u l e , b e c a u s e i t i s o f t y p e ${ lib . strings . escapeNixString m . _type } "
+ lib . optionalString ( fallbackFile != unknownModule ) " , i n f i l e ${ toString fallbackFile } . "
2024-06-30 08:16:52 +00:00
+
lib . optionalString ( m . _type == " c o n f i g u r a t i o n " )
" I f y o u d o i n t e n d t o i m p o r t t h i s c o n f i g u r a t i o n , p l e a s e o n l y i m p o r t t h e m o d u l e s t h a t m a k e u p t h e c o n f i g u r a t i o n . Y o u m a y h a v e t o c r e a t e a ` l e t ` b i n d i n g , f i l e o r a t t r i b u t e t o g i v e y o u r s e l f a c c e s s t o t h e r e l e v a n t m o d u l e s . \n W h i l e l o a d i n g a c o n f i g u r a t i o n i n t o t h e m o d u l e s y s t e m i s a v e r y s e n s i b l e i d e a , i t c a n n o t b e d o n e c l e a n l y i n p r a c t i c e . "
# Extended explanation: That's because a finalized configuration is more than just a set of modules. For instance, it has its own `specialArgs` that, by the nature of `specialArgs` can't be loaded through `imports` or the the `modules` argument. So instead, we have to ask you to extract the relevant modules and use those instead. This way, we keep the module system comparatively simple, and hopefully avoid a bad surprise down the line.
2024-05-01 22:14:04 +00:00
)
else if isList m then
2024-06-30 08:16:52 +00:00
let
defs = [
{
file = fallbackFile ;
value = m ;
}
] ;
in
2024-05-01 22:14:04 +00:00
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 } "
2024-06-30 08:16:52 +00:00
else
unifyModuleSyntax ( toString m ) ( toString m ) (
applyModuleArgsIfFunction ( toString m ) ( import m ) args
) ;
2024-05-01 22:14:04 +00:00
checkModule =
2024-06-30 08:16:52 +00:00
if class != null then
2024-05-01 22:14:04 +00:00
m :
2024-06-30 08:16:52 +00:00
if m . _class != null -> m . _class == class then
m
else
throw " T h e m o d u l e ${ m . _file or m . key } w a s i m p o r t e d i n t o ${ class } i n s t e a d o f ${ m . _class } . "
2024-05-01 22:14:04 +00:00
else
m : m ;
/*
2024-06-30 08:16:52 +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 = [ . . . ] ;
}
. . .
] ;
}
. . .
] ;
}
2024-05-01 22:14:04 +00:00
* /
collectStructuredModules =
let
collectResults = modules : {
disabled = concatLists ( catAttrs " d i s a b l e d " modules ) ;
inherit modules ;
} ;
2024-06-30 08:16:52 +00:00
in
parentFile : parentKey : initialModules : args :
collectResults (
imap1 (
n : x :
let
module = checkModule ( 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 =
(
if module . disabledModules != [ ] then
[
{
file = module . _file ;
disabled = module . disabledModules ;
}
]
else
[ ]
)
++ collectedImports . disabled ;
}
) initialModules
) ;
2024-05-01 22:14:04 +00:00
# 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
2024-06-30 08:16:52 +00:00
filterModules =
modulesPath :
{ disabled , modules }:
2024-05-01 22:14:04 +00:00
let
2024-06-30 08:16:52 +00:00
moduleKey =
file : m :
if isString m then
if builtins . substring 0 1 m == " / " then m else toString modulesPath + " / " + m
else if isConvertibleWithToString m then
if m ? key && m . key != toString m then
2024-05-01 22:14:04 +00:00
throw " M o d u l e ` ${ file } ` c o n t a i n s a d i s a b l e d M o d u l e s i t e m t h a t i s a n a t t r i b u t e s e t t h a t c a n b e c o n v e r t e d t o a s t r i n g ( ${ toString m } ) b u t a l s o h a s a ` . k e y ` a t t r i b u t e ( ${ m . key } ) w i t h a d i f f e r e n t v a l u e . T h i s m a k e s i t a m b i g u o u s w h i c h m o d u l e s h o u l d b e d i s a b l e d . "
else
toString m
2024-06-30 08:16:52 +00:00
else if m ? key then
2024-05-01 22:14:04 +00:00
m . key
2024-06-30 08:16:52 +00:00
else if isAttrs m then
throw " M o d u l e ` ${ file } ` c o n t a i n s a d i s a b l e d M o d u l e s i t e m t h a t i s a n a t t r i b u t e s e t , p r e s u m a b l y a m o d u l e , t h a t d o e s n o t h a v e a ` k e y ` a t t r i b u t e . T h i s m e a n s t h a t t h e m o d u l e s y s t e m d o e s n ' t h a v e a n y m e a n s t o i d e n t i f y t h e m o d u l e t h a t s h o u l d b e d i s a b l e d . M a k e s u r e t h a t y o u ' v e p u t t h e c o r r e c t v a l u e i n d i s a b l e d M o d u l e s : a s t r i n g p a t h r e l a t i v e t o m o d u l e s P a t h , a p a t h v a l u e , o r a n a t t r i b u t e s e t w i t h a ` k e y ` a t t r i b u t e . "
else
throw " E a c h d i s a b l e d M o d u l e s i t e m m u s t b e a p a t h , s t r i n g , o r a a t t r i b u t e s e t w i t h a k e y a t t r i b u t e , o r a v a l u e s u p p o r t e d b y t o S t r i n g . H o w e v e r , o n e o f t h e d i s a b l e d M o d u l e s i t e m s i n ` ${ toString file } ` i s n o n e o f t h a t , b u t i s o f t y p e ${ builtins . typeOf m } . " ;
2024-05-01 22:14:04 +00:00
disabledKeys = concatMap ( { file , disabled }: map ( moduleKey file ) disabled ) disabled ;
2024-06-30 08:16:52 +00:00
keyFilter = filter ( attrs : ! elem attrs . key disabledKeys ) ;
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 ) ;
# Wrap a module with a default location for reporting errors.
setDefaultModuleLocation = file : m : {
_file = file ;
imports = [ m ] ;
} ;
/*
Massage a module into canonical form , that is , a set consisting
of ‘ options ’ , ‘ config ’ and ‘ imports ’ attributes .
* /
unifyModuleSyntax =
file : key : m :
2024-05-01 22:14:04 +00:00
let
2024-06-30 08:16:52 +00:00
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 ;
2024-05-01 22:14:04 +00:00
in
if m ? config || m ? options then
2024-06-30 08:16:52 +00:00
let
badAttrs = removeAttrs m [
" _ c l a s s "
" _ 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
if badAttrs != { } then
2024-05-01 22:14:04 +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 . "
else
2024-06-30 08:16:52 +00:00
{
_file = toString m . _file or file ;
2024-05-01 22:14:04 +00:00
_class = m . _class or null ;
key = toString m . key or key ;
2024-06-30 08:16:52 +00:00
disabledModules = m . disabledModules or [ ] ;
imports = m . imports or [ ] ;
options = m . options or { } ;
config = addFreeformType ( addMeta ( m . config or { } ) ) ;
2024-05-01 22:14:04 +00:00
}
else
# shorthand syntax
2024-06-30 08:16:52 +00:00
lib . throwIfNot ( isAttrs m ) " m o d u l e ${ file } ( ${ key } ) d o e s n o t l o o k l i k e a m o d u l e . " {
_file = toString m . _file or file ;
2024-05-01 22:14:04 +00:00
_class = m . _class or null ;
key = toString m . key or key ;
2024-06-30 08:16:52 +00:00
disabledModules = m . disabledModules or [ ] ;
imports = m . require or [ ] ++ m . imports or [ ] ;
options = { } ;
config = addFreeformType (
removeAttrs m [
" _ c l a s s "
" _ 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 "
]
) ;
2024-05-01 22:14:04 +00:00
} ;
2024-06-30 08:16:52 +00:00
applyModuleArgsIfFunction =
key : f :
args @ {
config ,
options ,
lib ,
. . .
} :
2024-05-01 22:14:04 +00:00
if isFunction f then applyModuleArgs key f args else f ;
2024-06-30 08:16:52 +00:00
applyModuleArgs =
key : f :
args @ {
config ,
options ,
lib ,
. . .
} :
2024-05-01 22:14:04 +00:00
let
# Module arguments are resolved in a strict manner when attribute set
# deconstruction is used. As the arguments are now defined with the
# config._module.args option, the strictness used on the attribute
# 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.
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 } " : '' ;
2024-06-30 08:16:52 +00:00
extraArgs = builtins . mapAttrs (
name : _ : builtins . addErrorContext ( context name ) ( args . ${ name } or config . _module . args . ${ name } )
2024-05-01 22:14:04 +00:00
) ( lib . functionArgs f ) ;
2024-06-30 08:16:52 +00:00
in
# Note: we append in the opposite order such that we can add an error
# context on the explicit arguments of "args" too. This update
# operator is used to make the "args@{ ... }: with args.lib;" notation
# works.
f ( args // extraArgs ) ;
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +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
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 " ; }
. . .
] ;
}
* /
mergeModules =
prefix : modules :
mergeModules' prefix modules (
concatMap (
m :
map ( config : {
file = m . _file ;
inherit config ;
} ) ( pushDownProperties m . config )
) modules
) ;
mergeModules' =
prefix : modules : configs :
2024-05-01 22:14:04 +00:00
let
# an attrset 'name' => list of submodules that declare ‘ name’ .
2024-06-30 08:16:52 +00:00
declsByName = zipAttrsWith ( n : concatLists ) (
map (
module :
let
subtree = module . options ;
in
if ! ( builtins . isAttrs subtree ) then
throw ''
An option declaration for ` $ { builtins . concatStringsSep " . " prefix } ' has type
` $ { builtins . typeOf subtree } ' rather than an attribute set .
Did you mean to define this outside of ` options' ?
''
else
mapAttrs ( n : option : [
{
inherit ( module ) _file ;
pos = builtins . unsafeGetAttrPos n subtree ;
options = option ;
}
] ) subtree
) modules
) ;
2024-05-01 22:14:04 +00:00
# The root of any module definition must be an attrset.
checkedConfigs =
2024-06-30 08:16:52 +00:00
assert lib . all (
c :
# TODO: I have my doubts that this error would occur when option definitions are not matched.
# The implementation of this check used to be tied to a superficially similar check for
# options, so maybe that's why this is here.
isAttrs c . config
|| throw ''
In module ` $ { c . file } ' , you're trying to define a value of type ` $ { builtins . typeOf c . config } '
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' !
''
) configs ;
2024-05-01 22:14:04 +00:00
configs ;
# an attrset 'name' => list of submodules that define ‘ name’ .
2024-06-30 08:16:52 +00:00
pushedDownDefinitionsByName = zipAttrsWith ( n : concatLists ) (
map (
module :
mapAttrs (
n : value :
map ( config : {
inherit ( module ) file ;
inherit config ;
} ) ( pushDownProperties value )
) module . config
) checkedConfigs
) ;
2024-05-01 22:14:04 +00:00
# extract the definitions for each loc
2024-06-30 08:16:52 +00:00
rawDefinitionsByName = zipAttrsWith ( n : concatLists ) (
map (
module :
mapAttrs ( n : value : [
{
inherit ( module ) file ;
inherit value ;
}
] ) module . config
) checkedConfigs
) ;
2024-05-01 22:14:04 +00:00
# Convert an option tree decl to a submodule option decl
2024-06-30 08:16:52 +00:00
optionTreeToOption =
decl :
if isOption decl . options then
decl
else
decl
// {
2024-05-01 22:14:04 +00:00
options = mkOption {
type = types . submoduleWith {
modules = [ { options = decl . options ; } ] ;
# `null` is not intended for use by modules. It is an internal
# value that means "whatever the user has declared elsewhere".
# This might become obsolete with https://github.com/NixOS/nixpkgs/issues/162398
shorthandOnlyDefinesConfig = null ;
} ;
} ;
} ;
2024-06-30 08:16:52 +00:00
resultsByName = mapAttrs (
name : decls :
2024-05-01 22:14:04 +00:00
# We're descending into attribute ‘ name’ .
let
2024-06-30 08:16:52 +00:00
loc = prefix ++ [ name ] ;
defns = pushedDownDefinitionsByName . ${ name } or [ ] ;
defns' = rawDefinitionsByName . ${ name } or [ ] ;
optionDecls = filter (
m :
m . options ? _type
&& ( m . options . _type == " o p t i o n " || throwDeclarationTypeError loc m . options . _type m . _file )
) decls ;
2024-05-01 22:14:04 +00:00
in
2024-06-30 08:16:52 +00:00
if length optionDecls == length decls then
let
opt = fixupOptionType loc ( mergeOptionDecls loc decls ) ;
in
{
matchedOptions = evalOptionValue loc opt defns' ;
unmatchedDefns = [ ] ;
}
else if optionDecls != [ ] then
if
all ( x : x . options . type . name or null == " s u b m o d u l e " ) optionDecls
# Raw options can only be merged into submodules. Merging into
# attrsets might be nice, but ambiguous. Suppose we have
# attrset as a `attrsOf submodule`. User declares option
# attrset.foo.bar, this could mean:
# a. option `bar` is only available in `attrset.foo`
# b. option `foo.bar` is available in all `attrset.*`
# c. reject and require "<name>" as a reminder that it behaves like (b).
# d. magically combine (a) and (c).
# All of the above are merely syntax sugar though.
then
let
opt = fixupOptionType loc ( mergeOptionDecls loc ( map optionTreeToOption decls ) ) ;
in
{
2024-05-01 22:14:04 +00:00
matchedOptions = evalOptionValue loc opt defns' ;
2024-06-30 08:16:52 +00:00
unmatchedDefns = [ ] ;
2024-05-01 22:14:04 +00:00
}
else
2024-06-30 08:16:52 +00:00
let
nonOptions = filter ( m : ! isOption m . options ) decls ;
in
throw " T h e o p t i o n ` ${ showOption loc } ' i n m o d u l e ` ${ ( lib . head optionDecls ) . _file } ' w o u l d b e a p a r e n t o f t h e f o l l o w i n g o p t i o n s , b u t i t s t y p e ` ${
( lib . head optionDecls ) . options . type . description or " < n o d e s c r i p t i o n > "
} ' does not support nested options . \ n $ { showRawDecls loc nonOptions } "
else
mergeModules' loc decls defns
) declsByName ;
2024-05-01 22:14:04 +00:00
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 rawDefinitionsByName ( attrNames matchedOptions ) ;
2024-06-30 08:16:52 +00:00
in
{
2024-05-01 22:14:04 +00:00
inherit matchedOptions ;
# Transforms unmatchedDefnsByName into a list of definitions
unmatchedDefns =
2024-06-30 08:16:52 +00:00
if configs == [ ] then
2024-05-01 22:14:04 +00:00
# When no config values exist, there can be no unmatched config, so
# we short circuit and avoid evaluating more _options_ than necessary.
2024-06-30 08:16:52 +00:00
[ ]
2024-05-01 22:14:04 +00:00
else
2024-06-30 08:16:52 +00:00
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
) ;
2024-05-01 22:14:04 +00:00
} ;
2024-06-30 08:16:52 +00:00
throwDeclarationTypeError =
loc : actualTag : file :
2024-05-01 22:14:04 +00:00
let
name = lib . strings . escapeNixIdentifier ( lib . lists . last loc ) ;
path = showOption loc ;
depth = length loc ;
2024-06-30 08:16:52 +00:00
paragraphs =
[
" I n m o d u l e ${ file } : e x p e c t e d a n o p t i o n d e c l a r a t i o n a t o p t i o n p a t h ` ${ path } ` b u t g o t a n a t t r i b u t e s e t w i t h t y p e ${ actualTag } "
]
++ optional ( actualTag == " o p t i o n - t y p e " ) ''
2024-05-01 22:14:04 +00:00
When declaring an option , you must wrap the type in a ` mkOption ` call . It should look somewhat like :
$ { comment }
$ { name } = lib . mkOption {
description = . . . ;
type = < the type you wrote for $ { name } > ;
. . .
} ;
'' ;
# Ideally we'd know the exact syntax they used, but short of that,
# we can only reliably repeat the last. However, we repeat the
# full path in a non-misleading way here, in case they overlook
# the start of the message. Examples attract attention.
comment = optionalString ( depth > 1 ) " \n # ${ showOption loc } " ;
in
throw ( concatStringsSep " \n \n " paragraphs ) ;
2024-06-30 08:16:52 +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
( e . g . multiple modules define sub-options for ‘ fileSystems ’ ) .
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
' loc' is the list of attribute names where the option is located .
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
' opts' is a list of modules . Each module has an options attribute which
correspond to the definition of ' loc' in ' opt . file' .
* /
2024-05-01 22:14:04 +00:00
mergeOptionDecls =
2024-06-30 08:16:52 +00:00
loc : opts :
foldl'
(
res : opt :
let
t = res . type ;
2024-05-01 22:14:04 +00:00
t' = opt . options . type ;
mergedType = t . typeMerge t' . functor ;
typesMergeable = mergedType != null ;
2024-06-30 08:16:52 +00:00
typeSet = if ( bothHave " t y p e " ) && typesMergeable then { type = mergedType ; } else { } ;
2024-05-01 22:14:04 +00:00
bothHave = k : opt . options ? ${ k } && res ? ${ k } ;
2024-06-30 08:16:52 +00:00
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 ) )
then
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 } . "
else
let
getSubModules = opt . options . type . getSubModules or null ;
submodules =
if getSubModules != null then
map ( setDefaultModuleLocation opt . _file ) getSubModules ++ res . options
else
res . options ;
in
opt . options
// res
// {
declarations = res . declarations ++ [ opt . _file ] ;
2024-05-01 22:14:04 +00:00
# In the case of modules that are generated dynamically, we won't
# have exact declaration lines; fall back to just the file being
# evaluated.
2024-06-30 08:16:52 +00:00
declarationPositions =
res . declarationPositions
++ (
if opt . pos != null then
[ opt . pos ]
else
[
{
file = opt . _file ;
line = null ;
column = null ;
}
]
) ;
2024-05-01 22:14:04 +00:00
options = submodules ;
2024-06-30 08:16:52 +00:00
}
// typeSet
)
{
inherit loc ;
declarations = [ ] ;
declarationPositions = [ ] ;
options = [ ] ;
}
opts ;
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
/*
Merge all the definitions of an option to produce the final
config value .
* /
evalOptionValue =
loc : opt : defs :
2024-05-01 22:14:04 +00:00
let
# Add in the default value for this option, if any.
defs' =
2024-06-30 08:16:52 +00:00
( optional ( opt ? default ) {
file = head opt . declarations ;
value = mkOptionDefault opt . default ;
} )
++ defs ;
2024-05-01 22:14:04 +00:00
# Handle properties, check types, and merge everything together.
res =
if opt . readOnly or false && length defs' > 1 then
let
# For a better error message, evaluate all readOnly definitions as
# if they were the only definition.
2024-06-30 08:16:52 +00:00
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 } "
2024-05-01 22:14:04 +00:00
else
mergeDefinitions loc opt . type defs' ;
# Apply the 'apply' function to the merged value. This allows options to
# yield a value computed from the definitions
value = if opt ? apply then opt . apply res . mergedValue else res . mergedValue ;
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 } " ;
2024-06-30 08:16:52 +00:00
in
warnDeprecation opt
// {
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 ;
inherit ( res . defsFinal' ) highestPrio ;
definitions = map ( def : def . value ) res . defsFinal ;
files = map ( def : def . file ) res . defsFinal ;
definitionsWithLocations = res . defsFinal ;
inherit ( res ) isDefined ;
# This allows options to be correctly displayed using `${options.path.to.it}`
__toString = _ : showOption loc ;
} ;
2024-05-01 22:14:04 +00:00
# Merge definitions of a value of a given type.
mergeDefinitions = loc : type : defs : rec {
defsFinal' =
let
# Process mkMerge and mkIf properties.
2024-06-30 08:16:52 +00:00
defs' = concatMap (
m :
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
)
)
2024-05-01 22:14:04 +00:00
) defs ;
# Process mkOverride properties.
defs'' = filterOverrides' defs' ;
# Sort mkOrder properties.
defs''' =
# Avoid sorting if we don't have to.
2024-06-30 08:16:52 +00:00
if any ( def : def . value . _type or " " == " o r d e r " ) defs'' . values then
sortProperties defs'' . values
else
defs'' . values ;
in
{
2024-05-01 22:14:04 +00:00
values = defs''' ;
inherit ( defs'' ) highestPrio ;
} ;
defsFinal = defsFinal' . values ;
# Type-check the remaining definitions, and merge them. Or throw if no definitions.
mergedValue =
if isDefined then
2024-06-30 08:16:52 +00:00
if all ( def : type . check def . value ) defsFinal then
type . merge loc defsFinal
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 } "
2024-05-01 22:14:04 +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 . " ;
2024-06-30 08:16:52 +00:00
isDefined = defsFinal != [ ] ;
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
optionalValue = if isDefined then { value = mergedValue ; } else { } ;
2024-05-01 22:14:04 +00:00
} ;
2024-06-30 08:16:52 +00:00
/*
Given a config set , expand mkMerge properties , and push down the
other properties into the children . The result is a list of
config sets that do not have properties at top-level . For
example ,
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
mkMerge [ { boot = set1 ; } ( mkIf cond { boot = set2 ; services = set3 ; } ) ]
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
is transformed into
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
[ { boot = set1 ; } { boot = mkIf cond set2 ; services = mkIf cond set3 ; } ] .
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
This transform is the critical step that allows mkIf conditions
to refer to the full configuration without creating an infinite
recursion .
2024-05-01 22:14:04 +00:00
* /
2024-06-30 08:16:52 +00:00
pushDownProperties =
cfg :
2024-05-01 22:14:04 +00:00
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 )
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 )
2024-06-30 08:16:52 +00:00
# FIXME: handle mkOrder?
else
2024-05-01 22:14:04 +00:00
[ cfg ] ;
2024-06-30 08:16:52 +00:00
/*
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
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
mkMerge [ 1 ( mkIf true 2 ) ( mkIf true ( mkIf false 3 ) ) ]
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
yields ‘ [ 1 2 ] ’ .
2024-05-01 22:14:04 +00:00
* /
2024-06-30 08:16:52 +00:00
dischargeProperties =
def :
2024-05-01 22:14:04 +00:00
if def . _type or " " == " m e r g e " then
concatMap dischargeProperties def . contents
else if def . _type or " " == " i f " then
if isBool def . condition then
2024-06-30 08:16:52 +00:00
if def . condition then dischargeProperties def . content else [ ]
2024-05-01 22:14:04 +00:00
else
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 "
else
[ def ] ;
2024-06-30 08:16:52 +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
properties . For example ,
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
[ { file = " / 1 " ; value = mkOverride 10 " a " ; }
{ file = " / 2 " ; value = mkOverride 20 " b " ; }
{ file = " / 3 " ; value = " z " ; }
{ file = " / 4 " ; value = mkOverride 10 " d " ; }
]
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
yields
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
[ { file = " / 1 " ; value = " a " ; }
{ file = " / 4 " ; value = " d " ; }
]
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
Note that " z " has the default priority 100 .
2024-05-01 22:14:04 +00:00
* /
filterOverrides = defs : ( filterOverrides' defs ) . values ;
2024-06-30 08:16:52 +00:00
filterOverrides' =
defs :
2024-05-01 22:14:04 +00:00
let
2024-06-30 08:16:52 +00:00
getPrio =
def : if def . value . _type or " " == " o v e r r i d e " then def . value . priority else defaultOverridePriority ;
2024-05-01 22:14:04 +00:00
highestPrio = foldl' ( prio : def : min ( getPrio def ) prio ) 9999 defs ;
2024-06-30 08:16:52 +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 ;
in
{
values = concatMap ( def : if getPrio def == highestPrio then [ ( strip def ) ] else [ ] ) defs ;
2024-05-01 22:14:04 +00:00
inherit highestPrio ;
} ;
2024-06-30 08:16:52 +00:00
/*
Sort a list of properties . The sort priority of a property is
defaultOrderPriority by default , but can be overridden by wrapping the property
using mkOrder .
* /
sortProperties =
defs :
2024-05-01 22:14:04 +00:00
let
2024-06-30 08:16:52 +00:00
strip =
def :
if def . value . _type or " " == " o r d e r " then
def
// {
value = def . value . content ;
inherit ( def . value ) priority ;
}
else
def ;
2024-05-01 22:14:04 +00:00
defs' = map strip defs ;
compare = a : b : ( a . priority or defaultOrderPriority ) < ( b . priority or defaultOrderPriority ) ;
2024-06-30 08:16:52 +00:00
in
sort compare defs' ;
2024-05-01 22:14:04 +00:00
# This calls substSubModules, whose entire purpose is only to ensure that
# option declarations in submodules have accurate position information.
# TODO: Merge this into mergeOptionDecls
2024-06-30 08:16:52 +00:00
fixupOptionType =
loc : opt :
if opt . type . getSubModules or null == null then
opt // { type = opt . type or types . unspecified ; }
else
opt
// {
type = opt . type . substSubModules opt . options ;
options = [ ] ;
} ;
2024-05-01 22:14:04 +00:00
/*
Merge an option's definitions in a way that preserves the priority of the
individual attributes in the option value .
This does not account for all option semantics , such as readOnly .
Type :
option -> attrsOf { highestPrio , value }
* /
2024-06-30 08:16:52 +00:00
mergeAttrDefinitionsWithPrio =
opt :
let
defsByAttr = lib . zipAttrs (
lib . concatLists (
lib . concatMap (
{ value , . . . } @ def :
map ( lib . mapAttrsToList (
k : value : {
$ { k } = def // {
inherit value ;
} ;
}
) ) ( pushDownProperties value )
) opt . definitionsWithLocations
)
) ;
in
assert opt . type . name == " a t t r s O f " || opt . type . name == " l a z y A t t r s O f " ;
lib . mapAttrs (
k : v :
let
merging = lib . mergeDefinitions ( opt . loc ++ [ k ] ) opt . type . nestedTypes . elemType v ;
in
{
value = merging . mergedValue ;
inherit ( merging . defsFinal' ) highestPrio ;
}
) defsByAttr ;
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
# Properties.
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
mkIf = condition : content : {
_type = " i f " ;
inherit condition content ;
} ;
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
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 ;
} ;
2024-05-01 22:14:04 +00:00
mkOptionDefault = mkOverride 1500 ; # priority of option defaults
mkDefault = mkOverride 1000 ; # used in config sections of non-user modules to set a default
defaultOverridePriority = 100 ;
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
mkForce = mkOverride 50 ;
mkVMOverride = mkOverride 10 ; # used by ‘ nixos-rebuild build-vm’
2024-06-30 08:16:52 +00:00
defaultPriority =
lib . warnIf ( lib . isInOldestRelease 2305 )
" l i b . m o d u l e s . d e f a u l t P r i o r i t y i s d e p r e c a t e d , p l e a s e u s e l i b . m o d u l e s . d e f a u l t O v e r r i d e P r i o r i t y i n s t e a d . "
defaultOverridePriority ;
2024-05-01 22:14:04 +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 ;
2024-06-30 08:16:52 +00:00
mkOrder = priority : content : {
_type = " o r d e r " ;
inherit priority content ;
} ;
2024-05-01 22:14:04 +00:00
mkBefore = mkOrder 500 ;
defaultOrderPriority = 1000 ;
mkAfter = mkOrder 1500 ;
# 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 ;
2024-06-30 08:16:52 +00:00
mkAliasAndWrapDefinitions = wrap : option : mkAliasIfDef option ( wrap ( mkMerge option . definitions ) ) ;
2024-05-01 22:14:04 +00:00
# Similar to mkAliasAndWrapDefinitions but copies over the priority from the
# option as well.
#
# If a priority is not set, it assumes a priority of defaultOverridePriority.
2024-06-30 08:16:52 +00:00
mkAliasAndWrapDefsWithPriority =
wrap : option :
2024-05-01 22:14:04 +00:00
let
prio = option . highestPrio or defaultOverridePriority ;
defsWithPrio = map ( mkOverride prio ) option . definitions ;
2024-06-30 08:16:52 +00:00
in
mkAliasIfDef option ( wrap ( mkMerge defsWithPrio ) ) ;
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
mkAliasIfDef = option : mkIf ( isOption option && option . isDefined ) ;
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
# Compatibility.
fixMergeModules =
modules : args :
evalModules {
inherit modules args ;
check = false ;
} ;
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
/*
Return a module that causes a warning to be shown if the
specified option is defined . For example ,
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +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 > "
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
causes a assertion if the user defines boot . loader . grub . bootDevice .
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +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 !
2024-05-01 22:14:04 +00:00
* /
2024-06-30 08:16:52 +00:00
mkRemovedOptionModule =
optionName : replacementInstructions :
2024-05-01 22:14:04 +00:00
{ options , . . . }:
2024-06-30 08:16:52 +00:00
{
options = setAttrByPath optionName ( mkOption {
2024-05-01 22:14:04 +00:00
visible = false ;
2024-06-30 08:16:52 +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 } " ;
2024-05-01 22:14:04 +00:00
} ) ;
config . assertions =
2024-06-30 08:16:52 +00:00
let
opt = getAttrFromPath optionName options ;
in
[
{
assertion = ! opt . isDefined ;
message = ''
The option definition ` $ { showOption optionName } ' in $ { showFiles opt . files } no longer has any effect ; please remove it .
$ { replacementInstructions }
'' ;
}
] ;
2024-05-01 22:14:04 +00:00
} ;
2024-06-30 08:16:52 +00:00
/*
Return a module that causes a warning to be shown if the
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 ,
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
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 " ]
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
forwards any definitions of boot . copyKernels to
boot . loader . grub . copyKernels while printing a warning .
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
This also copies over the priority from the aliased option to the
non-aliased option .
2024-05-01 22:14:04 +00:00
* /
2024-06-30 08:16:52 +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 } ' . " ;
} ;
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
mkRenamedOptionModuleWith =
{
# Old option path as list of strings.
from ,
# New option path as list of strings.
to ,
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
/*
Release number of the first release that contains the rename , ignoring backports .
Set it to the upcoming release , matching the nixpkgs/.version file .
* /
sinceRelease ,
} :
doRename {
inherit from to ;
visible = false ;
warn = lib . isInOldestRelease sinceRelease ;
use = lib . warnIf ( lib . isInOldestRelease sinceRelease ) " 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 } ' . " ;
} ;
/*
Return a module that causes a warning to be shown if any of the " 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
x . y . z to the result of the merge function
2024-05-01 22:14:04 +00:00
* /
2024-06-30 08:16:52 +00:00
mkMergedOptionModule =
from : to : mergeFn :
2024-05-01 22:14:04 +00:00
{ config , options , . . . }:
{
2024-06-30 08:16:52 +00:00
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
) ;
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
config =
{
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
) ;
}
// setAttrByPath to (
mkMerge (
optional ( 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 )
)
) ;
} ;
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
/*
Single " f r o m " version of mkMergedOptionModule .
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 " )
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
- options . a . b . c is a removed int option
- options . x . y . z is a new str option that supersedes a . b . c
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
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
2024-05-01 22:14:04 +00:00
* /
2024-06-30 08:16:52 +00:00
mkChangedOptionModule =
from : to : changeFn :
2024-05-01 22:14:04 +00:00
mkMergedOptionModule [ from ] to changeFn ;
2024-06-30 08:16:52 +00:00
# Like ‘ mkRenamedOptionModule’ , but doesn't show a warning.
mkAliasOptionModule =
from : to :
doRename {
inherit from to ;
visible = true ;
warn = false ;
use = id ;
} ;
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
/*
Transitional version of mkAliasOptionModule that uses MD docs .
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
This function is no longer necessary and merely an alias of ` mkAliasOptionModule ` .
2024-05-01 22:14:04 +00:00
* /
mkAliasOptionModuleMD = mkAliasOptionModule ;
2024-06-30 08:16:52 +00:00
/*
mkDerivedConfig : Option a -> ( a -> Definition b ) -> Definition b
2024-05-01 22:14:04 +00:00
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.
2024-06-30 08:16:52 +00:00
mkDerivedConfig = opt : f : mkOverride ( opt . highestPrio or defaultOverridePriority ) ( f opt . value ) ;
2024-05-01 22:14:04 +00:00
/*
Return a module that help declares an option that has been renamed .
When a value is defined for the old option , it is forwarded to the ` to ` option .
2024-06-30 08:16:52 +00:00
* /
doRename =
{
# List of strings representing the attribute path of the old option.
from ,
# List of strings representing the attribute path of the new option.
to ,
# Boolean, whether the old option is to be included in documentation.
visible ,
# Whether to warn when a value is defined for the old option.
# NOTE: This requires the NixOS assertions module to be imported, so
# - this generally does not work in submodules
# - this may or may not work outside NixOS
warn ,
# A function that is applied to the option value, to form the value
# of the old `from` option.
#
# For example, the identity function can be passed, to return the option value unchanged.
# ```nix
# use = x: x;
# ```
#
# To add a warning, you can pass the partially applied `warn` function.
# ```nix
# use = lib.warn "Obsolete option `${opt.old}' is used. Use `${opt.to}' instead.";
# ```
use ,
# Legacy option, enabled by default: whether to preserve the priority of definitions in `old`.
withPriority ? true ,
# A boolean that defines the `mkIf` condition for `to`.
# If the condition evaluates to `true`, and the `to` path points into an
# `attrsOf (submodule ...)`, then `doRename` would cause an empty module to
# be created, even if the `from` option is undefined.
# By setting this to an expression that may return `false`, you can inhibit
# this undesired behavior.
#
# Example:
#
# ```nix
# { config, lib, ... }:
# let
# inherit (lib) mkOption mkEnableOption types doRename;
# in
# {
# options = {
#
# # Old service
# services.foo.enable = mkEnableOption "foo";
#
# # New multi-instance service
# services.foos = mkOption {
# type = types.attrsOf (types.submodule …);
# };
# };
# imports = [
# (doRename {
# from = [ "services" "foo" "bar" ];
# to = [ "services" "foos" "" "bar" ];
# visible = true;
# warn = false;
# use = x: x;
# withPriority = true;
# # Only define services.foos."" if needed. (It's not just about `bar`)
# condition = config.services.foo.enable;
# })
# ];
# }
# ```
condition ? true ,
} :
2024-05-01 22:14:04 +00:00
{ config , options , . . . }:
let
fromOpt = getAttrFromPath from options ;
2024-06-30 08:16:52 +00:00
toOf = attrByPath to ( 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 . " ) ;
toType =
let
opt = attrByPath to { } options ;
in
opt . type or ( types . submodule { } ) ;
2024-05-01 22:14:04 +00:00
in
{
2024-06-30 08:16:52 +00:00
options = setAttrByPath from (
mkOption {
inherit visible ;
description = " A l i a s o f { o p t i o n } ` ${ showOption to } ` . " ;
apply = x : use ( toOf config ) ;
}
// optionalAttrs ( toType != null ) { type = toType ; }
) ;
2024-05-01 22:14:04 +00:00
config = mkIf condition ( mkMerge [
( optionalAttrs ( options ? warnings ) {
2024-06-30 08:16:52 +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 } ' . " ;
2024-05-01 22:14:04 +00:00
} )
2024-06-30 08:16:52 +00:00
(
if withPriority then
mkAliasAndWrapDefsWithPriority ( setAttrByPath to ) fromOpt
else
mkAliasAndWrapDefinitions ( setAttrByPath to ) fromOpt
)
2024-05-01 22:14:04 +00:00
] ) ;
} ;
2024-06-30 08:16:52 +00:00
/*
Use this function to import a JSON file as NixOS configuration .
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
modules . importJSON : : path -> attrs
2024-05-01 22:14:04 +00:00
* /
importJSON = file : {
_file = file ;
config = lib . importJSON file ;
} ;
2024-06-30 08:16:52 +00:00
/*
Use this function to import a TOML file as NixOS configuration .
2024-05-01 22:14:04 +00:00
2024-06-30 08:16:52 +00:00
modules . importTOML : : path -> attrs
2024-05-01 22:14:04 +00:00
* /
importTOML = file : {
_file = file ;
config = lib . importTOML file ;
} ;
2024-06-30 08:16:52 +00:00
private =
lib . mapAttrs
(
k :
lib . warn " E x t e r n a l u s e o f ` l i b . m o d u l e s . ${ k } ` i s d e p r e c a t e d . I f y o u r u s e c a s e i s n ' t c o v e r e d b y n o n - d e p r e c a t e d f u n c t i o n s , w e ' d l i k e t o k n o w m o r e a n d p e r h a p s s u p p o r t y o u r u s e c a s e w e l l , i n s t e a d o f p r o v i d i n g a c c e s s t o t h e s e l o w l e v e l f u n c t i o n s . I n t h i s c a s e p l e a s e o p e n a n i s s u e i n h t t p s : / / g i t h u b . c o m / n i x o s / n i x p k g s / i s s u e s / . "
)
{
inherit
applyModuleArgsIfFunction
dischargeProperties
mergeModules
mergeModules'
pushDownProperties
unifyModuleSyntax
;
collectModules = collectModules null ;
} ;
2024-05-01 22:14:04 +00:00
in
2024-06-30 08:16:52 +00:00
private
// {
2024-05-01 22:14:04 +00:00
# NOTE: not all of these functions are necessarily public interfaces; some
# are just needed by types.nix, but are not meant to be consumed
# externally.
inherit
defaultOrderPriority
defaultOverridePriority
defaultPriority
doRename
evalModules
2024-06-30 08:16:52 +00:00
evalOptionValue # for use by lib.types
2024-05-01 22:14:04 +00:00
filterOverrides
filterOverrides'
fixMergeModules
2024-06-30 08:16:52 +00:00
fixupOptionType # should be private?
2024-05-01 22:14:04 +00:00
importJSON
importTOML
mergeDefinitions
mergeAttrDefinitionsWithPrio
2024-06-30 08:16:52 +00:00
mergeOptionDecls # should be private?
2024-05-01 22:14:04 +00:00
mkAfter
mkAliasAndWrapDefinitions
mkAliasAndWrapDefsWithPriority
mkAliasDefinitions
mkAliasIfDef
mkAliasOptionModule
mkAliasOptionModuleMD
mkAssert
mkBefore
mkChangedOptionModule
mkDefault
mkDerivedConfig
mkFixStrictness
mkForce
mkIf
mkImageMediaOverride
mkMerge
mkMergedOptionModule
mkOptionDefault
mkOrder
mkOverride
mkRemovedOptionModule
mkRenamedOptionModule
mkRenamedOptionModuleWith
mkVMOverride
setDefaultModuleLocation
2024-06-30 08:16:52 +00:00
sortProperties
;
2024-05-01 22:14:04 +00:00
}