677 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			677 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
| with import ./lists.nix;
 | ||
| with import ./strings.nix;
 | ||
| with import ./trivial.nix;
 | ||
| with import ./attrsets.nix;
 | ||
| with import ./options.nix;
 | ||
| with import ./debug.nix;
 | ||
| with import ./types.nix;
 | ||
| 
 | ||
| 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.
 | ||
|      !!! 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 {
 | ||
|             type = types.attrsOf types.unspecified;
 | ||
|             internal = true;
 | ||
|             description = "Arguments passed to each module.";
 | ||
|           };
 | ||
| 
 | ||
|           _module.check = mkOption {
 | ||
|             type = 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 ./.; } // specialArgs);
 | ||
| 
 | ||
|       options = mergeModules prefix (reverseList (filterModules (specialArgs.modulesPath or "") 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 options._module.check.value && set ? _definedNames then
 | ||
|           foldl' (res: m:
 | ||
|             foldl' (res: name:
 | ||
|               if set ? ${name} then res else throw "The option `${showOption (prefix ++ [name])}' defined in `${m.file}' does not exist.")
 | ||
|               res m.names)
 | ||
|             res set._definedNames
 | ||
|         else
 | ||
|           res;
 | ||
|       result = { inherit options config; };
 | ||
|     in result;
 | ||
| 
 | ||
| 
 | ||
|  # Filter disabled modules. Modules can be disabled allowing
 | ||
|  # their implementation to be replaced.
 | ||
|  filterModules = modulesPath: modules:
 | ||
|    let
 | ||
|      moduleKey = m: if isString m then toString modulesPath + "/" + m else toString m;
 | ||
|      disabledKeys = map moduleKey (concatMap (m: m.disabledModules) modules);
 | ||
|    in
 | ||
|      filter (m: !(elem m.key disabledKeys)) modules;
 | ||
| 
 | ||
|   /* Close a set of modules under the ‘imports’ relation. */
 | ||
|   closeModules = modules: args:
 | ||
|     let
 | ||
|       toClosureList = file: parentKey: imap (n: x:
 | ||
|         if isAttrs x || isFunction x then
 | ||
|           let key = "${parentKey}:anon-${toString n}"; in
 | ||
|           unifyModuleSyntax file key (unpackSubmodule (applyIfFunction key) x args)
 | ||
|         else
 | ||
|           let file = toString x; key = toString x; in
 | ||
|           unifyModuleSyntax file key (applyIfFunction key (import x) args));
 | ||
|     in
 | ||
|       builtins.genericClosure {
 | ||
|         startSet = toClosureList unknownModule "" modules;
 | ||
|         operator = m: toClosureList m.file m.key m.imports;
 | ||
|       };
 | ||
| 
 | ||
|   /* Massage a module into canonical form, that is, a set consisting
 | ||
|      of ‘options’, ‘config’ and ‘imports’ attributes. */
 | ||
|   unifyModuleSyntax = file: key: m:
 | ||
|     let metaSet = if m ? meta
 | ||
|       then { meta = m.meta; }
 | ||
|       else {};
 | ||
|     in
 | ||
|     if m ? config || m ? options then
 | ||
|       let badAttrs = removeAttrs m ["_file" "key" "disabledModules" "imports" "options" "config" "meta"]; in
 | ||
|       if badAttrs != {} then
 | ||
|         throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by assignments to the top-level attributes `config' or `options'."
 | ||
|       else
 | ||
|         { file = m._file or file;
 | ||
|           key = toString m.key or key;
 | ||
|           disabledModules = m.disabledModules or [];
 | ||
|           imports = m.imports or [];
 | ||
|           options = m.options or {};
 | ||
|           config = mkMerge [ (m.config or {}) metaSet ];
 | ||
|         }
 | ||
|     else
 | ||
|       { file = m._file or file;
 | ||
|         key = toString m.key or key;
 | ||
|         disabledModules = m.disabledModules or [];
 | ||
|         imports = m.require or [] ++ m.imports or [];
 | ||
|         options = {};
 | ||
|         config = mkMerge [ (removeAttrs m ["_file" "key" "disabledModules" "require" "imports"]) metaSet ];
 | ||
|       };
 | ||
| 
 | ||
|   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.
 | ||
|       requiredArgs = builtins.attrNames (builtins.functionArgs f);
 | ||
|       context = name: ''while evaluating the module argument `${name}' in "${key}":'';
 | ||
|       extraArgs = builtins.listToAttrs (map (name: {
 | ||
|         inherit name;
 | ||
|         value = addErrorContext (context name)
 | ||
|           (args.${name} or config._module.args.${name});
 | ||
|       }) requiredArgs);
 | ||
| 
 | ||
|       # 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;
 | ||
| 
 | ||
|   /* 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.
 | ||
|      At the same time, for each option declaration, it will merge the
 | ||
|      corresponding option definitions in all machines, returning them
 | ||
|      in the ‘value’ attribute of each option. */
 | ||
|   mergeModules = prefix: modules:
 | ||
|     mergeModules' prefix modules
 | ||
|       (concatMap (m: map (config: { inherit (m) file; inherit config; }) (pushDownProperties m.config)) modules);
 | ||
| 
 | ||
|   mergeModules' = prefix: options: configs:
 | ||
|     listToAttrs (map (name: {
 | ||
|       # We're descending into attribute ‘name’.
 | ||
|       inherit name;
 | ||
|       value =
 | ||
|         let
 | ||
|           loc = prefix ++ [name];
 | ||
|           # Get all submodules that declare ‘name’.
 | ||
|           decls = concatMap (m:
 | ||
|             if m.options ? ${name}
 | ||
|               then [ { inherit (m) file; options = m.options.${name}; } ]
 | ||
|               else []
 | ||
|             ) options;
 | ||
|           # Get all submodules that define ‘name’.
 | ||
|           defns = concatMap (m:
 | ||
|             if m.config ? ${name}
 | ||
|               then map (config: { inherit (m) file; inherit config; })
 | ||
|                 (pushDownProperties m.config.${name})
 | ||
|               else []
 | ||
|             ) configs;
 | ||
|           nrOptions = count (m: isOption m.options) decls;
 | ||
|           # 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);
 | ||
|             in evalOptionValue loc opt defns'
 | ||
|           else if nrOptions != 0 then
 | ||
|             let
 | ||
|               firstOption = findFirst (m: isOption m.options) "" decls;
 | ||
|               firstNonOption = findFirst (m: !isOption m.options) "" decls;
 | ||
|             in
 | ||
|               throw "The option `${showOption loc}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'."
 | ||
|           else
 | ||
|             mergeModules' loc decls defns;
 | ||
|     }) (concatMap (m: attrNames m.options) options))
 | ||
|     // { _definedNames = map (m: { inherit (m) file; names = attrNames m.config; }) configs; };
 | ||
| 
 | ||
|   /* Merge multiple option declarations into a single declaration.  In
 | ||
|      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 = loc: opts:
 | ||
|     foldl' (res: opt:
 | ||
|       let t  = res.type;
 | ||
|           t' = opt.options.type;
 | ||
|           mergedType = t.typeMerge t'.functor;
 | ||
|           typesMergeable = mergedType != null;
 | ||
|           typeSet = if (bothHave "type") && typesMergeable
 | ||
|                        then { type = mergedType; }
 | ||
|                        else {};
 | ||
|           bothHave = k: opt.options ? ${k} && res ? ${k};
 | ||
|       in
 | ||
|       if bothHave "default" ||
 | ||
|          bothHave "example" ||
 | ||
|          bothHave "description" ||
 | ||
|          bothHave "apply" ||
 | ||
|          (bothHave "type" && (! typesMergeable))
 | ||
|       then
 | ||
|         throw "The option `${showOption loc}' in `${opt.file}' is already declared in ${showFiles res.declarations}."
 | ||
|       else
 | ||
|         let
 | ||
|           /* Add the modules of the current option to the list of modules
 | ||
|              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;
 | ||
|           coerceOption = file: 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 (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;
 | ||
| 
 | ||
|   /* Merge all the definitions of an option to produce the final
 | ||
|      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
 | ||
|           throw "The option `${showOption loc}' is read-only, but it's set multiple times."
 | ||
|         else
 | ||
|           mergeDefinitions loc opt.type defs';
 | ||
| 
 | ||
|       # Check whether the option is defined, and apply the ‘apply’
 | ||
|       # function to the merged value.  This allows options to yield a
 | ||
|       # value computed from the definitions.
 | ||
|       value =
 | ||
|         if !res.isDefined then
 | ||
|           throw "The option `${showOption loc}' is used but not defined."
 | ||
|         else if opt ? apply then
 | ||
|           opt.apply res.mergedValue
 | ||
|         else
 | ||
|           res.mergedValue;
 | ||
| 
 | ||
|     in opt //
 | ||
|       { value = addErrorContext "while evaluating the option `${showOption loc}':" value;
 | ||
|         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; }) (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 "" == "order") defs''
 | ||
|           then sortProperties defs''
 | ||
|           else defs'';
 | ||
|       in defs''';
 | ||
| 
 | ||
|     # Type-check the remaining definitions, and merge them.
 | ||
|     mergedValue = foldl' (res: def:
 | ||
|       if type.check def.value then res
 | ||
|       else throw "The option value `${showOption loc}' in `${def.file}' is not a ${type.description}.")
 | ||
|       (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
 | ||
|      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 "" == "merge" then
 | ||
|       concatMap pushDownProperties cfg.contents
 | ||
|     else if cfg._type or "" == "if" then
 | ||
|       map (mapAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content)
 | ||
|     else if cfg._type or "" == "override" then
 | ||
|       map (mapAttrs (n: v: mkOverride cfg.priority v)) (pushDownProperties cfg.content)
 | ||
|     else # FIXME: handle mkOrder?
 | ||
|       [ cfg ];
 | ||
| 
 | ||
|   /* Given a config value, expand mkMerge properties, and discharge
 | ||
|      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 "" == "merge" then
 | ||
|       concatMap dischargeProperties def.contents
 | ||
|     else if def._type or "" == "if" then
 | ||
|       if isBool def.condition then
 | ||
|         if def.condition then
 | ||
|           dischargeProperties def.content
 | ||
|         else
 | ||
|           [ ]
 | ||
|       else
 | ||
|         throw "‘mkIf’ called with a non-Boolean condition"
 | ||
|     else
 | ||
|       [ def ];
 | ||
| 
 | ||
|   /* Given a list of config values, process the mkOverride properties,
 | ||
|      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:
 | ||
|     let
 | ||
|       defaultPrio = 100;
 | ||
|       getPrio = def: if def.value._type or "" == "override" then def.value.priority else defaultPrio;
 | ||
|       highestPrio = foldl' (prio: def: min (getPrio def) prio) 9999 defs;
 | ||
|       strip = def: if def.value._type or "" == "override" then def // { value = def.value.content; } else def;
 | ||
|     in concatMap (def: if getPrio def == highestPrio then [(strip def)] else []) defs;
 | ||
| 
 | ||
|   /* Sort a list of properties.  The sort priority of a property is
 | ||
|      1000 by default, but can be overridden by wrapping the property
 | ||
|      using mkOrder. */
 | ||
|   sortProperties = defs:
 | ||
|     let
 | ||
|       strip = def:
 | ||
|         if def.value._type or "" == "order"
 | ||
|         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';
 | ||
| 
 | ||
|   /* Hack for backward compatibility: convert options of type
 | ||
|      optionSet to options of type submodule.  FIXME: remove
 | ||
|      eventually. */
 | ||
|   fixupOptionType = loc: opt:
 | ||
|     let
 | ||
|       options = opt.options or
 | ||
|         (throw "Option `${showOption loc'}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}.");
 | ||
|       f = tp:
 | ||
|         let optionSetIn = type: (tp.name == type) && (tp.functor.wrapped.name == "optionSet");
 | ||
|         in
 | ||
|         if tp.name == "option set" || tp.name == "submodule" then
 | ||
|           throw "The option ${showOption loc} uses submodules without a wrapping type, in ${showFiles opt.declarations}."
 | ||
|         else if optionSetIn "attrsOf" then types.attrsOf (types.submodule options)
 | ||
|         else if optionSetIn "loaOf"   then types.loaOf   (types.submodule options)
 | ||
|         else if optionSetIn "listOf"  then types.listOf  (types.submodule options)
 | ||
|         else if optionSetIn "nullOr"  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 = []; };
 | ||
| 
 | ||
| 
 | ||
|   /* Properties. */
 | ||
| 
 | ||
|   mkIf = condition: content:
 | ||
|     { _type = "if";
 | ||
|       inherit condition content;
 | ||
|     };
 | ||
| 
 | ||
|   mkAssert = assertion: message: content:
 | ||
|     mkIf
 | ||
|       (if assertion then true else throw "\nFailed assertion: ${message}")
 | ||
|       content;
 | ||
| 
 | ||
|   mkMerge = contents:
 | ||
|     { _type = "merge";
 | ||
|       inherit contents;
 | ||
|     };
 | ||
| 
 | ||
|   mkOverride = priority: content:
 | ||
|     { _type = "override";
 | ||
|       inherit priority content;
 | ||
|     };
 | ||
| 
 | ||
|   mkOptionDefault = mkOverride 1001; # priority of option defaults
 | ||
|   mkDefault = mkOverride 1000; # used in config sections of non-user modules to set a default
 | ||
|   mkForce = mkOverride 50;
 | ||
|   mkVMOverride = mkOverride 10; # used by ‘nixos-rebuild build-vm’
 | ||
| 
 | ||
|   mkStrict = builtins.trace "`mkStrict' is obsolete; use `mkOverride 0' instead." (mkOverride 0);
 | ||
| 
 | ||
|   mkFixStrictness = id; # obsolete, no-op
 | ||
| 
 | ||
|   mkOrder = priority: content:
 | ||
|     { _type = "order";
 | ||
|       inherit priority content;
 | ||
|     };
 | ||
| 
 | ||
|   mkBefore = mkOrder 500;
 | ||
|   mkAfter = mkOrder 1500;
 | ||
| 
 | ||
| 
 | ||
|   # Convenient property used to transfer all definitions and their
 | ||
|   # properties from one option to another. This property is useful for
 | ||
|   # renaming options, and also for including properties from another module
 | ||
|   # system, including sub-modules.
 | ||
|   #
 | ||
|   #   { config, options, ... }:
 | ||
|   #
 | ||
|   #   {
 | ||
|   #     # 'bar' might not always be defined in the current module-set.
 | ||
|   #     config.foo.enable = mkAliasDefinitions (options.bar.enable or {});
 | ||
|   #
 | ||
|   #     # 'barbaz' has to be defined in the current module-set.
 | ||
|   #     config.foobar.paths = mkAliasDefinitions options.barbaz.paths;
 | ||
|   #   }
 | ||
|   #
 | ||
|   # Note, this is different than taking the value of the option and using it
 | ||
|   # as a definition, as the new definition will not keep the mkOverride /
 | ||
|   # mkDefault properties of the previous option.
 | ||
|   #
 | ||
|   mkAliasDefinitions = mkAliasAndWrapDefinitions id;
 | ||
|   mkAliasAndWrapDefinitions = wrap: option:
 | ||
|     mkMerge
 | ||
|       (optional (isOption option && option.isDefined)
 | ||
|         (wrap (mkMerge option.definitions)));
 | ||
| 
 | ||
| 
 | ||
|   /* Compatibility. */
 | ||
|   fixMergeModules = modules: args: evalModules { inherit modules args; check = false; };
 | ||
| 
 | ||
| 
 | ||
|   /* Return a module that causes a warning to be shown if the
 | ||
|      specified option is defined. For example,
 | ||
| 
 | ||
|        mkRemovedOptionModule [ "boot" "loader" "grub" "bootDevice" ] "<replacement instructions>"
 | ||
| 
 | ||
|      causes a warning if the user defines boot.loader.grub.bootDevice.
 | ||
| 
 | ||
|      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;
 | ||
|       });
 | ||
|       config.warnings =
 | ||
|         let opt = getAttrFromPath optionName options; in
 | ||
|         optional opt.isDefined ''
 | ||
|             The option definition `${showOption optionName}' in ${showFiles opt.files} no longer has any effect; please remove it.
 | ||
|             ${replacementInstructions}'';
 | ||
|     };
 | ||
| 
 | ||
|   /* Return a module that causes a warning to be shown if the
 | ||
|      specified "from" option is defined; the defined value is however
 | ||
|      forwarded to the "to" option. This can be used to rename options
 | ||
|      while providing backward compatibility. For example,
 | ||
| 
 | ||
|        mkRenamedOptionModule [ "boot" "copyKernels" ] [ "boot" "loader" "grub" "copyKernels" ]
 | ||
| 
 | ||
|      forwards any definitions of boot.copyKernels to
 | ||
|      boot.loader.grub.copyKernels while printing a warning.
 | ||
|   */
 | ||
|   mkRenamedOptionModule = from: to: doRename {
 | ||
|     inherit from to;
 | ||
|     visible = false;
 | ||
|     warn = true;
 | ||
|     use = builtins.trace "Obsolete option `${showOption from}' is used. It was renamed to `${showOption to}'.";
 | ||
|   };
 | ||
| 
 | ||
|   /* Return a module that causes a warning to be shown if any of the "from"
 | ||
|      option is defined; the defined values can be used in the "mergeFn" to set
 | ||
|      the "to" value.
 | ||
|      This function can be used to merge multiple options into one that has a
 | ||
|      different type.
 | ||
| 
 | ||
|      "mergeFn" takes the module "config" as a parameter and must return a value
 | ||
|      of "to" 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 "foo"
 | ||
|            else if (value [ "d" "e" "f" ]) == true then "bar"
 | ||
|            else "baz")
 | ||
| 
 | ||
|      - 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 = "_mkMergedOptionModule";
 | ||
|       })) from);
 | ||
| 
 | ||
|       config = {
 | ||
|         warnings = filter (x: x != "") (map (f:
 | ||
|           let val = getAttrFromPath f config;
 | ||
|               opt = getAttrFromPath f options;
 | ||
|           in
 | ||
|           optionalString
 | ||
|             (val != "_mkMergedOptionModule")
 | ||
|             "The option `${showOption f}' defined in ${showFiles opt.files} has been changed to `${showOption to}' that has a different type. Please read `${showOption to}' documentation and update your configuration accordingly."
 | ||
|         ) from);
 | ||
|       } // setAttrByPath to (mkMerge
 | ||
|              (optional
 | ||
|                (any (f: (getAttrFromPath f config) != "_mkMergedOptionModule") from)
 | ||
|                (mergeFn config)));
 | ||
|     };
 | ||
| 
 | ||
|   /* Single "from" version of mkMergedOptionModule.
 | ||
|      Return a module that causes a warning to be shown if the "from" option is
 | ||
|      defined; the defined value can be used in the "mergeFn" to set the "to"
 | ||
|      value.
 | ||
|      This function can be used to change an option into another that has a
 | ||
|      different type.
 | ||
| 
 | ||
|      "mergeFn" takes the module "config" as a parameter and must return a value of
 | ||
|      "to" option type.
 | ||
| 
 | ||
|        mkChangedOptionModule [ "a" "b" "c" ] [ "x" "y" "z" ]
 | ||
|          (config:
 | ||
|            let value = getAttrFromPath [ "a" "b" "c" ] config;
 | ||
|            in
 | ||
|            if   value > 100 then "high"
 | ||
|            else "normal")
 | ||
| 
 | ||
|      - 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;
 | ||
| 
 | ||
|   /* Like ‘mkRenamedOptionModule’, but doesn't show a warning. */
 | ||
|   mkAliasOptionModule = from: to: doRename {
 | ||
|     inherit from to;
 | ||
|     visible = true;
 | ||
|     warn = false;
 | ||
|     use = id;
 | ||
|   };
 | ||
| 
 | ||
|   doRename = { from, to, visible, warn, use }:
 | ||
|     let
 | ||
|       toOf = attrByPath to
 | ||
|         (abort "Renaming error: option `${showOption to}' does not exists.");
 | ||
|     in
 | ||
|       { config, options, ... }:
 | ||
|       { options = setAttrByPath from (mkOption {
 | ||
|           description = "Alias of <option>${showOption to}</option>.";
 | ||
|           apply = x: use (toOf config);
 | ||
|         });
 | ||
|         config = {
 | ||
|           warnings =
 | ||
|             let opt = getAttrFromPath from options; in
 | ||
|             optional (warn && opt.isDefined)
 | ||
|               "The option `${showOption from}' defined in ${showFiles opt.files} has been renamed to `${showOption to}'.";
 | ||
|         } // setAttrByPath to (mkAliasDefinitions (getAttrFromPath from options));
 | ||
|       };
 | ||
| 
 | ||
| }
 | 
