query
On this page

evalModules

lib.evalModules

Docs pulled from | This Revision | 10 minutes ago


See https://nixos.org/manual/nixpkgs/unstable/#module-system-lib-evalModules or file://./../doc/module-system/module-system.chapter.md

!!! 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.


Noogle detected

Aliases

Implementation

The following is the current implementation of this function.

evalModules = evalModulesArgs@
                { 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 ? {}
                , # `class`:
                  # A nominal type for modules. When set and non-null, this adds a check to
                  # make sure that only compatible modules are imported.
                  class ? null
                , # 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
      withWarnings = x:
        warnIf (evalModulesArgs?args) "The args argument to evalModules is deprecated. Please set config._module.args instead."
        warnIf (evalModulesArgs?check) "The check argument to evalModules is deprecated. Please set config._module.check instead."
        x;

      legacyModules =
        optional (evalModulesArgs?args) {
          config = {
            _module.args = args;
          };
        }
        ++ optional (evalModulesArgs?check) {
          config = {
            _module.check = mkDefault check;
          };
        };
      regularModules = modules ++ legacyModules;

      # 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.
      #
      # When extended with extendModules or moduleType, a fresh instance of
      # this module is used, to avoid conflicts and allow chaining of
      # extendModules.
      internalModule = rec {
        _file = "lib/modules.nix";

        key = _file;

        options = {
          _module.args = mkOption {
            # Because things like `mkIf` are entirely useless for
            # `_module.args` (because there's no way modules can check which
            # arguments were passed), we'll use `lazyAttrsOf` which drops
            # support for that, in turn it's lazy in its values. This means e.g.
            # a `_module.args.pkgs = import (fetchTarball { ... }) {}` won't
            # start a download when `pkgs` wasn't evaluated.
            type = types.lazyAttrsOf types.raw;
            # Only render documentation once at the root of the option tree,
            # not for all individual submodules.
            # Allow merging option decls to make this internal regardless.
            ${if prefix == []
              then null  # unset => visible
              else "internal"} = true;
            # TODO: Change the type of this option to a submodule with a
            # freeformType, so that individual arguments can be documented
            # separately
            description = ''
              Additional arguments passed to each module in addition to ones
              like `lib`, `config`,
              and `pkgs`, `modulesPath`.

              This option is also available to all submodules. Submodules do not
              inherit args from their parent module, nor do they provide args to
              their parent module or sibling submodules. The sole exception to
              this is the argument `name` which is provided by
              parent modules to a submodule and contains the attribute name
              the submodule is bound to, or a unique generated name if it is
              not bound to an attribute.

              Some arguments are already passed by default, of which the
              following *cannot* be changed with this option:
              - {var}`lib`: The nixpkgs library.
              - {var}`config`: The results of all options after merging the values from all modules together.
              - {var}`options`: The options declared in all modules.
              - {var}`specialArgs`: The `specialArgs` argument passed to `evalModules`.
              - All attributes of {var}`specialArgs`

                Whereas option values can generally depend on other option values
                thanks to laziness, this does not apply to `imports`, which
                must be computed statically before anything else.

                For this reason, callers of the module system can provide `specialArgs`
                which are available during import resolution.

                For NixOS, `specialArgs` includes
                {var}`modulesPath`, which allows you to import
                extra modules from the nixpkgs package tree without having to
                somehow make the module aware of the location of the
                `nixpkgs` or NixOS directories.
                ```
                { modulesPath, ... }: {
                  imports = [
                    (modulesPath + "/profiles/minimal.nix")
                  ];
                }
                ```

              For NixOS, the default value for this option includes at least this argument:
              - {var}`pkgs`: The nixpkgs package set according to
                the {option}`nixpkgs.pkgs` option.
            '';
          };

          _module.check = mkOption {
            type = types.bool;
            internal = true;
            default = true;
            description = "Whether to check whether all option definitions have matching declarations.";
          };

          _module.freeformType = mkOption {
            type = types.nullOr types.optionType;
            internal = true;
            default = null;
            description = ''
              If set, merge all definitions that don't have an associated option
              together using this type. The result then gets combined with the
              values of all declared options to produce the final `
              config` value.

              If this is `null`, definitions without an option
              will throw an error unless {option}`_module.check` is
              turned off.
            '';
          };

          _module.specialArgs = mkOption {
            readOnly = true;
            internal = true;
            description = ''
              Externally provided module arguments that can't be modified from
              within a configuration, but can be used in module imports.
            '';
          };
        };

        config = {
          _module.args = {
            inherit extendModules;
            moduleType = type;
          };
          _module.specialArgs = specialArgs;
        };
      };

      merged =
        let collected = collectModules
          class
          (specialArgs.modulesPath or "")
          (regularModules ++ [ internalModule ])
          ({ inherit lib options config specialArgs; } // specialArgs);
        in mergeModules prefix (reverseList collected);

      options = merged.matchedOptions;

      config =
        let

          # For definitions that have an associated option
          declaredConfig = mapAttrsRecursiveCond (v: ! isOption v) (_: v: v.value) options;

          # If freeformType is set, this is for definitions that don't have an associated option
          freeformConfig =
            let
              defs = map (def: {
                file = def.file;
                value = setAttrByPath def.prefix def.value;
              }) merged.unmatchedDefns;
            in if defs == [] then {}
            else declaredConfig._module.freeformType.merge prefix defs;

        in if declaredConfig._module.freeformType == null then declaredConfig
          # Because all definitions that had an associated option ended in
          # declaredConfig, freeformConfig can only contain the non-option
          # paths, meaning recursiveUpdate will never override any value
          else recursiveUpdate freeformConfig declaredConfig;

      checkUnmatched =
        if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [] then
          let
            firstDef = head merged.unmatchedDefns;
            baseMsg =
              let
                optText = showOption (prefix ++ firstDef.prefix);
                defText =
                  addErrorContext
                    "while evaluating the error message for definitions for `${optText}', which is an option that does not exist"
                    (addErrorContext
                      "while evaluating a definition from `${firstDef.file}'"
                      ( showDefs [ firstDef ])
                    );
              in
                "The option `${optText}' does not exist. Definition values:${defText}";
          in
            if attrNames options == [ "_module" ]
              # No options were declared at all (`_module` is built in)
              # but we do have unmatched definitions, and no freeformType (earlier conditions)
              then
                let
                  optionName = showOption prefix;
                in
                  if optionName == ""
                    then throw ''
                      ${baseMsg}

                      It seems as if you're trying to declare an option by placing it into `config' rather than `options'!
                    ''
                  else
                    throw ''
                      ${baseMsg}

                      However there are no options defined in `${showOption prefix}'. Are you sure you've
                      declared your options properly? This can happen if you e.g. declared your options in `types.submodule'
                      under `config' rather than `options'.
                    ''
            else throw baseMsg
        else null;

      checked = seq checkUnmatched;

      extendModules = extendArgs@{
        modules ? [],
        specialArgs ? {},
        prefix ? [],
        }:
          evalModules (evalModulesArgs // {
            inherit class;
            modules = regularModules ++ modules;
            specialArgs = evalModulesArgs.specialArgs or {} // specialArgs;
            prefix = extendArgs.prefix or evalModulesArgs.prefix or [];
          });

      type = types.submoduleWith {
        inherit modules specialArgs class;
      };

      result = withWarnings {
        _type = "configuration";
        options = checked options;
        config = checked (removeAttrs config [ "_module" ]);
        _module = checked (config._module);
        inherit extendModules type;
        class = class;
      };
    in result;