at 15.09-beta 20 kB view raw
1with import ./lists.nix; 2with import ./trivial.nix; 3with import ./attrsets.nix; 4with import ./options.nix; 5with import ./debug.nix; 6with import ./types.nix; 7 8rec { 9 10 /* Evaluate a set of modules. The result is a set of two 11 attributes: options: the nested set of all option declarations, 12 and config: the nested set of all option values. 13 !!! Please think twice before adding to this argument list! The more 14 that is specified here instead of in the modules themselves the harder 15 it is to transparently move a set of modules to be a submodule of another 16 config (as the proper arguments need to be replicated at each call to 17 evalModules) and the less declarative the module set is. */ 18 evalModules = { modules 19 , prefix ? [] 20 , # This should only be used for special arguments that need to be evaluated 21 # when resolving module structure (like in imports). For everything else, 22 # there's _module.args. 23 specialArgs ? {} 24 , # This would be remove in the future, Prefer _module.args option instead. 25 args ? {} 26 , # This would be remove in the future, Prefer _module.check option instead. 27 check ? true 28 }: 29 let 30 # This internal module declare internal options under the `_module' 31 # attribute. These options are fragile, as they are used by the 32 # module system to change the interpretation of modules. 33 internalModule = rec { 34 _file = ./modules.nix; 35 36 key = _file; 37 38 options = { 39 _module.args = mkOption { 40 type = types.attrsOf types.unspecified; 41 internal = true; 42 description = "Arguments passed to each module."; 43 }; 44 45 _module.check = mkOption { 46 type = types.bool; 47 internal = true; 48 default = check; 49 description = "Whether to check whether all option definitions have matching declarations."; 50 }; 51 }; 52 53 config = { 54 _module.args = args; 55 }; 56 }; 57 58 closed = closeModules (modules ++ [ internalModule ]) ({ inherit config options; lib = import ./.; } // specialArgs); 59 60 # Note: the list of modules is reversed to maintain backward 61 # compatibility with the old module system. Not sure if this is 62 # the most sensible policy. 63 options = mergeModules prefix (reverseList closed); 64 65 # Traverse options and extract the option values into the final 66 # config set. At the same time, check whether all option 67 # definitions have matching declarations. 68 # !!! _module.check's value can't depend on any other config values 69 # without an infinite recursion. One way around this is to make the 70 # 'config' passed around to the modules be unconditionally unchecked, 71 # and only do the check in 'result'. 72 config = yieldConfig prefix options; 73 yieldConfig = prefix: set: 74 let res = removeAttrs (mapAttrs (n: v: 75 if isOption v then v.value 76 else yieldConfig (prefix ++ [n]) v) set) ["_definedNames"]; 77 in 78 if options._module.check.value && set ? _definedNames then 79 foldl' (res: m: 80 foldl' (res: name: 81 if set ? ${name} then res else throw "The option `${showOption (prefix ++ [name])}' defined in `${m.file}' does not exist.") 82 res m.names) 83 res set._definedNames 84 else 85 res; 86 result = { inherit options config; }; 87 in result; 88 89 /* Close a set of modules under the imports relation. */ 90 closeModules = modules: args: 91 let 92 toClosureList = file: parentKey: imap (n: x: 93 if isAttrs x || isFunction x then 94 let key = "${parentKey}:anon-${toString n}"; in 95 unifyModuleSyntax file key (unpackSubmodule (applyIfFunction key) x args) 96 else 97 let file = toString x; key = toString x; in 98 unifyModuleSyntax file key (applyIfFunction key (import x) args)); 99 in 100 builtins.genericClosure { 101 startSet = toClosureList unknownModule "" modules; 102 operator = m: toClosureList m.file m.key m.imports; 103 }; 104 105 /* Massage a module into canonical form, that is, a set consisting 106 of options, config and imports attributes. */ 107 unifyModuleSyntax = file: key: m: 108 if m ? config || m ? options then 109 let badAttrs = removeAttrs m ["imports" "options" "config" "key" "_file"]; in 110 if badAttrs != {} then 111 throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by assignments to the top-level attributes `config' or `options'." 112 else 113 { file = m._file or file; 114 key = toString m.key or key; 115 imports = m.imports or []; 116 options = m.options or {}; 117 config = m.config or {}; 118 } 119 else 120 { file = m._file or file; 121 key = toString m.key or key; 122 imports = m.require or [] ++ m.imports or []; 123 options = {}; 124 config = removeAttrs m ["key" "_file" "require" "imports"]; 125 }; 126 127 applyIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then 128 let 129 # Module arguments are resolved in a strict manner when attribute set 130 # deconstruction is used. As the arguments are now defined with the 131 # config._module.args option, the strictness used on the attribute 132 # set argument would cause an infinite loop, if the result of the 133 # option is given as argument. 134 # 135 # To work-around the strictness issue on the deconstruction of the 136 # attributes set argument, we create a new attribute set which is 137 # constructed to satisfy the expected set of attributes. Thus calling 138 # a module will resolve strictly the attributes used as argument but 139 # not their values. The values are forwarding the result of the 140 # evaluation of the option. 141 requiredArgs = builtins.attrNames (builtins.functionArgs f); 142 context = name: ''while evaluating the module argument `${name}' in "${key}":''; 143 extraArgs = builtins.listToAttrs (map (name: { 144 inherit name; 145 value = addErrorContext (context name) 146 (args.${name} or config._module.args.${name}); 147 }) requiredArgs); 148 149 # Note: we append in the opposite order such that we can add an error 150 # context on the explicited arguments of "args" too. This update 151 # operator is used to make the "args@{ ... }: with args.lib;" notation 152 # works. 153 in f (args // extraArgs) 154 else 155 f; 156 157 /* We have to pack and unpack submodules. We cannot wrap the expected 158 result of the function as we would no longer be able to list the arguments 159 of the submodule. (see applyIfFunction) */ 160 unpackSubmodule = unpack: m: args: 161 if isType "submodule" m then 162 { _file = m.file; } // (unpack m.submodule args) 163 else unpack m args; 164 165 packSubmodule = file: m: 166 { _type = "submodule"; file = file; submodule = m; }; 167 168 /* Merge a list of modules. This will recurse over the option 169 declarations in all modules, combining them into a single set. 170 At the same time, for each option declaration, it will merge the 171 corresponding option definitions in all machines, returning them 172 in the value attribute of each option. */ 173 mergeModules = prefix: modules: 174 mergeModules' prefix modules 175 (concatMap (m: map (config: { inherit (m) file; inherit config; }) (pushDownProperties m.config)) modules); 176 177 mergeModules' = prefix: options: configs: 178 listToAttrs (map (name: { 179 # We're descending into attribute ‘name’. 180 inherit name; 181 value = 182 let 183 loc = prefix ++ [name]; 184 # Get all submodules that declare ‘name’. 185 decls = concatMap (m: 186 if m.options ? ${name} 187 then [ { inherit (m) file; options = m.options.${name}; } ] 188 else [] 189 ) options; 190 # Get all submodules that define ‘name’. 191 defns = concatMap (m: 192 if m.config ? ${name} 193 then map (config: { inherit (m) file; inherit config; }) 194 (pushDownProperties m.config.${name}) 195 else [] 196 ) configs; 197 nrOptions = count (m: isOption m.options) decls; 198 # Extract the definitions for this loc 199 defns' = map (m: { inherit (m) file; value = m.config.${name}; }) 200 (filter (m: m.config ? ${name}) configs); 201 in 202 if nrOptions == length decls then 203 let opt = fixupOptionType loc (mergeOptionDecls loc decls); 204 in evalOptionValue loc opt defns' 205 else if nrOptions != 0 then 206 let 207 firstOption = findFirst (m: isOption m.options) "" decls; 208 firstNonOption = findFirst (m: !isOption m.options) "" decls; 209 in 210 throw "The option `${showOption loc}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'." 211 else 212 mergeModules' loc decls defns; 213 }) (concatMap (m: attrNames m.options) options)) 214 // { _definedNames = map (m: { inherit (m) file; names = attrNames m.config; }) configs; }; 215 216 /* Merge multiple option declarations into a single declaration. In 217 general, there should be only one declaration of each option. 218 The exception is the options attribute, which specifies 219 sub-options. These can be specified multiple times to allow one 220 module to add sub-options to an option declared somewhere else 221 (e.g. multiple modules define sub-options for fileSystems). 222 223 'loc' is the list of attribute names where the option is located. 224 225 'opts' is a list of modules. Each module has an options attribute which 226 correspond to the definition of 'loc' in 'opt.file'. */ 227 mergeOptionDecls = loc: opts: 228 foldl' (res: opt: 229 if opt.options ? default && res ? default || 230 opt.options ? example && res ? example || 231 opt.options ? description && res ? description || 232 opt.options ? apply && res ? apply || 233 # Accept to merge options which have identical types. 234 opt.options ? type && res ? type && opt.options.type.name != res.type.name 235 then 236 throw "The option `${showOption loc}' in `${opt.file}' is already declared in ${showFiles res.declarations}." 237 else 238 let 239 /* Add the modules of the current option to the list of modules 240 already collected. The options attribute except either a list of 241 submodules or a submodule. For each submodule, we add the file of the 242 current option declaration as the file use for the submodule. If the 243 submodule defines any filename, then we ignore the enclosing option file. */ 244 options' = toList opt.options.options; 245 coerceOption = file: opt: 246 if isFunction opt then packSubmodule file opt 247 else packSubmodule file { options = opt; }; 248 getSubModules = opt.options.type.getSubModules or null; 249 submodules = 250 if getSubModules != null then map (packSubmodule opt.file) getSubModules ++ res.options 251 else if opt.options ? options then map (coerceOption opt.file) options' ++ res.options 252 else res.options; 253 in opt.options // res // 254 { declarations = res.declarations ++ [opt.file]; 255 options = submodules; 256 } 257 ) { inherit loc; declarations = []; options = []; } opts; 258 259 /* Merge all the definitions of an option to produce the final 260 config value. */ 261 evalOptionValue = loc: opt: defs: 262 let 263 # Add in the default value for this option, if any. 264 defs' = 265 (optional (opt ? default) 266 { file = head opt.declarations; value = mkOptionDefault opt.default; }) ++ defs; 267 268 # Handle properties, check types, and merge everything together. 269 res = 270 if opt.readOnly or false && length defs' > 1 then 271 throw "The option `${showOption loc}' is read-only, but it's set multiple times." 272 else 273 mergeDefinitions loc opt.type defs'; 274 275 # Check whether the option is defined, and apply the ‘apply’ 276 # function to the merged value. This allows options to yield a 277 # value computed from the definitions. 278 value = 279 if !res.isDefined then 280 throw "The option `${showOption loc}' is used but not defined." 281 else if opt ? apply then 282 opt.apply res.mergedValue 283 else 284 res.mergedValue; 285 286 in opt // 287 { value = addErrorContext "while evaluating the option `${showOption loc}':" value; 288 definitions = map (def: def.value) res.defsFinal; 289 files = map (def: def.file) res.defsFinal; 290 inherit (res) isDefined; 291 }; 292 293 # Merge definitions of a value of a given type. 294 mergeDefinitions = loc: type: defs: rec { 295 defsFinal = 296 let 297 # Process mkMerge and mkIf properties. 298 defs' = concatMap (m: 299 map (value: { inherit (m) file; inherit value; }) (dischargeProperties m.value) 300 ) defs; 301 302 # Process mkOverride properties. 303 defs'' = filterOverrides defs'; 304 305 # Sort mkOrder properties. 306 defs''' = 307 # Avoid sorting if we don't have to. 308 if any (def: def.value._type or "" == "order") defs'' 309 then sortProperties defs'' 310 else defs''; 311 in defs'''; 312 313 # Type-check the remaining definitions, and merge them. 314 mergedValue = foldl' (res: def: 315 if type.check def.value then res 316 else throw "The option value `${showOption loc}' in `${def.file}' is not a ${type.name}.") 317 (type.merge loc defsFinal) defsFinal; 318 319 isDefined = defsFinal != []; 320 321 optionalValue = 322 if isDefined then { value = mergedValue; } 323 else {}; 324 }; 325 326 /* Given a config set, expand mkMerge properties, and push down the 327 other properties into the children. The result is a list of 328 config sets that do not have properties at top-level. For 329 example, 330 331 mkMerge [ { boot = set1; } (mkIf cond { boot = set2; services = set3; }) ] 332 333 is transformed into 334 335 [ { boot = set1; } { boot = mkIf cond set2; services = mkIf cond set3; } ]. 336 337 This transform is the critical step that allows mkIf conditions 338 to refer to the full configuration without creating an infinite 339 recursion. 340 */ 341 pushDownProperties = cfg: 342 if cfg._type or "" == "merge" then 343 concatMap pushDownProperties cfg.contents 344 else if cfg._type or "" == "if" then 345 map (mapAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content) 346 else if cfg._type or "" == "override" then 347 map (mapAttrs (n: v: mkOverride cfg.priority v)) (pushDownProperties cfg.content) 348 else # FIXME: handle mkOrder? 349 [ cfg ]; 350 351 /* Given a config value, expand mkMerge properties, and discharge 352 any mkIf conditions. That is, this is the place where mkIf 353 conditions are actually evaluated. The result is a list of 354 config values. For example, mkIf false x yields [], 355 mkIf true x yields [x], and 356 357 mkMerge [ 1 (mkIf true 2) (mkIf true (mkIf false 3)) ] 358 359 yields [ 1 2 ]. 360 */ 361 dischargeProperties = def: 362 if def._type or "" == "merge" then 363 concatMap dischargeProperties def.contents 364 else if def._type or "" == "if" then 365 if def.condition then 366 dischargeProperties def.content 367 else 368 [ ] 369 else 370 [ def ]; 371 372 /* Given a list of config values, process the mkOverride properties, 373 that is, return the values that have the highest (that is, 374 numerically lowest) priority, and strip the mkOverride 375 properties. For example, 376 377 [ { file = "/1"; value = mkOverride 10 "a"; } 378 { file = "/2"; value = mkOverride 20 "b"; } 379 { file = "/3"; value = "z"; } 380 { file = "/4"; value = mkOverride 10 "d"; } 381 ] 382 383 yields 384 385 [ { file = "/1"; value = "a"; } 386 { file = "/4"; value = "d"; } 387 ] 388 389 Note that "z" has the default priority 100. 390 */ 391 filterOverrides = defs: 392 let 393 defaultPrio = 100; 394 getPrio = def: if def.value._type or "" == "override" then def.value.priority else defaultPrio; 395 highestPrio = foldl' (prio: def: min (getPrio def) prio) 9999 defs; 396 strip = def: if def.value._type or "" == "override" then def // { value = def.value.content; } else def; 397 in concatMap (def: if getPrio def == highestPrio then [(strip def)] else []) defs; 398 399 /* Sort a list of properties. The sort priority of a property is 400 1000 by default, but can be overriden by wrapping the property 401 using mkOrder. */ 402 sortProperties = defs: 403 let 404 strip = def: 405 if def.value._type or "" == "order" 406 then def // { value = def.value.content; inherit (def.value) priority; } 407 else def; 408 defs' = map strip defs; 409 compare = a: b: (a.priority or 1000) < (b.priority or 1000); 410 in sort compare defs'; 411 412 /* Hack for backward compatibility: convert options of type 413 optionSet to options of type submodule. FIXME: remove 414 eventually. */ 415 fixupOptionType = loc: opt: 416 let 417 options = opt.options or 418 (throw "Option `${showOption loc'}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}."); 419 f = tp: 420 if tp.name == "option set" || tp.name == "submodule" then 421 throw "The option ${showOption loc} uses submodules without a wrapping type, in ${showFiles opt.declarations}." 422 else if tp.name == "attribute set of option sets" then types.attrsOf (types.submodule options) 423 else if tp.name == "list or attribute set of option sets" then types.loaOf (types.submodule options) 424 else if tp.name == "list of option sets" then types.listOf (types.submodule options) 425 else if tp.name == "null or option set" then types.nullOr (types.submodule options) 426 else tp; 427 in 428 if opt.type.getSubModules or null == null 429 then opt // { type = f (opt.type or types.unspecified); } 430 else opt // { type = opt.type.substSubModules opt.options; options = []; }; 431 432 433 /* Properties. */ 434 435 mkIf = condition: content: 436 { _type = "if"; 437 inherit condition content; 438 }; 439 440 mkAssert = assertion: message: content: 441 mkIf 442 (if assertion then true else throw "\nFailed assertion: ${message}") 443 content; 444 445 mkMerge = contents: 446 { _type = "merge"; 447 inherit contents; 448 }; 449 450 mkOverride = priority: content: 451 { _type = "override"; 452 inherit priority content; 453 }; 454 455 mkOptionDefault = mkOverride 1001; # priority of option defaults 456 mkDefault = mkOverride 1000; # used in config sections of non-user modules to set a default 457 mkForce = mkOverride 50; 458 mkVMOverride = mkOverride 10; # used by ‘nixos-rebuild build-vm’ 459 460 mkStrict = builtins.trace "`mkStrict' is obsolete; use `mkOverride 0' instead." (mkOverride 0); 461 462 mkFixStrictness = id; # obsolete, no-op 463 464 mkOrder = priority: content: 465 { _type = "order"; 466 inherit priority content; 467 }; 468 469 mkBefore = mkOrder 500; 470 mkAfter = mkOrder 1500; 471 472 # Convenient property used to transfer all definitions and their 473 # properties from one option to another. This property is useful for 474 # renaming options, and also for including properties from another module 475 # system, including sub-modules. 476 # 477 # { config, options, ... }: 478 # 479 # { 480 # # 'bar' might not always be defined in the current module-set. 481 # config.foo.enable = mkAliasDefinitions (options.bar.enable or {}); 482 # 483 # # 'barbaz' has to be defined in the current module-set. 484 # config.foobar.paths = mkAliasDefinitions options.barbaz.paths; 485 # } 486 # 487 # Note, this is different than taking the value of the option and using it 488 # as a definition, as the new definition will not keep the mkOverride / 489 # mkDefault properties of the previous option. 490 # 491 mkAliasDefinitions = mkAliasAndWrapDefinitions id; 492 mkAliasAndWrapDefinitions = wrap: option: 493 mkMerge 494 (optional (isOption option && option.isDefined) 495 (wrap (mkMerge option.definitions))); 496 497 498 /* Compatibility. */ 499 fixMergeModules = modules: args: evalModules { inherit modules args; check = false; }; 500 501}