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
count
elem
filter
findFirst
flip
foldl
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
toList
types
2021-05-03 20:48:10 +00:00
warnIf
2020-10-27 00:29:36 +00:00
;
inherit ( lib . options )
isOption
mkOption
showDefs
showFiles
showOption
unknownModule
;
in
2020-04-24 23:36:52 +00:00
rec {
/* E v a l u a t e a s e t o f m o d u l e s . T h e r e s u l t i s a s e t o f t w o
attributes : ‘ options ’ : the nested set of all option declarations ,
and ‘ config ’ : the nested set of all option values .
! ! ! Please think twice before adding to this argument list ! The more
that is specified here instead of in the modules themselves the harder
it is to transparently move a set of modules to be a submodule of another
config ( as the proper arguments need to be replicated at each call to
evalModules ) and the less declarative the module set is . * /
evalModules = { modules
, prefix ? [ ]
, # 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 ? { }
, # 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
} :
let
# 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.
internalModule = rec {
_file = ./modules.nix ;
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 . unspecified ;
internal = true ;
description = " A r g u m e n t s p a s s e d t o e a c h m o d u l e . " ;
} ;
_module . check = mkOption {
type = types . bool ;
internal = true ;
default = check ;
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 . " ;
} ;
2020-08-20 17:08:02 +00:00
_module . freeformType = mkOption {
# Disallow merging for now, but could be implemented nicely with a `types.optionType`
type = types . nullOr ( types . uniq types . attrs ) ;
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 <literal>
config < /literal > value .
If this is <literal> null < /literal > , definitions without an option
will throw an error unless <option> _module . check < /option > is
turned off .
'' ;
} ;
2020-04-24 23:36:52 +00:00
} ;
config = {
_module . args = args ;
} ;
} ;
2020-08-20 17:08:02 +00:00
merged =
let collected = collectModules
( specialArgs . modulesPath or " " )
( modules ++ [ 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 ;
2020-09-25 04:45:31 +00:00
baseMsg = " T h e o p t i o n ` ${ showOption ( prefix ++ firstDef . prefix ) } ' d o e s n o t e x i s t . D e f i n i t i o n v a l u e s : ${ showDefs [ firstDef ] } " ;
2020-08-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 ;
result = builtins . seq checkUnmatched {
2020-04-24 23:36:52 +00:00
inherit options ;
config = removeAttrs config [ " _ m o d u l e " ] ;
inherit ( config ) _module ;
} ;
in result ;
# collectModules :: (modulesPath: String) -> (modules: [ Module ]) -> (args: Attrs) -> [ Module ]
#
# Collects all modules recursively through `import` statements, filtering out
# all modules in disabledModules.
collectModules = let
# Like unifyModuleSyntax, but also imports paths and calls functions if necessary
loadModule = args : fallbackFile : fallbackKey : m :
if isFunction m || isAttrs m then
unifyModuleSyntax fallbackFile fallbackKey ( applyIfFunction fallbackKey m args )
2021-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 } "
2020-04-24 23:36:52 +00:00
else unifyModuleSyntax ( toString m ) ( toString m ) ( applyIfFunction ( toString m ) ( import m ) args ) ;
/*
Collects all modules recursively into the form
{
disabled = [ < list of disabled modules > ] ;
# All modules of the main module list
modules = [
{
key = <key1> ;
module = < module for key1 > ;
# All modules imported by the module for key1
modules = [
{
key = <key1-1> ;
module = < module for key1-1 > ;
# All modules imported by the module for key1-1
modules = [ . . . ] ;
}
. . .
] ;
}
. . .
] ;
}
* /
collectStructuredModules =
let
collectResults = modules : {
disabled = concatLists ( catAttrs " d i s a b l e d " modules ) ;
inherit modules ;
} ;
in parentFile : parentKey : initialModules : args : collectResults ( imap1 ( n : x :
let
module = loadModule args parentFile " ${ parentKey } : a n o n - ${ toString n } " x ;
collectedImports = collectStructuredModules module . _file module . key module . imports args ;
in {
key = module . key ;
module = module ;
modules = collectedImports . modules ;
disabled = module . disabledModules ++ collectedImports . disabled ;
} ) initialModules ) ;
# filterModules :: String -> { disabled, modules } -> [ Module ]
#
# Filters a structure as emitted by collectStructuredModules by removing all disabled
# modules recursively. It returns the final list of unique-by-key modules
filterModules = modulesPath : { disabled , modules }:
let
moduleKey = m : if isString m then toString modulesPath + " / " + m else toString m ;
disabledKeys = map moduleKey disabled ;
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 ) ;
/* 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
2020-08-20 17:08:02 +00:00
let badAttrs = removeAttrs m [ " _ f i l e " " k e y " " d i s a b l e d M o d u l e s " " i m p o r t s " " o p t i o n s " " c o n f i g " " m e t a " " f r e e f o r m T y p e " ] ; in
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 ;
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
2020-12-03 08:41:04 +00:00
{ _file = toString m . _file or file ;
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 = { } ;
2020-08-20 17:08:02 +00:00
config = addFreeformType ( addMeta ( removeAttrs m [ " _ f i l e " " k e y " " d i s a b l e d M o d u l e s " " r e q u i r e " " i m p o r t s " " f r e e f o r m T y p e " ] ) ) ;
2020-04-24 23:36:52 +00:00
} ;
applyIfFunction = key : f : args @ { config , options , lib , . . . }: if isFunction f then
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
# context on the explicited arguments of "args" too. This update
# operator is used to make the "args@{ ... }: with args.lib;" notation
# works.
in f ( args // extraArgs )
else
f ;
/* 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
/* b y N a m e i s l i k e f o l d A t t r s , b u t w i l l l o o k f o r a t t r i b u t e s t o m e r g e i n t h e
specified attribute name .
byName " f o o " ( module : value : [ " m o d u l e . h i d d e n = ${ module . hidden } , v a l u e = ${ value } " ] )
[
{
hidden = " b a z " ;
foo = { qux = " b a r " ; gla = " f l o p " ; } ;
}
{
hidden = " f l i " ;
foo = { qux = " g n e " ; gli = " f l i p " ; } ;
}
]
== = >
{
gla = [ " m o d u l e . h i d d e n = b a z , v a l u e = f l o p " ] ;
gli = [ " m o d u l e . h i d d e n = f l i , v a l u e = f l i p " ] ;
qux = [ " m o d u l e . h i d d e n = b a z , v a l u e = b a r " " m o d u l e . h i d d e n = f l i , v a l u e = g n e " ] ;
}
* /
byName = attr : f : modules :
foldl' ( acc : module :
2021-03-20 04:20:00 +00:00
if ! ( builtins . isAttrs module . ${ attr } ) then
throw ''
You're trying to declare a value of type ` $ { builtins . typeOf module . ${ attr } } '
rather than an attribute-set for the option
` $ { builtins . concatStringsSep " . " prefix } ' !
This usually happens if ` $ { builtins . concatStringsSep " . " prefix } ' has option
definitions inside that are not matched . Please check how to properly define
this option by e . g . referring to ` man 5 configuration . nix' !
''
else
2020-04-24 23:36:52 +00:00
acc // ( mapAttrs ( n : v :
( acc . ${ n } or [ ] ) ++ f module v
) module . ${ attr }
)
) { } modules ;
# an attrset 'name' => list of submodules that declare ‘ name’ .
declsByName = byName " o p t i o n s " ( module : option :
[ { inherit ( module ) _file ; options = option ; } ]
) options ;
# an attrset 'name' => list of submodules that define ‘ name’ .
defnsByName = byName " c o n f i g " ( module : value :
map ( config : { inherit ( module ) file ; inherit config ; } ) ( pushDownProperties value )
) configs ;
# extract the definitions for each loc
defnsByName' = byName " c o n f i g " ( module : value :
[ { inherit ( module ) file ; inherit value ; } ]
) configs ;
2020-08-20 17:08:02 +00:00
resultsByName = flip mapAttrs declsByName ( name : decls :
# We're descending into attribute ‘ name’ .
2020-04-24 23:36:52 +00:00
let
loc = prefix ++ [ name ] ;
defns = defnsByName . ${ name } or [ ] ;
defns' = defnsByName' . ${ name } or [ ] ;
nrOptions = count ( m : isOption m . options ) decls ;
in
if nrOptions == length decls then
let opt = fixupOptionType loc ( mergeOptionDecls loc decls ) ;
2020-08-20 17:08:02 +00:00
in {
matchedOptions = evalOptionValue loc opt defns' ;
unmatchedDefns = [ ] ;
}
2020-04-24 23:36:52 +00:00
else if nrOptions != 0 then
let
firstOption = findFirst ( m : isOption m . options ) " " decls ;
firstNonOption = findFirst ( m : ! isOption m . options ) " " decls ;
in
throw " T h e o p t i o n ` ${ showOption loc } ' i n ` ${ firstOption . _file } ' i s a p r e f i x o f o p t i o n s i n ` ${ firstNonOption . _file } ' . "
else
2020-08-20 17:08:02 +00:00
mergeModules' loc decls defns ) ;
matchedOptions = mapAttrs ( n : v : v . matchedOptions ) resultsByName ;
# an attrset 'name' => list of unmatched definitions for 'name'
unmatchedDefnsByName =
# Propagate all unmatched definitions from nested option sets
mapAttrs ( n : v : v . unmatchedDefns ) resultsByName
# Plus the definitions for the current prefix that don't have a matching option
// removeAttrs defnsByName' ( attrNames matchedOptions ) ;
in {
inherit matchedOptions ;
# Transforms unmatchedDefnsByName into a list of definitions
unmatchedDefns = concatLists ( mapAttrsToList ( name : defs :
map ( def : def // {
# Set this so we know when the definition first left unmatched territory
prefix = [ name ] ++ ( def . prefix or [ ] ) ;
} ) defs
) unmatchedDefnsByName ) ;
} ;
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 =
let
packSubmodule = file : m :
{ _file = file ; imports = [ m ] ; } ;
coerceOption = file : opt :
if isFunction opt then packSubmodule file opt
else packSubmodule file { options = opt ; } ;
in loc : opts :
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
/* A d d t h e m o d u l e s o f t h e c u r r e n t o p t i o n t o t h e l i s t o f m o d u l e s
already collected . The options attribute except either a list of
submodules or a submodule . For each submodule , we add the file of the
current option declaration as the file use for the submodule . If the
submodule defines any filename , then we ignore the enclosing option file . * /
options' = toList opt . options . options ;
getSubModules = opt . options . type . getSubModules or null ;
submodules =
if getSubModules != null then map ( packSubmodule opt . _file ) getSubModules ++ res . options
else if opt . options ? options then map ( coerceOption opt . _file ) options' ++ res . options
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 ;
inherit ( res ) isDefined ;
} ;
# 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
getPrio = def : if def . value . _type or " " == " o v e r r i d e " then def . value . priority else defaultPriority ;
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
1000 by default , but can be overridden by wrapping the property
using mkOrder . * /
sortProperties = defs :
let
strip = def :
if def . value . _type or " " == " o r d e r "
then def // { value = def . value . content ; inherit ( def . value ) priority ; }
else def ;
defs' = map strip defs ;
compare = a : b : ( a . priority or 1000 ) < ( b . priority or 1000 ) ;
in sort compare defs' ;
/* H a c k f o r b a c k w a r d c o m p a t i b i l i t y : c o n v e r t o p t i o n s o f t y p e
optionSet to options of type submodule . FIXME : remove
eventually . * /
fixupOptionType = loc : opt :
let
options = opt . options or
2020-10-27 00:29:36 +00:00
( throw " O p t i o n ` ${ showOption loc } ' h a s t y p e o p t i o n S e t b u t h a s n o o p t i o n a t t r i b u t e , i n ${ showFiles opt . declarations } . " ) ;
2020-04-24 23:36:52 +00:00
f = tp :
let optionSetIn = type : ( tp . name == type ) && ( tp . functor . wrapped . name == " o p t i o n S e t " ) ;
in
if tp . name == " o p t i o n s e t " || tp . name == " s u b m o d u l e " then
throw " T h e o p t i o n ${ showOption loc } u s e s s u b m o d u l e s w i t h o u t a w r a p p i n g t y p e , i n ${ showFiles opt . declarations } . "
else if optionSetIn " a t t r s O f " then types . attrsOf ( types . submodule options )
else if optionSetIn " l i s t O f " then types . listOf ( types . submodule options )
else if optionSetIn " n u l l O r " then types . nullOr ( types . submodule options )
else tp ;
in
if opt . type . getSubModules or null == null
then opt // { type = f ( opt . type or types . unspecified ) ; }
else opt // { type = opt . type . substSubModules opt . options ; options = [ ] ; } ;
/* 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
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’
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 ;
mkAfter = mkOrder 1500 ;
# The default priority for things that don't have a priority specified.
defaultPriority = 100 ;
# 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.
#
# If a priority is not set, it assumes a priority of defaultPriority.
mkAliasAndWrapDefsWithPriority = wrap : option :
let
prio = option . highestPrio or defaultPriority ;
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 } ' . " ;
} ;
/* 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 , . . . }:
{
options = foldl recursiveUpdate { } ( map ( path : setAttrByPath path ( mkOption {
visible = false ;
# To use the value in mergeFn without triggering errors
default = " _ m k M e r g e d O p t i o n M o d u l e " ;
} ) ) from ) ;
config = {
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 ;
} ;
doRename = { from , to , visible , warn , use , withPriority ? true }:
{ 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 ;
description = " A l i a s o f < o p t i o n > ${ showOption to } < / o p t i o n > . " ;
apply = x : use ( toOf config ) ;
} // optionalAttrs ( toType != null ) {
type = toType ;
} ) ;
config = mkMerge [
{
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 } ' . " ;
}
( 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 .
importJSON -> path -> attrs
* /
importJSON = file : {
_file = file ;
config = lib . importJSON file ;
} ;
/* U s e t h i s f u n c t i o n t o i m p o r t a T O M L f i l e a s N i x O S c o n f i g u r a t i o n .
importTOML -> path -> attrs
* /
importTOML = file : {
_file = file ;
config = lib . importTOML file ;
} ;
2020-04-24 23:36:52 +00:00
}