diff --git a/lib/modules.nix b/lib/modules.nix index d0b8f90e5ce6..dcede0c46c63 100644 --- a/lib/modules.nix +++ b/lib/modules.nix @@ -9,25 +9,69 @@ rec { /* Evaluate a set of modules. The result is a set of two attributes: ‘options’: the nested set of all option declarations, - and ‘config’: the nested set of all option values. */ - evalModules = { modules, prefix ? [], args ? {}, check ? true }: + 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 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 - args' = args // { lib = import ./.; } // result; - closed = closeModules modules args'; + # 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 { + type = types.attrsOf types.unspecified; + internal = true; + description = "Arguments passed to each module."; + }; + + _module.check = mkOption { + type = types.uniq types.bool; + internal = true; + default = check; + description = "Whether to check whether all option definitions have matching declarations."; + }; + }; + + config = { + _module.args = args; + }; + }; + + closed = closeModules (modules ++ [ internalModule ]) { inherit config options; lib = import ./.; }; + # Note: the list of modules is reversed to maintain backward # compatibility with the old module system. Not sure if this is # the most sensible policy. options = mergeModules prefix (reverseList closed); + # Traverse options and extract the option values into the final # config set. At the same time, check whether all option # definitions have matching declarations. + # !!! _module.check's value can't depend on any other config values + # without an infinite recursion. One way around this is to make the + # 'config' passed around to the modules be unconditionally unchecked, + # and only do the check in 'result'. config = yieldConfig prefix options; yieldConfig = prefix: set: let res = removeAttrs (mapAttrs (n: v: if isOption v then v.value else yieldConfig (prefix ++ [n]) v) set) ["_definedNames"]; in - if check && set ? _definedNames then + if options._module.check.value && set ? _definedNames then fold (m: res: fold (name: res: if set ? ${name} then res else throw "The option `${showOption (prefix ++ [name])}' defined in `${m.file}' does not exist.") @@ -43,7 +87,7 @@ rec { let toClosureList = file: parentKey: imap (n: x: if isAttrs x || isFunction x then - unifyModuleSyntax file "${parentKey}:anon-${toString n}" (applyIfFunction x args) + unifyModuleSyntax file "${parentKey}:anon-${toString n}" (unpackSubmodule applyIfFunction x args) else unifyModuleSyntax (toString x) (toString x) (applyIfFunction (import x) args)); in @@ -74,7 +118,39 @@ rec { config = removeAttrs m ["key" "_file" "require" "imports"]; }; - applyIfFunction = f: arg: if isFunction f then f arg else f; + applyIfFunction = f: arg@{ 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. + requiredArgs = builtins.attrNames (builtins.functionArgs f); + extraArgs = builtins.listToAttrs (map (name: { + inherit name; + value = config._module.args.${name}; + }) requiredArgs); + in f (extraArgs // arg) + else + f; + + /* We have to pack and unpack submodules. We cannot wrap the expected + result of the function as we would no longer be able to list the arguments + of the submodule. (see applyIfFunction) */ + unpackSubmodule = unpack: m: args: + if isType "submodule" m then + { _file = m.file; } // (unpack m.submodule args) + else unpack m args; + + packSubmodule = file: m: + { _type = "submodule"; file = file; submodule = m; }; /* Merge a list of modules. This will recurse over the option declarations in all modules, combining them into a single set. @@ -106,12 +182,9 @@ rec { else [] ) configs); nrOptions = count (m: isOption m.options) decls; - # Process mkMerge and mkIf properties. - defns' = concatMap (m: - if m.config ? ${name} - then map (m': { inherit (m) file; value = m'; }) (dischargeProperties m.config.${name}) - else [] - ) configs; + # Extract the definitions for this loc + defns' = map (m: { inherit (m) file; value = m.config.${name}; }) + (filter (m: m.config ? ${name}) configs); in if nrOptions == length decls then let opt = fixupOptionType loc (mergeOptionDecls loc decls); @@ -156,15 +229,12 @@ rec { 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; - addModuleFile = m: - if isFunction m then args: { _file = opt.file; } // (m args) - else { _file = opt.file; } // m; coerceOption = file: opt: - if isFunction opt then args: { _file = file; } // (opt args) - else { _file = file; options = opt; }; + if isFunction opt then packSubmodule file opt + else packSubmodule file { options = opt; }; getSubModules = opt.options.type.getSubModules or null; submodules = - if getSubModules != null then map addModuleFile getSubModules ++ res.options + 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 // @@ -177,27 +247,17 @@ rec { config value. */ evalOptionValue = loc: opt: defs: let - # Process mkOverride properties, adding in the default - # value specified in the option declaration (if any). - defsFinal' = filterOverrides - ((if opt ? default then [{ file = head opt.declarations; value = mkOptionDefault opt.default; }] else []) ++ defs); - # Sort mkOrder properties. - defsFinal = - # Avoid sorting if we don't have to. - if any (def: def.value._type or "" == "order") defsFinal' - then sortProperties defsFinal' - else defsFinal'; + # 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 + inherit (mergeDefinitions loc opt.type defs') isDefined defsFinal mergedValue; files = map (def: def.file) defsFinal; - # Type-check the remaining definitions, and merge them if - # possible. merged = - if defsFinal == [] then - throw "The option `${showOption loc}' is used but not defined." - else - fold (def: res: - if opt.type.check def.value then res - else throw "The option value `${showOption loc}' in `${def.file}' is not a ${opt.type.name}.") - (opt.type.merge loc defsFinal) defsFinal; + if isDefined then mergedValue + else throw "The option `${showOption loc}' is used but not defined."; + # Finally, apply the ‘apply’ function to the merged # value. This allows options to yield a value computed # from the definitions. @@ -205,10 +265,42 @@ rec { in opt // { value = addErrorContext "while evaluating the option `${showOption loc}':" value; definitions = map (def: def.value) defsFinal; - isDefined = defsFinal != []; - inherit files; + inherit isDefined files; }; + # Merge definitions of a value of a given type + mergeDefinitions = loc: type: defs: rec { + defsFinal = + let + # Process mkMerge and mkIf properties + processIfAndMerge = defs: concatMap (m: + map (value: { inherit (m) file; inherit value; }) (dischargeProperties m.value) + ) defs; + + # Process mkOverride properties + processOverride = defs: filterOverrides defs; + + # Sort mkOrder properties + processOrder = defs: + # Avoid sorting if we don't have to. + if any (def: def.value._type or "" == "order") defs + then sortProperties defs + else defs; + in + processOrder (processOverride (processIfAndMerge defs)); + + # Type-check the remaining definitions, and merge them + mergedValue = fold (def: res: + if type.check def.value then res + else throw "The option value `${showOption loc}' in `${def.file}' is not a ${type.name}.") + (type.merge loc defsFinal) defsFinal; + + isDefined = defsFinal != []; + optionalValue = + if isDefined then { value = mergedValue; } + else {}; + }; + /* Given a config set, expand mkMerge properties, and push down the other properties into the children. The result is a list of config sets that do not have properties at top-level. For diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index 58231a356369..66c6f560fbe8 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -57,13 +57,17 @@ checkConfigError() { fi } +# Check boolean option. checkConfigOutput "false" config.enable ./declare-enable.nix checkConfigError 'The option .* defined in .* does not exist.' config.enable ./define-enable.nix + +# Check mkForce without submodules. set -- config.enable ./declare-enable.nix ./define-enable.nix checkConfigOutput "true" "$@" checkConfigOutput "false" "$@" ./define-force-enable.nix checkConfigOutput "false" "$@" ./define-enable-force.nix +# Check mkForce with option and submodules. checkConfigError 'attribute .*foo.* .* not found' config.loaOfSub.foo.enable ./declare-loaOfSub-any-enable.nix checkConfigOutput 'false' config.loaOfSub.foo.enable ./declare-loaOfSub-any-enable.nix ./define-loaOfSub-foo.nix set -- config.loaOfSub.foo.enable ./declare-loaOfSub-any-enable.nix ./define-loaOfSub-foo-enable.nix @@ -73,6 +77,7 @@ checkConfigOutput 'false' "$@" ./define-loaOfSub-force-foo-enable.nix checkConfigOutput 'false' "$@" ./define-loaOfSub-foo-force-enable.nix checkConfigOutput 'false' "$@" ./define-loaOfSub-foo-enable-force.nix +# Check overriding effect of mkForce on submodule definitions. checkConfigError 'attribute .*bar.* .* not found' config.loaOfSub.bar.enable ./declare-loaOfSub-any-enable.nix ./define-loaOfSub-foo.nix checkConfigOutput 'false' config.loaOfSub.bar.enable ./declare-loaOfSub-any-enable.nix ./define-loaOfSub-foo.nix ./define-loaOfSub-bar.nix set -- config.loaOfSub.bar.enable ./declare-loaOfSub-any-enable.nix ./define-loaOfSub-foo.nix ./define-loaOfSub-bar-enable.nix @@ -82,6 +87,26 @@ checkConfigError 'attribute .*bar.* .* not found' "$@" ./define-loaOfSub-force-f checkConfigOutput 'true' "$@" ./define-loaOfSub-foo-force-enable.nix checkConfigOutput 'true' "$@" ./define-loaOfSub-foo-enable-force.nix +# Check mkIf with submodules. +checkConfigError 'attribute .*foo.* .* not found' config.loaOfSub.foo.enable ./declare-enable.nix ./declare-loaOfSub-any-enable.nix +set -- config.loaOfSub.foo.enable ./declare-enable.nix ./declare-loaOfSub-any-enable.nix +checkConfigError 'attribute .*foo.* .* not found' "$@" ./define-if-loaOfSub-foo-enable.nix +checkConfigError 'attribute .*foo.* .* not found' "$@" ./define-loaOfSub-if-foo-enable.nix +checkConfigError 'attribute .*foo.* .* not found' "$@" ./define-loaOfSub-foo-if-enable.nix +checkConfigOutput 'false' "$@" ./define-loaOfSub-foo-enable-if.nix +checkConfigOutput 'true' "$@" ./define-enable.nix ./define-if-loaOfSub-foo-enable.nix +checkConfigOutput 'true' "$@" ./define-enable.nix ./define-loaOfSub-if-foo-enable.nix +checkConfigOutput 'true' "$@" ./define-enable.nix ./define-loaOfSub-foo-if-enable.nix +checkConfigOutput 'true' "$@" ./define-enable.nix ./define-loaOfSub-foo-enable-if.nix + +# Check _module.args. +checkConfigOutput "true" config.enable ./declare-enable.nix ./custom-arg-define-enable.nix + +# Check _module.check. +set -- config.enable ./declare-enable.nix ./define-enable.nix ./define-loaOfSub-foo.nix +checkConfigError 'The option .* defined in .* does not exist.' "$@" +checkConfigOutput "true" "$@" ./define-module-check.nix + cat <"]); getSubModules = elemType.getSubModules; substSubModules = m: attrsOf (elemType.substSubModules m); @@ -150,10 +157,7 @@ rec { attrOnly = attrsOf elemType; in mkOptionType { name = "list or attribute set of ${elemType.name}s"; - check = x: - if isList x then listOnly.check x - else if isAttrs x then attrOnly.check x - else false; + check = x: isList x || isAttrs x; merge = loc: defs: attrOnly.merge loc (imap convertIfList defs); getSubOptions = prefix: elemType.getSubOptions (prefix ++ [""]); getSubModules = elemType.getSubModules; @@ -194,7 +198,11 @@ rec { let coerce = def: if isFunction def then def else { config = def; }; modules = opts' ++ map (def: { _file = def.file; imports = [(coerce def.value)]; }) defs; - in (evalModules { inherit modules; args.name = last loc; prefix = loc; }).config; + in (evalModules { + inherit modules; + args.name = last loc; + prefix = loc; + }).config; getSubOptions = prefix: (evalModules { modules = opts'; inherit prefix; # FIXME: hack to get shit to evaluate. diff --git a/nixos/lib/eval-config.nix b/nixos/lib/eval-config.nix index 08adcf3a0078..adacbd0863e3 100644 --- a/nixos/lib/eval-config.nix +++ b/nixos/lib/eval-config.nix @@ -2,27 +2,51 @@ # configuration object (`config') from which we can retrieve option # values. -{ system ? builtins.currentSystem -, pkgs ? null -, baseModules ? import ../modules/module-list.nix -, extraArgs ? {} +# !!! Please think twice before adding to this argument list! +# Ideally eval-config.nix would be an extremely thin wrapper +# around lib.evalModules, so that modular systems that have nixos configs +# as subcomponents (e.g. the container feature, or nixops if network +# expressions are ever made modular at the top level) can just use +# types.submodule instead of using eval-config.nix +{ # !!! system can be set modularly, would be nice to remove + system ? builtins.currentSystem +, # !!! is this argument needed any more? The pkgs argument can + # be set modularly anyway. + pkgs ? null +, # !!! what do we gain by making this configurable? + baseModules ? import ../modules/module-list.nix +, # !!! See comment about args in lib/modules.nix + extraArgs ? {} , modules -, check ? true +, # !!! See comment about check in lib/modules.nix + check ? true , prefix ? [] +, lib ? import ../../lib }: let extraArgs_ = extraArgs; pkgs_ = pkgs; system_ = system; extraModules = let e = builtins.getEnv "NIXOS_EXTRA_MODULE_PATH"; in if e == "" then [] else [(import (builtins.toPath e))]; +in + +let + pkgsModule = rec { + _file = ./eval-config.nix; + key = _file; + config = { + nixpkgs.system = lib.mkDefault system_; + _module.args.pkgs = lib.mkIf (pkgs_ != null) (lib.mkForce pkgs_); + }; + }; + in rec { # Merge the option definitions in all modules, forming the full # system configuration. - inherit (pkgs.lib.evalModules { - inherit prefix; - modules = modules ++ extraModules ++ baseModules; + inherit (lib.evalModules { + inherit prefix check; + modules = modules ++ extraModules ++ baseModules ++ [ pkgsModule ]; args = extraArgs; - check = check && options.environment.checkConfigurationOptions.value; }) config options; # These are the extra arguments passed to every module. In @@ -33,40 +57,8 @@ in rec { # the 64-bit package anyway. However, it would be cleaner to respect # nixpkgs.config here. extraArgs = extraArgs_ // { - inherit pkgs modules baseModules; - modulesPath = ../modules; - pkgs_i686 = import ./nixpkgs.nix { system = "i686-linux"; config.allowUnfree = true; }; - utils = import ./utils.nix pkgs; + inherit modules baseModules; }; - # Import Nixpkgs, allowing the NixOS option nixpkgs.config to - # specify the Nixpkgs configuration (e.g., to set package options - # such as firefox.enableGeckoMediaPlayer, or to apply global - # overrides such as changing GCC throughout the system), and the - # option nixpkgs.system to override the platform type. This is - # tricky, because we have to prevent an infinite recursion: "pkgs" - # is passed as an argument to NixOS modules, but the value of "pkgs" - # depends on config.nixpkgs.config, which we get from the modules. - # So we call ourselves here with "pkgs" explicitly set to an - # instance that doesn't depend on nixpkgs.config. - pkgs = - if pkgs_ != null - then pkgs_ - else import ./nixpkgs.nix ( - let - system = if nixpkgsOptions.system != "" then nixpkgsOptions.system else system_; - nixpkgsOptions = (import ./eval-config.nix { - inherit system extraArgs modules prefix; - # For efficiency, leave out most NixOS modules; they don't - # define nixpkgs.config, so it's pointless to evaluate them. - baseModules = [ ../modules/misc/nixpkgs.nix ../modules/config/no-x-libs.nix ]; - pkgs = import ./nixpkgs.nix { system = system_; config = {}; }; - check = false; - }).config.nixpkgs; - in - { - inherit system; - inherit (nixpkgsOptions) config; - }); - + inherit (config._module.args) pkgs; } diff --git a/nixos/modules/config/fonts/fontconfig-ultimate.nix b/nixos/modules/config/fonts/fontconfig-ultimate.nix index 853f253ff9bc..02568f9de51e 100644 --- a/nixos/modules/config/fonts/fontconfig-ultimate.nix +++ b/nixos/modules/config/fonts/fontconfig-ultimate.nix @@ -1,6 +1,6 @@ -{ config, pkgs, ... }: +{ config, pkgs, lib, ... }: -with pkgs.lib; +with lib; let fcBool = x: if x then "true" else "false"; in diff --git a/nixos/modules/misc/check-config.nix b/nixos/modules/misc/check-config.nix deleted file mode 100644 index e9803de21961..000000000000 --- a/nixos/modules/misc/check-config.nix +++ /dev/null @@ -1,15 +0,0 @@ -{ lib, ... }: - -with lib; - -{ - options = { - environment.checkConfigurationOptions = mkOption { - type = types.bool; - default = true; - description = '' - Whether to check the validity of the entire configuration. - ''; - }; - }; -} diff --git a/nixos/modules/misc/extra-arguments.nix b/nixos/modules/misc/extra-arguments.nix new file mode 100644 index 000000000000..c2c8903546d5 --- /dev/null +++ b/nixos/modules/misc/extra-arguments.nix @@ -0,0 +1,14 @@ +{ lib, pkgs, config, ... }: + +{ + _module.args = { + modulesPath = ../.; + + pkgs_i686 = import ../../lib/nixpkgs.nix { + system = "i686-linux"; + config.allowUnfree = true; + }; + + utils = import ../../lib/utils.nix pkgs; + }; +} diff --git a/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix index f41c8817ba4a..395ba82f2d16 100644 --- a/nixos/modules/misc/nixpkgs.nix +++ b/nixos/modules/misc/nixpkgs.nix @@ -60,6 +60,7 @@ in nixpkgs.system = mkOption { type = types.str; + default = builtins.currentSystem; description = '' Specifies the Nix platform type for which NixOS should be built. If unset, it defaults to the platform type of your host system. @@ -71,6 +72,10 @@ in }; config = { - nixpkgs.system = mkDefault pkgs.stdenv.system; + _module.args.pkgs = import ../../lib/nixpkgs.nix { + system = config.nixpkgs.system; + + inherit (config.nixpkgs) config; + }; }; } diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 33830dbc8102..640c23762d6d 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -44,8 +44,8 @@ ./installer/tools/nixos-checkout.nix ./installer/tools/tools.nix ./misc/assertions.nix - ./misc/check-config.nix ./misc/crashdump.nix + ./misc/extra-arguments.nix ./misc/ids.nix ./misc/lib.nix ./misc/locate.nix diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix index 07fe568697b6..d90cffbd967f 100644 --- a/nixos/modules/rename.nix +++ b/nixos/modules/rename.nix @@ -135,6 +135,8 @@ in zipModules ([] ++ obsolete [ "services" "mysql55" ] [ "services" "mysql" ] +++ obsolete [ "environment" "checkConfigurationOptions" ] [ "_module" "check" ] + # XBMC ++ obsolete [ "services" "xserver" "windowManager" "xbmc" ] [ "services" "xserver" "desktopManager" "kodi" ] ++ obsolete [ "services" "xserver" "desktopManager" "xbmc" ] [ "services" "xserver" "desktopManager" "kodi" ] diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix index e81278a95d5c..631e8317cb4c 100644 --- a/nixos/modules/security/pam.nix +++ b/nixos/modules/security/pam.nix @@ -6,8 +6,9 @@ with lib; let + parentConfig = config; - pamOpts = args: { + pamOpts = { config, name, ... }: let cfg = config; in let config = parentConfig; in { options = { @@ -180,8 +181,8 @@ let }; - config = let cfg = args.config; in { - name = mkDefault args.name; + config = { + name = mkDefault name; setLoginUid = mkDefault cfg.startSession; limits = mkDefault config.security.pam.loginLimits; diff --git a/nixos/modules/services/misc/nixos-manual.nix b/nixos/modules/services/misc/nixos-manual.nix index c0d7885280a5..f73c4102cfe5 100644 --- a/nixos/modules/services/misc/nixos-manual.nix +++ b/nixos/modules/services/misc/nixos-manual.nix @@ -3,7 +3,7 @@ # of the virtual consoles. The latter is useful for the installation # CD. -{ config, lib, pkgs, baseModules, ... } @ extraArgs: +{ config, lib, pkgs, baseModules, ... }: with lib; @@ -18,7 +18,7 @@ let eval = evalModules { modules = [ versionModule ] ++ baseModules; - args = (removeAttrs extraArgs ["config" "options"]) // { modules = [ ]; }; + args = (config._module.args) // { modules = [ ]; }; }; manual = import ../../../doc/manual { diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index 6d9871a2f6f9..29c449d4d0be 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -1,7 +1,7 @@ { config, lib, pkgs, utils, ... }: -with lib; with utils; +with lib; with import ./systemd-unit-options.nix { inherit config lib; }; let diff --git a/nixos/modules/virtualisation/amazon-config.nix b/nixos/modules/virtualisation/amazon-config.nix index e816ed2d183a..a27e52a8e68c 100644 --- a/nixos/modules/virtualisation/amazon-config.nix +++ b/nixos/modules/virtualisation/amazon-config.nix @@ -1,5 +1,3 @@ -{ config, pkgs, modulesPath, ... }: - { - imports = [ "${modulesPath}/virtualisation/amazon-image.nix" ]; + imports = [ ./amazon-image.nix ]; }