at 16.09-beta 23 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 let metaSet = if m ? meta 109 then { meta = m.meta; } 110 else {}; 111 in 112 if m ? config || m ? options then 113 let badAttrs = removeAttrs m ["imports" "options" "config" "key" "_file" "meta"]; in 114 if badAttrs != {} then 115 throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by assignments to the top-level attributes `config' or `options'." 116 else 117 { file = m._file or file; 118 key = toString m.key or key; 119 imports = m.imports or []; 120 options = m.options or {}; 121 config = mkMerge [ (m.config or {}) metaSet ]; 122 } 123 else 124 { file = m._file or file; 125 key = toString m.key or key; 126 imports = m.require or [] ++ m.imports or []; 127 options = {}; 128 config = mkMerge [ (removeAttrs m ["key" "_file" "require" "imports"]) metaSet ]; 129 }; 130 131 applyIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then 132 let 133 # Module arguments are resolved in a strict manner when attribute set 134 # deconstruction is used. As the arguments are now defined with the 135 # config._module.args option, the strictness used on the attribute 136 # set argument would cause an infinite loop, if the result of the 137 # option is given as argument. 138 # 139 # To work-around the strictness issue on the deconstruction of the 140 # attributes set argument, we create a new attribute set which is 141 # constructed to satisfy the expected set of attributes. Thus calling 142 # a module will resolve strictly the attributes used as argument but 143 # not their values. The values are forwarding the result of the 144 # evaluation of the option. 145 requiredArgs = builtins.attrNames (builtins.functionArgs f); 146 context = name: ''while evaluating the module argument `${name}' in "${key}":''; 147 extraArgs = builtins.listToAttrs (map (name: { 148 inherit name; 149 value = addErrorContext (context name) 150 (args.${name} or config._module.args.${name}); 151 }) requiredArgs); 152 153 # Note: we append in the opposite order such that we can add an error 154 # context on the explicited arguments of "args" too. This update 155 # operator is used to make the "args@{ ... }: with args.lib;" notation 156 # works. 157 in f (args // extraArgs) 158 else 159 f; 160 161 /* We have to pack and unpack submodules. We cannot wrap the expected 162 result of the function as we would no longer be able to list the arguments 163 of the submodule. (see applyIfFunction) */ 164 unpackSubmodule = unpack: m: args: 165 if isType "submodule" m then 166 { _file = m.file; } // (unpack m.submodule args) 167 else unpack m args; 168 169 packSubmodule = file: m: 170 { _type = "submodule"; file = file; submodule = m; }; 171 172 /* Merge a list of modules. This will recurse over the option 173 declarations in all modules, combining them into a single set. 174 At the same time, for each option declaration, it will merge the 175 corresponding option definitions in all machines, returning them 176 in the value attribute of each option. */ 177 mergeModules = prefix: modules: 178 mergeModules' prefix modules 179 (concatMap (m: map (config: { inherit (m) file; inherit config; }) (pushDownProperties m.config)) modules); 180 181 mergeModules' = prefix: options: configs: 182 listToAttrs (map (name: { 183 # We're descending into attribute ‘name’. 184 inherit name; 185 value = 186 let 187 loc = prefix ++ [name]; 188 # Get all submodules that declare ‘name’. 189 decls = concatMap (m: 190 if m.options ? ${name} 191 then [ { inherit (m) file; options = m.options.${name}; } ] 192 else [] 193 ) options; 194 # Get all submodules that define ‘name’. 195 defns = concatMap (m: 196 if m.config ? ${name} 197 then map (config: { inherit (m) file; inherit config; }) 198 (pushDownProperties m.config.${name}) 199 else [] 200 ) configs; 201 nrOptions = count (m: isOption m.options) decls; 202 # Extract the definitions for this loc 203 defns' = map (m: { inherit (m) file; value = m.config.${name}; }) 204 (filter (m: m.config ? ${name}) configs); 205 in 206 if nrOptions == length decls then 207 let opt = fixupOptionType loc (mergeOptionDecls loc decls); 208 in evalOptionValue loc opt defns' 209 else if nrOptions != 0 then 210 let 211 firstOption = findFirst (m: isOption m.options) "" decls; 212 firstNonOption = findFirst (m: !isOption m.options) "" decls; 213 in 214 throw "The option `${showOption loc}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'." 215 else 216 mergeModules' loc decls defns; 217 }) (concatMap (m: attrNames m.options) options)) 218 // { _definedNames = map (m: { inherit (m) file; names = attrNames m.config; }) configs; }; 219 220 /* Merge multiple option declarations into a single declaration. In 221 general, there should be only one declaration of each option. 222 The exception is the options attribute, which specifies 223 sub-options. These can be specified multiple times to allow one 224 module to add sub-options to an option declared somewhere else 225 (e.g. multiple modules define sub-options for fileSystems). 226 227 'loc' is the list of attribute names where the option is located. 228 229 'opts' is a list of modules. Each module has an options attribute which 230 correspond to the definition of 'loc' in 'opt.file'. */ 231 mergeOptionDecls = loc: opts: 232 foldl' (res: opt: 233 if opt.options ? default && res ? default || 234 opt.options ? example && res ? example || 235 opt.options ? description && res ? description || 236 opt.options ? apply && res ? apply || 237 # Accept to merge options which have identical types. 238 opt.options ? type && res ? type && opt.options.type.name != res.type.name 239 then 240 throw "The option `${showOption loc}' in `${opt.file}' is already declared in ${showFiles res.declarations}." 241 else 242 let 243 /* Add the modules of the current option to the list of modules 244 already collected. The options attribute except either a list of 245 submodules or a submodule. For each submodule, we add the file of the 246 current option declaration as the file use for the submodule. If the 247 submodule defines any filename, then we ignore the enclosing option file. */ 248 options' = toList opt.options.options; 249 coerceOption = file: opt: 250 if isFunction opt then packSubmodule file opt 251 else packSubmodule file { options = opt; }; 252 getSubModules = opt.options.type.getSubModules or null; 253 submodules = 254 if getSubModules != null then map (packSubmodule opt.file) getSubModules ++ res.options 255 else if opt.options ? options then map (coerceOption opt.file) options' ++ res.options 256 else res.options; 257 in opt.options // res // 258 { declarations = res.declarations ++ [opt.file]; 259 options = submodules; 260 } 261 ) { inherit loc; declarations = []; options = []; } opts; 262 263 /* Merge all the definitions of an option to produce the final 264 config value. */ 265 evalOptionValue = loc: opt: defs: 266 let 267 # Add in the default value for this option, if any. 268 defs' = 269 (optional (opt ? default) 270 { file = head opt.declarations; value = mkOptionDefault opt.default; }) ++ defs; 271 272 # Handle properties, check types, and merge everything together. 273 res = 274 if opt.readOnly or false && length defs' > 1 then 275 throw "The option `${showOption loc}' is read-only, but it's set multiple times." 276 else 277 mergeDefinitions loc opt.type defs'; 278 279 # Check whether the option is defined, and apply the ‘apply’ 280 # function to the merged value. This allows options to yield a 281 # value computed from the definitions. 282 value = 283 if !res.isDefined then 284 throw "The option `${showOption loc}' is used but not defined." 285 else if opt ? apply then 286 opt.apply res.mergedValue 287 else 288 res.mergedValue; 289 290 in opt // 291 { value = addErrorContext "while evaluating the option `${showOption loc}':" value; 292 definitions = map (def: def.value) res.defsFinal; 293 files = map (def: def.file) res.defsFinal; 294 inherit (res) isDefined; 295 }; 296 297 # Merge definitions of a value of a given type. 298 mergeDefinitions = loc: type: defs: rec { 299 defsFinal = 300 let 301 # Process mkMerge and mkIf properties. 302 defs' = concatMap (m: 303 map (value: { inherit (m) file; inherit value; }) (dischargeProperties m.value) 304 ) defs; 305 306 # Process mkOverride properties. 307 defs'' = filterOverrides defs'; 308 309 # Sort mkOrder properties. 310 defs''' = 311 # Avoid sorting if we don't have to. 312 if any (def: def.value._type or "" == "order") defs'' 313 then sortProperties defs'' 314 else defs''; 315 in defs'''; 316 317 # Type-check the remaining definitions, and merge them. 318 mergedValue = foldl' (res: def: 319 if type.check def.value then res 320 else throw "The option value `${showOption loc}' in `${def.file}' is not a ${type.name}.") 321 (type.merge loc defsFinal) defsFinal; 322 323 isDefined = defsFinal != []; 324 325 optionalValue = 326 if isDefined then { value = mergedValue; } 327 else {}; 328 }; 329 330 /* Given a config set, expand mkMerge properties, and push down the 331 other properties into the children. The result is a list of 332 config sets that do not have properties at top-level. For 333 example, 334 335 mkMerge [ { boot = set1; } (mkIf cond { boot = set2; services = set3; }) ] 336 337 is transformed into 338 339 [ { boot = set1; } { boot = mkIf cond set2; services = mkIf cond set3; } ]. 340 341 This transform is the critical step that allows mkIf conditions 342 to refer to the full configuration without creating an infinite 343 recursion. 344 */ 345 pushDownProperties = cfg: 346 if cfg._type or "" == "merge" then 347 concatMap pushDownProperties cfg.contents 348 else if cfg._type or "" == "if" then 349 map (mapAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content) 350 else if cfg._type or "" == "override" then 351 map (mapAttrs (n: v: mkOverride cfg.priority v)) (pushDownProperties cfg.content) 352 else # FIXME: handle mkOrder? 353 [ cfg ]; 354 355 /* Given a config value, expand mkMerge properties, and discharge 356 any mkIf conditions. That is, this is the place where mkIf 357 conditions are actually evaluated. The result is a list of 358 config values. For example, mkIf false x yields [], 359 mkIf true x yields [x], and 360 361 mkMerge [ 1 (mkIf true 2) (mkIf true (mkIf false 3)) ] 362 363 yields [ 1 2 ]. 364 */ 365 dischargeProperties = def: 366 if def._type or "" == "merge" then 367 concatMap dischargeProperties def.contents 368 else if def._type or "" == "if" then 369 if def.condition then 370 dischargeProperties def.content 371 else 372 [ ] 373 else 374 [ def ]; 375 376 /* Given a list of config values, process the mkOverride properties, 377 that is, return the values that have the highest (that is, 378 numerically lowest) priority, and strip the mkOverride 379 properties. For example, 380 381 [ { file = "/1"; value = mkOverride 10 "a"; } 382 { file = "/2"; value = mkOverride 20 "b"; } 383 { file = "/3"; value = "z"; } 384 { file = "/4"; value = mkOverride 10 "d"; } 385 ] 386 387 yields 388 389 [ { file = "/1"; value = "a"; } 390 { file = "/4"; value = "d"; } 391 ] 392 393 Note that "z" has the default priority 100. 394 */ 395 filterOverrides = defs: 396 let 397 defaultPrio = 100; 398 getPrio = def: if def.value._type or "" == "override" then def.value.priority else defaultPrio; 399 highestPrio = foldl' (prio: def: min (getPrio def) prio) 9999 defs; 400 strip = def: if def.value._type or "" == "override" then def // { value = def.value.content; } else def; 401 in concatMap (def: if getPrio def == highestPrio then [(strip def)] else []) defs; 402 403 /* Sort a list of properties. The sort priority of a property is 404 1000 by default, but can be overriden by wrapping the property 405 using mkOrder. */ 406 sortProperties = defs: 407 let 408 strip = def: 409 if def.value._type or "" == "order" 410 then def // { value = def.value.content; inherit (def.value) priority; } 411 else def; 412 defs' = map strip defs; 413 compare = a: b: (a.priority or 1000) < (b.priority or 1000); 414 in sort compare defs'; 415 416 /* Hack for backward compatibility: convert options of type 417 optionSet to options of type submodule. FIXME: remove 418 eventually. */ 419 fixupOptionType = loc: opt: 420 let 421 options = opt.options or 422 (throw "Option `${showOption loc'}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}."); 423 f = tp: 424 if tp.name == "option set" || tp.name == "submodule" then 425 throw "The option ${showOption loc} uses submodules without a wrapping type, in ${showFiles opt.declarations}." 426 else if tp.name == "attribute set of option sets" then types.attrsOf (types.submodule options) 427 else if tp.name == "list or attribute set of option sets" then types.loaOf (types.submodule options) 428 else if tp.name == "list of option sets" then types.listOf (types.submodule options) 429 else if tp.name == "null or option set" then types.nullOr (types.submodule options) 430 else tp; 431 in 432 if opt.type.getSubModules or null == null 433 then opt // { type = f (opt.type or types.unspecified); } 434 else opt // { type = opt.type.substSubModules opt.options; options = []; }; 435 436 437 /* Properties. */ 438 439 mkIf = condition: content: 440 { _type = "if"; 441 inherit condition content; 442 }; 443 444 mkAssert = assertion: message: content: 445 mkIf 446 (if assertion then true else throw "\nFailed assertion: ${message}") 447 content; 448 449 mkMerge = contents: 450 { _type = "merge"; 451 inherit contents; 452 }; 453 454 mkOverride = priority: content: 455 { _type = "override"; 456 inherit priority content; 457 }; 458 459 mkOptionDefault = mkOverride 1001; # priority of option defaults 460 mkDefault = mkOverride 1000; # used in config sections of non-user modules to set a default 461 mkForce = mkOverride 50; 462 mkVMOverride = mkOverride 10; # used by ‘nixos-rebuild build-vm’ 463 464 mkStrict = builtins.trace "`mkStrict' is obsolete; use `mkOverride 0' instead." (mkOverride 0); 465 466 mkFixStrictness = id; # obsolete, no-op 467 468 mkOrder = priority: content: 469 { _type = "order"; 470 inherit priority content; 471 }; 472 473 mkBefore = mkOrder 500; 474 mkAfter = mkOrder 1500; 475 476 477 # Convenient property used to transfer all definitions and their 478 # properties from one option to another. This property is useful for 479 # renaming options, and also for including properties from another module 480 # system, including sub-modules. 481 # 482 # { config, options, ... }: 483 # 484 # { 485 # # 'bar' might not always be defined in the current module-set. 486 # config.foo.enable = mkAliasDefinitions (options.bar.enable or {}); 487 # 488 # # 'barbaz' has to be defined in the current module-set. 489 # config.foobar.paths = mkAliasDefinitions options.barbaz.paths; 490 # } 491 # 492 # Note, this is different than taking the value of the option and using it 493 # as a definition, as the new definition will not keep the mkOverride / 494 # mkDefault properties of the previous option. 495 # 496 mkAliasDefinitions = mkAliasAndWrapDefinitions id; 497 mkAliasAndWrapDefinitions = wrap: option: 498 mkMerge 499 (optional (isOption option && option.isDefined) 500 (wrap (mkMerge option.definitions))); 501 502 503 /* Compatibility. */ 504 fixMergeModules = modules: args: evalModules { inherit modules args; check = false; }; 505 506 507 /* Return a module that causes a warning to be shown if the 508 specified option is defined. For example, 509 510 mkRemovedOptionModule [ "boot" "loader" "grub" "bootDevice" ] "<replacement instructions>" 511 512 causes a warning if the user defines boot.loader.grub.bootDevice. 513 514 replacementInstructions is a string that provides instructions on 515 how to achieve the same functionality without the removed option, 516 or alternatively a reasoning why the functionality is not needed. 517 replacementInstructions SHOULD be provided! 518 */ 519 mkRemovedOptionModule = optionName: replacementInstructions: 520 { options, ... }: 521 { options = setAttrByPath optionName (mkOption { 522 visible = false; 523 }); 524 config.warnings = 525 let opt = getAttrFromPath optionName options; in 526 optional opt.isDefined '' 527 The option definition `${showOption optionName}' in ${showFiles opt.files} no longer has any effect; please remove it. 528 ${replacementInstructions}''; 529 }; 530 531 /* Return a module that causes a warning to be shown if the 532 specified "from" option is defined; the defined value is however 533 forwarded to the "to" option. This can be used to rename options 534 while providing backward compatibility. For example, 535 536 mkRenamedOptionModule [ "boot" "copyKernels" ] [ "boot" "loader" "grub" "copyKernels" ] 537 538 forwards any definitions of boot.copyKernels to 539 boot.loader.grub.copyKernels while printing a warning. 540 */ 541 mkRenamedOptionModule = from: to: doRename { 542 inherit from to; 543 visible = false; 544 warn = true; 545 use = builtins.trace "Obsolete option `${showOption from}' is used. It was renamed to `${showOption to}'."; 546 }; 547 548 /* Like mkRenamedOptionModule, but doesn't show a warning. */ 549 mkAliasOptionModule = from: to: doRename { 550 inherit from to; 551 visible = true; 552 warn = false; 553 use = id; 554 }; 555 556 doRename = { from, to, visible, warn, use }: 557 let 558 toOf = attrByPath to 559 (abort "Renaming error: option `${showOption to}' does not exists."); 560 in 561 { config, options, ... }: 562 { options = setAttrByPath from (mkOption { 563 description = "Alias of <option>${showOption to}</option>."; 564 apply = x: use (toOf config); 565 }); 566 config = { 567 warnings = 568 let opt = getAttrFromPath from options; in 569 optional (warn && opt.isDefined) 570 "The option `${showOption from}' defined in ${showFiles opt.files} has been renamed to `${showOption to}'."; 571 } // setAttrByPath to (mkAliasDefinitions (getAttrFromPath from options)); 572 }; 573 574}