2020-04-24 23:36:52 +00:00
{ lib }:
2020-10-27 00:29:36 +00:00
let
inherit ( lib )
all
any
attrByPath
attrNames
catAttrs
concatLists
concatMap
2022-03-30 09:31:56 +00:00
concatStringsSep
2020-10-27 00:29:36 +00:00
elem
filter
foldl'
getAttrFromPath
head
id
imap1
isAttrs
isBool
isFunction
2021-05-20 23:08:51 +00:00
isList
2020-10-27 00:29:36 +00:00
isString
length
mapAttrs
mapAttrsToList
mapAttrsRecursiveCond
min
optional
optionalAttrs
optionalString
recursiveUpdate
reverseList sort
setAttrByPath
types
2021-05-03 20:48:10 +00:00
warnIf
2022-01-13 20:06:32 +00:00
zipAttrsWith
2020-10-27 00:29:36 +00:00
;
inherit ( lib . options )
isOption
mkOption
showDefs
showFiles
showOption
unknownModule
;
2023-03-15 16:39:30 +00:00
inherit ( lib . strings )
isConvertibleWithToString
;
2022-03-30 09:31:56 +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
) ) ;
2023-05-24 13:37:59 +00:00
/* S e e h t t p s : / / n i x o s . o r g / m a n u a l / n i x p k g s / u n s t a b l e / # m o d u l e - s y s t e m - l i b - e v a l M o d u l e s
or file://./../doc/module-system/module-system.chapter.md
2021-12-06 16:07:01 +00:00
2020-04-24 23:36:52 +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-12-06 16:07:01 +00:00
evalModules = evalModulesArgs @
{ modules
2020-04-24 23:36:52 +00:00
, 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 ? { }
2023-05-24 13:37:59 +00:00
, # `class`:
# A nominal type for modules. When set and non-null, this adds a check to
# make sure that only compatible modules are imported.
# This would be remove in the future, Prefer _module.args option instead.
class ? null
, args ? { }
2020-04-24 23:36:52 +00:00
, # This would be remove in the future, Prefer _module.check option instead.
check ? true
} :
let
2021-12-19 01:06:50 +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 ;
legacyModules =
optional ( evalModulesArgs ? args ) {
config = {
_module . args = args ;
} ;
}
++ optional ( evalModulesArgs ? check ) {
config = {
_module . check = mkDefault check ;
} ;
} ;
regularModules = modules ++ legacyModules ;
2020-04-24 23:36:52 +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-19 01:06:50 +00:00
#
# When extended with extendModules or moduleType, a fresh instance of
# this module is used, to avoid conflicts and allow chaining of
# extendModules.
2020-04-24 23:36:52 +00:00
internalModule = rec {
2022-04-15 01:41:22 +00:00
_file = " l i b / m o d u l e s . n i x " ;
2020-04-24 23:36:52 +00:00
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.
2022-03-05 16:20:37 +00:00
type = types . lazyAttrsOf types . raw ;
2022-04-15 01:41:22 +00:00
# Only render documentation once at the root of the option tree,
# not for all individual submodules.
2022-04-27 09:35:20 +00:00
# Allow merging option decls to make this internal regardless.
$ { if prefix == [ ]
then null # unset => visible
else " i n t e r n a l " } = true ;
2022-04-15 01:41:22 +00:00
# TODO: Change the type of this option to a submodule with a
# freeformType, so that individual arguments can be documented
# separately
2022-09-09 14:08:57 +00:00
description = lib . mdDoc ''
2022-04-15 01:41:22 +00:00
Additional arguments passed to each module in addition to ones
2022-09-09 14:08:57 +00:00
like ` lib ` , ` config ` ,
and ` pkgs ` , ` modulesPath ` .
2022-04-15 01:41:22 +00:00
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
2022-09-09 14:08:57 +00:00
this is the argument ` name ` which is provided by
2022-04-15 01:41:22 +00:00
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 .
2022-09-09 14:08:57 +00:00
2022-04-15 01:41:22 +00:00
Some arguments are already passed by default , of which the
2022-09-09 14:08:57 +00:00
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 " )
] ;
}
` ` `
2022-04-15 01:41:22 +00:00
For NixOS , the default value for this option includes at least this argument :
2022-09-09 14:08:57 +00:00
- { var } ` pkgs ` : The nixpkgs package set according to
the { option } ` nixpkgs . pkgs ` option .
2022-04-15 01:41:22 +00:00
'' ;
2020-04-24 23:36:52 +00:00
} ;
_module . check = mkOption {
type = types . bool ;
internal = true ;
2021-12-19 01:06:50 +00:00
default = true ;
2022-09-09 14:08:57 +00:00
description = lib . mdDoc " 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 . " ;
2020-04-24 23:36:52 +00:00
} ;
2020-08-20 17:08:02 +00:00
_module . freeformType = mkOption {
2022-03-05 16:20:37 +00:00
type = types . nullOr types . optionType ;
2020-08-20 17:08:02 +00:00
internal = true ;
default = null ;
2022-09-09 14:08:57 +00:00
description = lib . mdDoc ''
2020-08-20 17:08:02 +00:00
If set , merge all definitions that don't have an associated option
together using this type . The result then gets combined with the
2022-09-09 14:08:57 +00:00
values of all declared options to produce the final `
config ` value .
2020-08-20 17:08:02 +00:00
2022-09-09 14:08:57 +00:00
If this is ` null ` , definitions without an option
will throw an error unless { option } ` _module . check ` is
2020-08-20 17:08:02 +00:00
turned off .
'' ;
} ;
2022-08-12 12:06:08 +00:00
_module . specialArgs = mkOption {
readOnly = true ;
internal = true ;
2022-09-09 14:08:57 +00:00
description = lib . mdDoc ''
2022-08-12 12:06:08 +00:00
Externally provided module arguments that can't be modified from
within a configuration , but can be used in module imports .
'' ;
} ;
2020-04-24 23:36:52 +00:00
} ;
config = {
2021-12-06 16:07:01 +00:00
_module . args = {
inherit extendModules ;
moduleType = type ;
2021-12-19 01:06:50 +00:00
} ;
2022-08-12 12:06:08 +00:00
_module . specialArgs = specialArgs ;
2020-04-24 23:36:52 +00:00
} ;
} ;
2020-08-20 17:08:02 +00:00
merged =
let collected = collectModules
2023-05-24 13:37:59 +00:00
class
2020-08-20 17:08:02 +00:00
( specialArgs . modulesPath or " " )
2021-12-19 01:06:50 +00:00
( regularModules ++ [ internalModule ] )
2021-05-20 23:08:51 +00:00
( { inherit lib options config specialArgs ; } // specialArgs ) ;
2020-08-20 17:08:02 +00:00
in mergeModules prefix ( reverseList collected ) ;
options = merged . matchedOptions ;
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 ;
in if defs == [ ] then { }
else declaredConfig . _module . freeformType . merge prefix defs ;
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 ;
checkUnmatched =
if config . _module . check && config . _module . freeformType == null && merged . unmatchedDefns != [ ] then
let
firstDef = head merged . unmatchedDefns ;
2022-12-17 10:02:37 +00:00
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 "
( 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 ] )
) ;
in
" 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 } " ;
2020-08-20 17:08:02 +00:00
in
if attrNames options == [ " _ m o d u l e " ]
2021-10-01 09:20:50 +00:00
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 }
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-20 17:08:02 +00:00
else throw baseMsg
else null ;
2021-12-06 16:07:01 +00:00
checked = builtins . seq checkUnmatched ;
extendModules = extendArgs @ {
modules ? [ ] ,
specialArgs ? { } ,
prefix ? [ ] ,
} :
evalModules ( evalModulesArgs // {
2023-05-24 13:37:59 +00:00
inherit class ;
2021-12-19 01:06:50 +00:00
modules = regularModules ++ modules ;
2021-12-06 16:07:01 +00:00
specialArgs = evalModulesArgs . specialArgs or { } // specialArgs ;
2022-06-16 17:23:12 +00:00
prefix = extendArgs . prefix or evalModulesArgs . prefix or [ ] ;
2021-12-06 16:07:01 +00:00
} ) ;
type = lib . types . submoduleWith {
2023-05-24 13:37:59 +00:00
inherit modules specialArgs class ;
2021-12-06 16:07:01 +00:00
} ;
2021-12-19 01:06:50 +00:00
result = withWarnings {
2023-05-24 13:37:59 +00:00
_type = " c o n f i g u r a t i o n " ;
2021-12-06 16:07:01 +00:00
options = checked options ;
config = checked ( removeAttrs config [ " _ m o d u l e " ] ) ;
_module = checked ( config . _module ) ;
inherit extendModules type ;
2023-05-24 13:37:59 +00:00
class = class ;
2020-04-24 23:36:52 +00:00
} ;
in result ;
2023-05-24 13:37:59 +00:00
# collectModules :: (class: String) -> (modulesPath: String) -> (modules: [ Module ]) -> (args: Attrs) -> [ Module ]
2020-04-24 23:36:52 +00:00
#
# Collects all modules recursively through `import` statements, filtering out
# all modules in disabledModules.
2023-05-24 13:37:59 +00:00
collectModules = class : let
2020-04-24 23:36:52 +00:00
# Like unifyModuleSyntax, but also imports paths and calls functions if necessary
loadModule = args : fallbackFile : fallbackKey : m :
2023-05-24 13:37:59 +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 } . "
+ 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.
)
2021-05-20 23:08:51 +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 } "
2022-03-30 09:31:56 +00:00
else unifyModuleSyntax ( toString m ) ( toString m ) ( applyModuleArgsIfFunction ( toString m ) ( import m ) args ) ;
2020-04-24 23:36:52 +00:00
2023-05-24 13:37:59 +00:00
checkModule =
if class != null
then
m :
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 } . "
else
m : m ;
2020-04-24 23:36: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 = [ . . . ] ;
}
. . .
] ;
}
. . .
] ;
}
* /
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
2023-05-24 13:37:59 +00:00
module = checkModule ( loadModule args parentFile " ${ parentKey } : a n o n - ${ toString n } " x ) ;
2020-04-24 23:36:52 +00:00
collectedImports = collectStructuredModules module . _file module . key module . imports args ;
in {
key = module . key ;
module = module ;
modules = collectedImports . modules ;
2023-03-15 16:39:30 +00:00
disabled = ( if module . disabledModules != [ ] then [ { file = module . _file ; disabled = module . disabledModules ; } ] else [ ] ) ++ collectedImports . disabled ;
2020-04-24 23:36:52 +00:00
} ) 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
2023-03-15 16:39:30 +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
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
else if m ? key
then
m . key
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 } . " ;
disabledKeys = concatMap ( { file , disabled }: map ( moduleKey file ) disabled ) disabled ;
2020-04-24 23:36: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 ) ;
2022-02-10 20:34:41 +00:00
/* W r a p a m o d u l e w i t h a d e f a u l t l o c a t i o n f o r r e p o r t i n g e r r o r s . */
setDefaultModuleLocation = file : m :
{ _file = file ; imports = [ m ] ; } ;
2020-04-24 23:36:52 +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 . * /
unifyModuleSyntax = file : key : m :
2020-08-20 17:08:02 +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 ;
2020-04-24 23:36:52 +00:00
in
if m ? config || m ? options then
2023-05-24 13:37:59 +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
2020-04-24 23:36:52 +00:00
if badAttrs != { } then
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
2020-12-03 08:41:04 +00:00
{ _file = toString m . _file or file ;
2023-05-24 13:37:59 +00:00
_class = m . _class or null ;
2020-04-24 23:36:52 +00:00
key = toString m . key or key ;
disabledModules = m . disabledModules or [ ] ;
imports = m . imports or [ ] ;
options = m . options or { } ;
2020-08-20 17:08:02 +00:00
config = addFreeformType ( addMeta ( m . config or { } ) ) ;
2020-04-24 23:36:52 +00:00
}
else
2022-09-30 11:47:45 +00:00
# shorthand syntax
2022-06-16 17:23:12 +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 . "
2020-12-03 08:41:04 +00:00
{ _file = toString m . _file or file ;
2023-05-24 13:37:59 +00:00
_class = m . _class or null ;
2020-04-24 23:36:52 +00:00
key = toString m . key or key ;
disabledModules = m . disabledModules or [ ] ;
imports = m . require or [ ] ++ m . imports or [ ] ;
options = { } ;
2023-05-24 13:37:59 +00:00
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 " ] ) ;
2020-04-24 23:36:52 +00:00
} ;
2023-05-24 13:37:59 +00:00
applyModuleArgsIfFunction = key : f : args @ { config , options , lib , . . . }:
if isFunction f then applyModuleArgs key f args else f ;
applyModuleArgs = key : f : args @ { config , options , lib , . . . }:
2020-04-24 23:36:52 +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 } " : '' ;
2021-05-20 23:08:51 +00:00
extraArgs = builtins . mapAttrs ( name : _ :
builtins . addErrorContext ( context name )
( args . ${ name } or config . _module . args . ${ name } )
) ( lib . functionArgs f ) ;
2020-04-24 23:36:52 +00:00
# Note: we append in the opposite order such that we can add an error
2022-12-28 21:21:41 +00:00
# context on the explicit arguments of "args" too. This update
2020-04-24 23:36:52 +00:00
# operator is used to make the "args@{ ... }: with args.lib;" notation
# works.
2023-05-24 13:37:59 +00:00
in f ( args // extraArgs ) ;
2020-04-24 23:36:52 +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-08-20 17:08:02 +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 " ; }
. . .
] ;
}
* /
2020-04-24 23:36:52 +00:00
mergeModules = prefix : modules :
mergeModules' prefix modules
( concatMap ( m : map ( config : { file = m . _file ; inherit config ; } ) ( pushDownProperties m . config ) ) modules ) ;
mergeModules' = prefix : options : configs :
let
2023-07-15 17:15:38 +00:00
# an attrset 'name' => list of submodules that declare ‘ name’ .
declsByName =
zipAttrsWith
( n : concatLists )
( map
( module : let subtree = module . options ; in
2022-01-13 20:06:32 +00:00
if ! ( builtins . isAttrs subtree ) then
2023-07-15 17:15:38 +00:00
throw ''
2023-04-12 12:48:02 +00:00
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' ?
2023-07-15 17:15:38 +00:00
''
2021-03-20 04:20:00 +00:00
else
2023-07-15 17:15:38 +00:00
mapAttrs
( n : option :
[ { inherit ( module ) _file ; options = option ; } ]
)
subtree
)
options ) ;
# The root of any module definition must be an attrset.
checkedConfigs =
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 ;
configs ;
2020-04-24 23:36:52 +00:00
# an attrset 'name' => list of submodules that define ‘ name’ .
2023-07-15 17:15:38 +00:00
pushedDownDefinitionsByName =
zipAttrsWith
( n : concatLists )
( map
( module :
mapAttrs
( n : value :
map ( config : { inherit ( module ) file ; inherit config ; } ) ( pushDownProperties value )
)
module . config
)
checkedConfigs ) ;
2020-04-24 23:36:52 +00:00
# extract the definitions for each loc
2023-07-15 17:15:38 +00:00
rawDefinitionsByName =
zipAttrsWith
( n : concatLists )
( map
( module :
mapAttrs
( n : value :
[ { inherit ( module ) file ; inherit value ; } ]
)
module . config
)
checkedConfigs ) ;
2020-08-20 17:08:02 +00:00
2022-03-30 09:31:56 +00:00
# Convert an option tree decl to a submodule option decl
optionTreeToOption = decl :
if isOption decl . options
then decl
else decl // {
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 ;
} ;
} ;
} ;
2021-12-19 01:06:50 +00:00
resultsByName = mapAttrs ( name : decls :
2020-08-20 17:08:02 +00:00
# We're descending into attribute ‘ name’ .
2020-04-24 23:36:52 +00:00
let
loc = prefix ++ [ name ] ;
2023-07-15 17:15:38 +00:00
defns = pushedDownDefinitionsByName . ${ name } or [ ] ;
defns' = rawDefinitionsByName . ${ name } or [ ] ;
2023-08-22 20:05:09 +00:00
optionDecls = filter
( m : m . options ? _type
&& ( m . options . _type == " o p t i o n "
|| throwDeclarationTypeError loc m . options . _type m . _file
)
)
decls ;
2020-04-24 23:36:52 +00:00
in
2022-03-30 09:31:56 +00:00
if length optionDecls == length decls then
2020-04-24 23:36:52 +00:00
let opt = fixupOptionType loc ( mergeOptionDecls loc decls ) ;
2020-08-20 17:08:02 +00:00
in {
matchedOptions = evalOptionValue loc opt defns' ;
unmatchedDefns = [ ] ;
}
2022-03-30 09:31:56 +00:00
else if optionDecls != [ ] then
2023-08-04 22:07:22 +00:00
if all ( x : x . options . type . name or null == " s u b m o d u l e " ) optionDecls
2022-03-30 09:31:56 +00:00
# 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 {
matchedOptions = evalOptionValue loc opt defns' ;
unmatchedDefns = [ ] ;
}
else
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 > " } ' d o e s n o t s u p p o r t n e s t e d o p t i o n s . \n ${
showRawDecls loc nonOptions
} "
2020-04-24 23:36:52 +00:00
else
2021-12-19 01:06:50 +00:00
mergeModules' loc decls defns ) declsByName ;
2020-08-20 17:08:02 +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
2023-07-15 17:15:38 +00:00
// removeAttrs rawDefinitionsByName ( attrNames matchedOptions ) ;
2020-08-20 17:08:02 +00:00
in {
inherit matchedOptions ;
# Transforms unmatchedDefnsByName into a list of definitions
2021-12-19 01:06:50 +00:00
unmatchedDefns =
if configs == [ ]
then
# When no config values exist, there can be no unmatched config, so
# we short circuit and avoid evaluating more _options_ than necessary.
[ ]
else
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 ) ;
2020-08-20 17:08:02 +00:00
} ;
2020-04-24 23:36:52 +00:00
2023-08-22 20:05:09 +00:00
throwDeclarationTypeError = loc : actualTag : file :
let
name = lib . strings . escapeNixIdentifier ( lib . lists . last loc ) ;
path = showOption loc ;
depth = length loc ;
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 " ) ''
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 ) ;
2020-04-24 23:36:52 +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
( 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' . * /
mergeOptionDecls =
2022-10-06 18:32:54 +00:00
loc : opts :
2020-04-24 23:36:52 +00:00
foldl' ( res : opt :
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 ) )
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 =
2022-02-10 20:34:41 +00:00
if getSubModules != null then map ( setDefaultModuleLocation opt . _file ) getSubModules ++ res . options
2020-04-24 23:36:52 +00:00
else res . options ;
in opt . options // res //
{ declarations = res . declarations ++ [ opt . _file ] ;
options = submodules ;
} // typeSet
) { inherit loc ; declarations = [ ] ; options = [ ] ; } opts ;
/* 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 . * /
evalOptionValue = loc : opt : defs :
let
# Add in the default value for this option, if any.
defs' =
( optional ( opt ? default )
{ file = head opt . declarations ; value = mkOptionDefault opt . default ; } ) ++ defs ;
# Handle properties, check types, and merge everything together.
res =
if opt . readOnly or false && length defs' > 1 then
2020-09-25 04:45:31 +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 } "
2020-04-24 23:36:52 +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 ;
2020-09-25 04:45:31 +00:00
warnDeprecation =
2021-05-03 20:48:10 +00:00
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-25 04:45:31 +00:00
in warnDeprecation opt //
2020-04-24 23:36:52 +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 ;
inherit ( res . defsFinal' ) highestPrio ;
definitions = map ( def : def . value ) res . defsFinal ;
files = map ( def : def . file ) res . defsFinal ;
2022-09-30 11:47:45 +00:00
definitionsWithLocations = res . defsFinal ;
2020-04-24 23:36:52 +00:00
inherit ( res ) isDefined ;
2021-12-19 01:06:50 +00:00
# This allows options to be correctly displayed using `${options.path.to.it}`
__toString = _ : showOption loc ;
2020-04-24 23:36:52 +00:00
} ;
# Merge definitions of a value of a given type.
mergeDefinitions = loc : type : defs : rec {
defsFinal' =
let
# Process mkMerge and mkIf properties.
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 ) )
) defs ;
# Process mkOverride properties.
defs'' = filterOverrides' defs' ;
# Sort mkOrder properties.
defs''' =
# Avoid sorting if we don't have to.
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 ;
# Type-check the remaining definitions, and merge them. Or throw if no definitions.
mergedValue =
if isDefined then
if all ( def : type . check def . value ) defsFinal then type . merge loc defsFinal
2020-09-25 04:45:31 +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 } "
2020-04-24 23:36:52 +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 . " ;
isDefined = defsFinal != [ ] ;
optionalValue =
if isDefined then { value = mergedValue ; }
else { } ;
} ;
/* 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
other properties into the children . The result is a list of
config sets that do not have properties at top-level . For
example ,
mkMerge [ { boot = set1 ; } ( mkIf cond { boot = set2 ; services = set3 ; } ) ]
is transformed into
[ { boot = set1 ; } { boot = mkIf cond set2 ; services = mkIf cond set3 ; } ] .
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 )
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 )
else # FIXME: handle mkOrder?
[ 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
if isBool def . condition then
if def . condition then
dischargeProperties def . content
else
[ ]
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 ] ;
/* 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
properties . For example ,
[ { file = " / 1 " ; value = mkOverride 10 " a " ; }
{ file = " / 2 " ; value = mkOverride 20 " b " ; }
{ file = " / 3 " ; value = " z " ; }
{ file = " / 4 " ; value = mkOverride 10 " d " ; }
]
yields
[ { file = " / 1 " ; value = " a " ; }
{ file = " / 4 " ; value = " d " ; }
]
Note that " z " has the default priority 100 .
* /
filterOverrides = defs : ( filterOverrides' defs ) . values ;
filterOverrides' = defs :
let
2022-12-17 10:02:37 +00:00
getPrio = def : if def . value . _type or " " == " o v e r r i d e " then def . value . priority else defaultOverridePriority ;
2020-04-24 23:36:52 +00:00
highestPrio = foldl' ( prio : def : min ( getPrio def ) prio ) 9999 defs ;
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 ;
inherit highestPrio ;
} ;
/* 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
2022-12-17 10:02:37 +00:00
defaultOrderPriority by default , but can be overridden by wrapping the property
2020-04-24 23:36:52 +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 ;
2022-12-17 10:02:37 +00:00
compare = a : b : ( a . priority or defaultOrderPriority ) < ( b . priority or defaultOrderPriority ) ;
2020-04-24 23:36:52 +00:00
in sort compare defs' ;
2022-03-30 09:31:56 +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
2020-04-24 23:36:52 +00:00
fixupOptionType = loc : opt :
2022-03-30 09:31:56 +00:00
if opt . type . getSubModules or null == null
then opt // { type = opt . type or types . unspecified ; }
else opt // { type = opt . type . substSubModules opt . options ; options = [ ] ; } ;
2020-04-24 23:36:52 +00:00
2023-07-15 17:15:38 +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 }
* /
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 ;
2020-04-24 23:36:52 +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 ;
} ;
mkOptionDefault = mkOverride 1500 ; # priority of option defaults
mkDefault = mkOverride 1000 ; # used in config sections of non-user modules to set a default
2022-12-17 10:02:37 +00:00
defaultOverridePriority = 100 ;
2021-08-18 13:19:15 +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
2020-04-24 23:36:52 +00:00
mkForce = mkOverride 50 ;
mkVMOverride = mkOverride 10 ; # used by ‘ nixos-rebuild build-vm’
2022-12-17 10:02:37 +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 ;
2021-07-14 22:03: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 ;
2020-04-24 23:36:52 +00:00
mkOrder = priority : content :
{ _type = " o r d e r " ;
inherit priority content ;
} ;
mkBefore = mkOrder 500 ;
2022-12-17 10:02:37 +00:00
defaultOrderPriority = 1000 ;
2020-04-24 23:36:52 +00:00
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 ;
mkAliasAndWrapDefinitions = wrap : option :
mkAliasIfDef option ( wrap ( mkMerge option . definitions ) ) ;
# Similar to mkAliasAndWrapDefinitions but copies over the priority from the
# option as well.
#
2022-12-17 10:02:37 +00:00
# If a priority is not set, it assumes a priority of defaultOverridePriority.
2020-04-24 23:36:52 +00:00
mkAliasAndWrapDefsWithPriority = wrap : option :
let
2022-12-17 10:02:37 +00:00
prio = option . highestPrio or defaultOverridePriority ;
2020-04-24 23:36:52 +00:00
defsWithPrio = map ( mkOverride prio ) option . definitions ;
in mkAliasIfDef option ( wrap ( mkMerge defsWithPrio ) ) ;
mkAliasIfDef = option :
mkIf ( isOption option && option . isDefined ) ;
/* C o m p a t i b i l i t y . */
fixMergeModules = modules : args : evalModules { inherit modules args ; check = false ; } ;
/* 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 ,
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 > "
2020-10-27 00:29:36 +00:00
causes a assertion if the user defines boot . loader . grub . bootDevice .
2020-04-24 23:36: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 !
* /
mkRemovedOptionModule = optionName : replacementInstructions :
{ options , . . . }:
{ options = setAttrByPath optionName ( mkOption {
visible = false ;
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 } " ;
} ) ;
config . assertions =
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 }
'' ;
} ] ;
} ;
/* 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 .
This also copies over the priority from the aliased option to the
non-aliased option .
* /
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 } ' . " ;
} ;
2022-03-30 09:31:56 +00:00
mkRenamedOptionModuleWith = {
/* O l d o p t i o n p a t h a s l i s t o f s t r i n g s . */
from ,
/* N e w o p t i o n p a t h a s l i s t o f s t r i n g s . */
to ,
/*
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 } ' . " ;
} ;
2020-04-24 23:36: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
x . y . z to the result of the merge function
* /
mkMergedOptionModule = from : to : mergeFn :
{ config , options , . . . }:
{
2021-12-19 01:06:50 +00:00
options = foldl' recursiveUpdate { } ( map ( path : setAttrByPath path ( mkOption {
2020-04-24 23:36:52 +00:00
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 = {
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 ) ) ) ;
} ;
/* 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 ;
/* 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 ;
} ;
2023-07-15 17:15:38 +00:00
/* T r a n s i t i o n a l v e r s i o n o f m k A l i a s O p t i o n M o d u l e t h a t u s e s M D d o c s .
This function is no longer necessary and merely an alias of ` mkAliasOptionModule ` .
* /
mkAliasOptionModuleMD = mkAliasOptionModule ;
2023-01-11 07:51:40 +00:00
2021-12-06 16:07:01 +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
2022-12-17 10:02:37 +00:00
( opt . highestPrio or defaultOverridePriority )
2021-12-06 16:07:01 +00:00
( f opt . value ) ;
2023-07-15 17:15:38 +00:00
doRename = { from , to , visible , warn , use , withPriority ? true }:
2020-04-24 23:36:52 +00:00
{ config , options , . . . }:
let
fromOpt = getAttrFromPath from options ;
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 . " ) ;
2021-02-05 17:12:51 +00:00
toType = let opt = attrByPath to { } options ; in opt . type or ( types . submodule { } ) ;
2020-04-24 23:36:52 +00:00
in
{
options = setAttrByPath from ( mkOption {
inherit visible ;
2023-07-15 17:15:38 +00:00
description = " A l i a s o f { o p t i o n } ` ${ showOption to } ` . " ;
2020-04-24 23:36:52 +00:00
apply = x : use ( toOf config ) ;
} // optionalAttrs ( toType != null ) {
type = toType ;
} ) ;
config = mkMerge [
2022-11-04 12:27:35 +00:00
( optionalAttrs ( options ? warnings ) {
2020-04-24 23:36: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 } ' . " ;
2022-11-04 12:27:35 +00:00
} )
2020-04-24 23:36:52 +00:00
( if withPriority
then mkAliasAndWrapDefsWithPriority ( setAttrByPath to ) fromOpt
else mkAliasAndWrapDefinitions ( setAttrByPath to ) fromOpt )
] ;
} ;
2020-10-11 12:50:04 +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 .
2021-12-19 01:06:50 +00:00
modules . importJSON : : path -> attrs
2020-10-11 12:50:04 +00:00
* /
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 .
2021-12-19 01:06:50 +00:00
modules . importTOML : : path -> attrs
2020-10-11 12:50:04 +00:00
* /
importTOML = file : {
_file = file ;
config = lib . importTOML file ;
} ;
2023-05-24 13:37:59 +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
evalOptionValue
mergeModules
mergeModules'
pushDownProperties
unifyModuleSyntax
;
collectModules = collectModules null ;
} ;
in
private //
{
# 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
filterOverrides
filterOverrides'
fixMergeModules
fixupOptionType # should be private?
importJSON
importTOML
mergeDefinitions
2023-07-15 17:15:38 +00:00
mergeAttrDefinitionsWithPrio
2023-05-24 13:37:59 +00:00
mergeOptionDecls # should be private?
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
sortProperties ;
2020-04-24 23:36:52 +00:00
}