summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorpennae <github@quasiparticle.net>2021-11-19 00:26:27 +0100
committerpennae <github@quasiparticle.net>2022-01-02 19:46:13 +0100
commitfc614c37c653637e5475a0b0a987489b4d1f351d (patch)
tree7b2470a3a67979ddcc51f18bb0d424f8e95908b1 /nixos
parent55daffc1c943bddb71dc89a606f8284f6d50f5bd (diff)
downloadnixpkgs-fc614c37c653637e5475a0b0a987489b4d1f351d.tar
nixpkgs-fc614c37c653637e5475a0b0a987489b4d1f351d.tar.gz
nixpkgs-fc614c37c653637e5475a0b0a987489b4d1f351d.tar.bz2
nixpkgs-fc614c37c653637e5475a0b0a987489b4d1f351d.tar.lz
nixpkgs-fc614c37c653637e5475a0b0a987489b4d1f351d.tar.xz
nixpkgs-fc614c37c653637e5475a0b0a987489b4d1f351d.tar.zst
nixpkgs-fc614c37c653637e5475a0b0a987489b4d1f351d.zip
nixos/documentation: split options doc build
most modules can be evaluated for their documentation in a very
restricted environment that doesn't include all of nixpkgs. this
evaluation can then be cached and reused for subsequent builds, merging
only documentation that has changed into the cached set. since nixos
ships with a large number of modules of which only a few are used in any
given config this can save evaluation a huge percentage of nixos
options available in any given config.

in tests of this caching, despite having to copy most of nixos/, saves
about 80% of the time needed to build the system manual, or about two
second on the machine used for testing. build time for a full system
config shrank from 9.4s to 7.4s, while turning documentation off
entirely shortened the build to 7.1s.
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/default.nix8
-rw-r--r--nixos/doc/manual/development/meta-attributes.section.md28
-rw-r--r--nixos/doc/manual/from_md/development/meta-attributes.section.xml44
-rw-r--r--nixos/lib/eval-cacheable-options.nix53
-rw-r--r--nixos/lib/make-options-doc/default.nix16
-rw-r--r--nixos/lib/make-options-doc/mergeJSON.py71
-rw-r--r--nixos/modules/config/qt5.nix3
-rw-r--r--nixos/modules/i18n/input-method/fcitx.nix3
-rw-r--r--nixos/modules/i18n/input-method/ibus.nix3
-rw-r--r--nixos/modules/i18n/input-method/kime.nix4
-rw-r--r--nixos/modules/misc/documentation.nix95
-rw-r--r--nixos/modules/misc/meta.nix15
-rw-r--r--nixos/modules/misc/nixpkgs.nix3
-rw-r--r--nixos/modules/misc/version.nix2
-rw-r--r--nixos/modules/programs/dmrconfig.nix2
-rw-r--r--nixos/modules/programs/gnupg.nix2
-rw-r--r--nixos/modules/programs/tmux.nix3
-rw-r--r--nixos/modules/services/backup/sanoid.nix5
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire-media-session.nix2
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire.nix2
-rw-r--r--nixos/modules/services/hardware/thinkfan.nix3
-rw-r--r--nixos/modules/services/misc/matrix-appservice-irc.nix3
-rw-r--r--nixos/modules/services/networking/dnscrypt-proxy2.nix3
-rw-r--r--nixos/modules/services/networking/kea.nix2
-rw-r--r--nixos/modules/services/networking/searx.nix3
-rw-r--r--nixos/modules/services/security/vaultwarden/default.nix3
-rw-r--r--nixos/modules/services/web-apps/dex.nix3
-rw-r--r--nixos/modules/services/web-apps/gerrit.nix2
-rw-r--r--nixos/modules/services/web-apps/jirafeau.nix3
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix2
-rw-r--r--nixos/modules/services/web-apps/powerdns-admin.nix3
-rw-r--r--nixos/modules/services/x11/xserver.nix2
-rw-r--r--nixos/modules/system/activation/top-level.nix2
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix3
-rw-r--r--nixos/modules/virtualisation/xen-dom0.nix3
35 files changed, 383 insertions, 21 deletions
diff --git a/nixos/doc/manual/default.nix b/nixos/doc/manual/default.nix
index 31b6da01c6b..9bc63686fa3 100644
--- a/nixos/doc/manual/default.nix
+++ b/nixos/doc/manual/default.nix
@@ -1,4 +1,4 @@
-{ pkgs, options, config, version, revision, extraSources ? [] }:
+{ pkgs, options, config, version, revision, extraSources ? [], baseOptionsJSON ? null, prefix ? ../../.. }:
 
 with pkgs;
 
@@ -11,11 +11,11 @@ let
   #
   # E.g. if some `options` came from modules in ${pkgs.customModules}/nix,
   # you'd need to include `extraSources = [ pkgs.customModules ]`
-  prefixesToStrip = map (p: "${toString p}/") ([ ../../.. ] ++ extraSources);
+  prefixesToStrip = map (p: "${toString p}/") ([ prefix ] ++ extraSources);
   stripAnyPrefixes = lib.flip (lib.foldr lib.removePrefix) prefixesToStrip;
 
   optionsDoc = buildPackages.nixosOptionsDoc {
-    inherit options revision;
+    inherit options revision baseOptionsJSON;
     transformOptions = opt: opt // {
       # Clean up declaration sites to not refer to the NixOS source tree.
       declarations = map stripAnyPrefixes opt.declarations;
@@ -161,7 +161,7 @@ let
 in rec {
   inherit generatedSources;
 
-  inherit (optionsDoc) optionsJSON optionsDocBook;
+  inherit (optionsDoc) optionsJSON optionsNix optionsDocBook;
 
   # Generate the NixOS manual.
   manualHTML = runCommand "nixos-manual-html"
diff --git a/nixos/doc/manual/development/meta-attributes.section.md b/nixos/doc/manual/development/meta-attributes.section.md
index ca4ba007f7d..946c08efd0a 100644
--- a/nixos/doc/manual/development/meta-attributes.section.md
+++ b/nixos/doc/manual/development/meta-attributes.section.md
@@ -5,7 +5,7 @@ extra information. Module meta attributes are defined in the `meta.nix`
 special module.
 
 `meta` is a top level attribute like `options` and `config`. Available
-meta-attributes are `maintainers` and `doc`.
+meta-attributes are `maintainers`, `doc`, and `buildDocsInSandbox`.
 
 Each of the meta-attributes must be defined at most once per module
 file.
@@ -24,6 +24,7 @@ file.
   meta = {
     maintainers = with lib.maintainers; [ ericsagnes ];
     doc = ./default.xml;
+    buildDocsInSandbox = true;
   };
 }
 ```
@@ -38,3 +39,28 @@ file.
     ```ShellSession
     $ nix-build nixos/release.nix -A manual.x86_64-linux
     ```
+
+-  `buildDocsInSandbox` indicates whether the option documentation for the
+   module can be built in a derivation sandbox. This option is currently only
+   honored for modules shipped by nixpkgs. User modules and modules taken from
+   `NIXOS_EXTRA_MODULE_PATH` are always built outside of the sandbox, as has
+   been the case in previous releases.
+
+   Building NixOS option documentation in a sandbox allows caching of the built
+   documentation, which greatly decreases the amount of time needed to evaluate
+   a system configuration that has NixOS documentation enabled. The sandbox also
+   restricts which attributes may be referenced by documentation attributes
+   (such as option descriptions) to the `options` and `lib` module arguments and
+   the `pkgs.formats` attribute of the `pkgs` argument, `config` and the rest of
+   `pkgs` are disallowed and will cause doc build failures when used. This
+   restriction is necessary because we cannot reproduce the full nixpkgs
+   instantiation with configuration and overlays from a system configuration
+   inside the sandbox. The `options` argument only includes options of modules
+   that are also built inside the sandbox, referencing an option of a module
+   that isn't built in the sandbox is also forbidden.
+
+   The default is `true` and should usually not be changed; set it to `false`
+   only if the module requires access to `pkgs` in its documentation (e.g.
+   because it loads information from a linked package to build an option type)
+   or if its documentation depends on other modules that also aren't sandboxed
+   (e.g. by using types defined in the other module).
diff --git a/nixos/doc/manual/from_md/development/meta-attributes.section.xml b/nixos/doc/manual/from_md/development/meta-attributes.section.xml
index f535d94602b..1eb6e0f3036 100644
--- a/nixos/doc/manual/from_md/development/meta-attributes.section.xml
+++ b/nixos/doc/manual/from_md/development/meta-attributes.section.xml
@@ -8,8 +8,8 @@
   <para>
     <literal>meta</literal> is a top level attribute like
     <literal>options</literal> and <literal>config</literal>. Available
-    meta-attributes are <literal>maintainers</literal> and
-    <literal>doc</literal>.
+    meta-attributes are <literal>maintainers</literal>,
+    <literal>doc</literal>, and <literal>buildDocsInSandbox</literal>.
   </para>
   <para>
     Each of the meta-attributes must be defined at most once per module
@@ -29,6 +29,7 @@
   meta = {
     maintainers = with lib.maintainers; [ ericsagnes ];
     doc = ./default.xml;
+    buildDocsInSandbox = true;
   };
 }
 </programlisting>
@@ -51,5 +52,44 @@
 $ nix-build nixos/release.nix -A manual.x86_64-linux
 </programlisting>
     </listitem>
+    <listitem>
+      <para>
+        <literal>buildDocsInSandbox</literal> indicates whether the
+        option documentation for the module can be built in a derivation
+        sandbox. This option is currently only honored for modules
+        shipped by nixpkgs. User modules and modules taken from
+        <literal>NIXOS_EXTRA_MODULE_PATH</literal> are always built
+        outside of the sandbox, as has been the case in previous
+        releases.
+      </para>
+      <para>
+        Building NixOS option documentation in a sandbox allows caching
+        of the built documentation, which greatly decreases the amount
+        of time needed to evaluate a system configuration that has NixOS
+        documentation enabled. The sandbox also restricts which
+        attributes may be referenced by documentation attributes (such
+        as option descriptions) to the <literal>options</literal> and
+        <literal>lib</literal> module arguments and the
+        <literal>pkgs.formats</literal> attribute of the
+        <literal>pkgs</literal> argument, <literal>config</literal> and
+        the rest of <literal>pkgs</literal> are disallowed and will
+        cause doc build failures when used. This restriction is
+        necessary because we cannot reproduce the full nixpkgs
+        instantiation with configuration and overlays from a system
+        configuration inside the sandbox. The <literal>options</literal>
+        argument only includes options of modules that are also built
+        inside the sandbox, referencing an option of a module that isn’t
+        built in the sandbox is also forbidden.
+      </para>
+      <para>
+        The default is <literal>true</literal> and should usually not be
+        changed; set it to <literal>false</literal> only if the module
+        requires access to <literal>pkgs</literal> in its documentation
+        (e.g. because it loads information from a linked package to
+        build an option type) or if its documentation depends on other
+        modules that also aren’t sandboxed (e.g. by using types defined
+        in the other module).
+      </para>
+    </listitem>
   </itemizedlist>
 </section>
diff --git a/nixos/lib/eval-cacheable-options.nix b/nixos/lib/eval-cacheable-options.nix
new file mode 100644
index 00000000000..c3ba2ce6637
--- /dev/null
+++ b/nixos/lib/eval-cacheable-options.nix
@@ -0,0 +1,53 @@
+{ libPath
+, pkgsLibPath
+, nixosPath
+, modules
+, stateVersion
+, release
+}:
+
+let
+  lib = import libPath;
+  modulesPath = "${nixosPath}/modules";
+  # dummy pkgs set that contains no packages, only `pkgs.lib` from the full set.
+  # not having `pkgs.lib` causes all users of `pkgs.formats` to fail.
+  pkgs = import pkgsLibPath {
+    inherit lib;
+    pkgs = null;
+  };
+  utils = import "${nixosPath}/lib/utils.nix" {
+    inherit config lib;
+    pkgs = null;
+  };
+  # this is used both as a module and as specialArgs.
+  # as a module it sets the _module special values, as specialArgs it makes `config`
+  # unusable. this causes documentation attributes depending on `config` to fail.
+  config = {
+    _module.check = false;
+    _module.args = {};
+    system.stateVersion = stateVersion;
+  };
+  eval = lib.evalModules {
+    modules = (map (m: "${modulesPath}/${m}") modules) ++ [
+      config
+    ];
+    specialArgs = {
+      inherit config pkgs utils;
+    };
+  };
+  docs = import "${nixosPath}/doc/manual" {
+    pkgs = pkgs // {
+      inherit lib;
+      # duplicate of the declaration in all-packages.nix
+      buildPackages.nixosOptionsDoc = attrs:
+        (import "${nixosPath}/lib/make-options-doc")
+          ({ inherit pkgs lib; } // attrs);
+    };
+    config = config.config;
+    options = eval.options;
+    version = release;
+    revision = "release-${release}";
+    prefix = modulesPath;
+  };
+in
+  docs.optionsNix
diff --git a/nixos/lib/make-options-doc/default.nix b/nixos/lib/make-options-doc/default.nix
index 44bc25be923..4b40af34b18 100644
--- a/nixos/lib/make-options-doc/default.nix
+++ b/nixos/lib/make-options-doc/default.nix
@@ -21,6 +21,10 @@
 , options
 , transformOptions ? lib.id  # function for additional tranformations of the options
 , revision ? "" # Specify revision for the options
+# a set of options the docs we are generating will be merged into, as if by recursiveUpdate.
+# used to split the options doc build into a static part (nixos/modules) and a dynamic part
+# (non-nixos modules imported via configuration.nix, other module sources).
+, baseOptionsJSON ? null
 }:
 
 let
@@ -99,13 +103,23 @@ in rec {
   optionsJSON = pkgs.runCommand "options.json"
     { meta.description = "List of NixOS options in JSON format";
       buildInputs = [ pkgs.brotli ];
+      options = builtins.toFile "options.json"
+        (builtins.unsafeDiscardStringContext (builtins.toJSON optionsNix));
     }
     ''
       # Export list of options in different format.
       dst=$out/share/doc/nixos
       mkdir -p $dst
 
-      cp ${builtins.toFile "options.json" (builtins.unsafeDiscardStringContext (builtins.toJSON optionsNix))} $dst/options.json
+      ${
+        if baseOptionsJSON == null
+          then "cp $options $dst/options.json"
+          else ''
+            ${pkgs.python3Minimal}/bin/python ${./mergeJSON.py} \
+              ${baseOptionsJSON} $options \
+              > $dst/options.json
+          ''
+      }
 
       brotli -9 < $dst/options.json > $dst/options.json.br
 
diff --git a/nixos/lib/make-options-doc/mergeJSON.py b/nixos/lib/make-options-doc/mergeJSON.py
new file mode 100644
index 00000000000..e7f6897c6d0
--- /dev/null
+++ b/nixos/lib/make-options-doc/mergeJSON.py
@@ -0,0 +1,71 @@
+import collections
+import json
+import sys
+
+class Key:
+    def __init__(self, path):
+        self.path = path
+    def __hash__(self):
+        result = 0
+        for id in self.path:
+            result ^= hash(id)
+        return result
+    def __eq__(self, other):
+        return type(self) is type(other) and self.path == other.path
+
+Option = collections.namedtuple('Option', ['name', 'value'])
+
+# pivot a dict of options keyed by their display name to a dict keyed by their path
+def pivot(options):
+    result = dict()
+    for (name, opt) in options.items():
+        result[Key(opt['loc'])] = Option(name, opt)
+    return result
+
+# pivot back to indexed-by-full-name
+# like the docbook build we'll just fail if multiple options with differing locs
+# render to the same option name.
+def unpivot(options):
+    result = dict()
+    for (key, opt) in options.items():
+        if opt.name in result:
+            raise RuntimeError(
+                'multiple options with colliding ids found',
+                opt.name,
+                result[opt.name]['loc'],
+                opt.value['loc'],
+            )
+        result[opt.name] = opt.value
+    return result
+
+options = pivot(json.load(open(sys.argv[1], 'r')))
+overrides = pivot(json.load(open(sys.argv[2], 'r')))
+
+# fix up declaration paths in lazy options, since we don't eval them from a full nixpkgs dir
+for (k, v) in options.items():
+    v.value['declarations'] = list(map(lambda s: f'nixos/modules/{s}', v.value['declarations']))
+
+# merge both descriptions
+for (k, v) in overrides.items():
+    cur = options.setdefault(k, v).value
+    for (ok, ov) in v.value.items():
+        if ok == 'declarations':
+            decls = cur[ok]
+            for d in ov:
+                if d not in decls:
+                    decls += [d]
+        elif ok == "type":
+            # ignore types of placeholder options
+            if ov != "_unspecified" or cur[ok] == "_unspecified":
+                cur[ok] = ov
+        elif ov is not None or cur.get(ok, None) is None:
+            cur[ok] = ov
+
+# check that every option has a description
+# TODO: nixos-rebuild with flakes may hide the warning, maybe turn on -L by default for those?
+for (k, v) in options.items():
+    if v.value.get('description', None) is None:
+        print(f"\x1b[1;31mwarning: option {v.name} has no description\x1b[0m", file=sys.stderr)
+        v.value['description'] = "This option has no description."
+
+json.dump(unpivot(options), fp=sys.stdout)
diff --git a/nixos/modules/config/qt5.nix b/nixos/modules/config/qt5.nix
index eabba9ad95f..24b2a6f9f4a 100644
--- a/nixos/modules/config/qt5.nix
+++ b/nixos/modules/config/qt5.nix
@@ -101,4 +101,7 @@ in
     environment.systemPackages = packages;
 
   };
+
+  # uses relatedPackages
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/i18n/input-method/fcitx.nix b/nixos/modules/i18n/input-method/fcitx.nix
index 57960cc365b..7738581b893 100644
--- a/nixos/modules/i18n/input-method/fcitx.nix
+++ b/nixos/modules/i18n/input-method/fcitx.nix
@@ -40,4 +40,7 @@ in
     };
     services.xserver.displayManager.sessionCommands = "${fcitxPackage}/bin/fcitx";
   };
+
+  # uses attributes of the linked package
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/i18n/input-method/ibus.nix b/nixos/modules/i18n/input-method/ibus.nix
index 92f8c64338a..c5b0cbc2150 100644
--- a/nixos/modules/i18n/input-method/ibus.nix
+++ b/nixos/modules/i18n/input-method/ibus.nix
@@ -80,4 +80,7 @@ in
       ibusPackage
     ];
   };
+
+  # uses attributes of the linked package
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/i18n/input-method/kime.nix b/nixos/modules/i18n/input-method/kime.nix
index e462cae2437..729a665614a 100644
--- a/nixos/modules/i18n/input-method/kime.nix
+++ b/nixos/modules/i18n/input-method/kime.nix
@@ -45,5 +45,7 @@ in
 
     environment.etc."xdg/kime/config.yaml".text = replaceStrings [ "\\\\" ] [ "\\" ] (builtins.toJSON cfg.config);
   };
-}
 
+  # uses attributes of the linked package
+  meta.buildDocsInSandbox = false;
+}
diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix
index 64b1c15086f..f868e4b709a 100644
--- a/nixos/modules/misc/documentation.nix
+++ b/nixos/modules/misc/documentation.nix
@@ -1,19 +1,35 @@
-{ config, lib, pkgs, extendModules, noUserModules, ... }:
+{ config, options, lib, pkgs, utils, modules, baseModules, extraModules, modulesPath, ... }:
 
 with lib;
 
 let
 
   cfg = config.documentation;
+  allOpts = options;
 
   /* Modules for which to show options even when not imported. */
   extraDocModules = [ ../virtualisation/qemu-vm.nix ];
 
-  /* For the purpose of generating docs, evaluate options with each derivation
-    in `pkgs` (recursively) replaced by a fake with path "\${pkgs.attribute.path}".
-    It isn't perfect, but it seems to cover a vast majority of use cases.
-    Caveat: even if the package is reached by a different means,
-    the path above will be shown and not e.g. `${config.services.foo.package}`. */
+  canCacheDocs = m:
+    let
+      f = import m;
+      instance = f (mapAttrs (n: _: abort "evaluating ${n} for `meta` failed") (functionArgs f));
+    in
+      cfg.nixos.splitOptionDocBuild
+        && builtins.isPath m
+        && isFunction f
+        && instance ? options
+        && instance.meta.buildDocsInSandbox or true;
+
+  docModules =
+    let
+      p = partition canCacheDocs (baseModules ++ extraDocModules);
+    in
+      {
+        lazy = p.right;
+        eager = p.wrong ++ optionals cfg.nixos.includeAllModules (extraModules ++ modules);
+      };
+
   manual = import ../../doc/manual rec {
     inherit pkgs config;
     version = config.system.nixos.release;
@@ -21,10 +37,17 @@ let
     extraSources = cfg.nixos.extraModuleSources;
     options =
       let
-        extendNixOS = if cfg.nixos.includeAllModules then extendModules else noUserModules.extendModules;
-        scrubbedEval = extendNixOS {
-          modules = extraDocModules;
-          specialArgs.pkgs = scrubDerivations "pkgs" pkgs;
+        scrubbedEval = evalModules {
+          modules = [ {
+            _module.check = false;
+          } ] ++ docModules.eager;
+          specialArgs = {
+            pkgs = scrubDerivations "pkgs" pkgs;
+            # allow access to arbitrary options for eager modules, eg for getting
+            # option types from lazy modules
+            options = allOpts;
+            inherit modulesPath utils;
+          };
         };
         scrubDerivations = namePrefix: pkgSet: mapAttrs
           (name: value:
@@ -36,6 +59,48 @@ let
           )
           pkgSet;
       in scrubbedEval.options;
+    baseOptionsJSON =
+      let
+        filter =
+          builtins.filterSource
+            (n: t:
+              (t == "directory" -> baseNameOf n != "tests")
+              && (t == "file" -> hasSuffix ".nix" n)
+            );
+      in
+        pkgs.runCommand "lazy-options.json" {
+          libPath = filter "${toString pkgs.path}/lib";
+          pkgsLibPath = filter "${toString pkgs.path}/pkgs/pkgs-lib";
+          nixosPath = filter "${toString pkgs.path}/nixos";
+          modules = map (p: ''"${removePrefix "${modulesPath}/" (toString p)}"'') docModules.lazy;
+        } ''
+          export NIX_STORE_DIR=$TMPDIR/store
+          export NIX_STATE_DIR=$TMPDIR/state
+          ${pkgs.nix}/bin/nix-instantiate \
+            --show-trace \
+            --eval --json --strict \
+            --argstr libPath "$libPath" \
+            --argstr pkgsLibPath "$pkgsLibPath" \
+            --argstr nixosPath "$nixosPath" \
+            --arg modules "[ $modules ]" \
+            --argstr stateVersion "${options.system.stateVersion.default}" \
+            --argstr release "${config.system.nixos.release}" \
+            $nixosPath/lib/eval-cacheable-options.nix > $out \
+            || {
+              echo -en "\e[1;31m"
+              echo 'Cacheable portion of option doc build failed.'
+              echo 'Usually this means that an option attribute that ends up in documentation (eg' \
+                '`default` or `description`) depends on the restricted module arguments' \
+                '`config` or `pkgs`.'
+              echo
+              echo 'Rebuild your configuration with `--show-trace` to find the offending' \
+                'location. Remove the references to restricted arguments (eg by escaping' \
+                'their antiquotations or adding a `defaultText`) or disable the sandboxed' \
+                'build for the failing module by setting `meta.buildDocsInSandbox = false`.'
+              echo -en "\e[0m"
+              exit 1
+            } >&2
+        '';
   };
 
 
@@ -191,6 +256,16 @@ in
         '';
       };
 
+      nixos.splitOptionDocBuild = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to split the option docs build into a cacheable and an uncacheable part.
+          Splitting the build can substantially decrease the amount of time needed to build
+          the manual, but some user modules may be incompatible with this splitting.
+        '';
+      };
+
       nixos.includeAllModules = mkOption {
         type = types.bool;
         default = false;
diff --git a/nixos/modules/misc/meta.nix b/nixos/modules/misc/meta.nix
index 3dd97cbec23..8e689a63f6b 100644
--- a/nixos/modules/misc/meta.nix
+++ b/nixos/modules/misc/meta.nix
@@ -54,6 +54,21 @@ in
         '';
       };
 
+      buildDocsInSandbox = mkOption {
+        type = types.bool // {
+          merge = loc: defs: defs;
+        };
+        internal = true;
+        default = true;
+        description = ''
+          Whether to include this module in the split options doc build.
+          Disable if the module references `config`, `pkgs` or other module
+          arguments that cannot be evaluated as constants.
+
+          This option should be defined at most once per module.
+        '';
+      };
+
     };
   };
 
diff --git a/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix
index 08bc4398555..2e0c8e4cf2c 100644
--- a/nixos/modules/misc/nixpkgs.nix
+++ b/nixos/modules/misc/nixpkgs.nix
@@ -248,4 +248,7 @@ in
       )
     ];
   };
+
+  # needs a full nixpkgs path to import nixpkgs
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix
index fc0d65d5148..6c526f6d4f2 100644
--- a/nixos/modules/misc/version.nix
+++ b/nixos/modules/misc/version.nix
@@ -119,4 +119,6 @@ in
 
   };
 
+  # uses version info nixpkgs, which requires a full nixpkgs path
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/programs/dmrconfig.nix b/nixos/modules/programs/dmrconfig.nix
index d2a5117c48e..73e1b529da9 100644
--- a/nixos/modules/programs/dmrconfig.nix
+++ b/nixos/modules/programs/dmrconfig.nix
@@ -7,6 +7,8 @@ let
 
 in {
   meta.maintainers = [ maintainers.etu ];
+  # uses relatedPackages
+  meta.buildDocsInSandbox = false;
 
   ###### interface
   options = {
diff --git a/nixos/modules/programs/gnupg.nix b/nixos/modules/programs/gnupg.nix
index fe5d7bd834b..b41f30287ea 100644
--- a/nixos/modules/programs/gnupg.nix
+++ b/nixos/modules/programs/gnupg.nix
@@ -149,4 +149,6 @@ in
     ];
   };
 
+  # uses attributes of the linked package
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/programs/tmux.nix b/nixos/modules/programs/tmux.nix
index c39908751d2..54c32a463e5 100644
--- a/nixos/modules/programs/tmux.nix
+++ b/nixos/modules/programs/tmux.nix
@@ -185,4 +185,7 @@ in {
   imports = [
     (lib.mkRenamedOptionModule [ "programs" "tmux" "extraTmuxConf" ] [ "programs" "tmux" "extraConfig" ])
   ];
+
+  # uses relatedPackages
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/services/backup/sanoid.nix b/nixos/modules/services/backup/sanoid.nix
index e70063415ec..5eb031b2e9f 100644
--- a/nixos/modules/services/backup/sanoid.nix
+++ b/nixos/modules/services/backup/sanoid.nix
@@ -51,7 +51,10 @@ let
   datasetOptions = rec {
     use_template = mkOption {
       description = "Names of the templates to use for this dataset.";
-      type = types.listOf (types.enum (attrNames cfg.templates));
+      type = types.listOf (types.str // {
+        check = (types.enum (attrNames cfg.templates)).check;
+        description = "configured template name";
+      });
       default = [ ];
     };
     useTemplate = use_template;
diff --git a/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix b/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
index 4be3e881a9d..803438b6f7e 100644
--- a/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
+++ b/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
@@ -29,6 +29,8 @@ in {
 
   meta = {
     maintainers = teams.freedesktop.members;
+    # uses attributes of the linked package
+    buildDocsInSandbox = false;
   };
 
   ###### interface
diff --git a/nixos/modules/services/desktops/pipewire/pipewire.nix b/nixos/modules/services/desktops/pipewire/pipewire.nix
index 55755ecd645..372b4785f18 100644
--- a/nixos/modules/services/desktops/pipewire/pipewire.nix
+++ b/nixos/modules/services/desktops/pipewire/pipewire.nix
@@ -40,6 +40,8 @@ in {
 
   meta = {
     maintainers = teams.freedesktop.members;
+    # uses attributes of the linked package
+    buildDocsInSandbox = false;
   };
 
   ###### interface
diff --git a/nixos/modules/services/hardware/thinkfan.nix b/nixos/modules/services/hardware/thinkfan.nix
index 4ea829e496e..1c5b428d5d6 100644
--- a/nixos/modules/services/hardware/thinkfan.nix
+++ b/nixos/modules/services/hardware/thinkfan.nix
@@ -221,4 +221,7 @@ in {
     boot.extraModprobeConfig = "options thinkpad_acpi experimental=1 fan_control=1";
 
   };
+
+  # uses relatedPackages
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/services/misc/matrix-appservice-irc.nix b/nixos/modules/services/misc/matrix-appservice-irc.nix
index 02627e51c93..b041c9c82c5 100644
--- a/nixos/modules/services/misc/matrix-appservice-irc.nix
+++ b/nixos/modules/services/misc/matrix-appservice-irc.nix
@@ -226,4 +226,7 @@ in {
       isSystemUser = true;
     };
   };
+
+  # uses attributes of the linked package
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/services/networking/dnscrypt-proxy2.nix b/nixos/modules/services/networking/dnscrypt-proxy2.nix
index dc6a019e9b7..316e6e37f9d 100644
--- a/nixos/modules/services/networking/dnscrypt-proxy2.nix
+++ b/nixos/modules/services/networking/dnscrypt-proxy2.nix
@@ -118,4 +118,7 @@ in
       };
     };
   };
+
+  # uses attributes of the linked package
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/services/networking/kea.nix b/nixos/modules/services/networking/kea.nix
index 4da47f575f7..17b4eb2e283 100644
--- a/nixos/modules/services/networking/kea.nix
+++ b/nixos/modules/services/networking/kea.nix
@@ -378,4 +378,6 @@ in
   ]);
 
   meta.maintainers = with maintainers; [ hexa ];
+  # uses attributes of the linked package
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/services/networking/searx.nix b/nixos/modules/services/networking/searx.nix
index 9fb06af7442..6fd81521e7f 100644
--- a/nixos/modules/services/networking/searx.nix
+++ b/nixos/modules/services/networking/searx.nix
@@ -228,5 +228,6 @@ in
   };
 
   meta.maintainers = with maintainers; [ rnhmjoj ];
-
+  # uses relatedPackages
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/services/security/vaultwarden/default.nix b/nixos/modules/services/security/vaultwarden/default.nix
index 5b951bc85ec..71088fc4dcd 100644
--- a/nixos/modules/services/security/vaultwarden/default.nix
+++ b/nixos/modules/services/security/vaultwarden/default.nix
@@ -179,4 +179,7 @@ in {
       wantedBy = [ "multi-user.target" ];
     };
   };
+
+  # uses attributes of the linked package
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/services/web-apps/dex.nix b/nixos/modules/services/web-apps/dex.nix
index f08dd65bdb0..4d4689a4cf2 100644
--- a/nixos/modules/services/web-apps/dex.nix
+++ b/nixos/modules/services/web-apps/dex.nix
@@ -112,4 +112,7 @@ in
       };
     };
   };
+
+  # uses attributes of the linked package
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/services/web-apps/gerrit.nix b/nixos/modules/services/web-apps/gerrit.nix
index 9ee9dbf1aa4..6bfc67368dd 100644
--- a/nixos/modules/services/web-apps/gerrit.nix
+++ b/nixos/modules/services/web-apps/gerrit.nix
@@ -237,4 +237,6 @@ in
   };
 
   meta.maintainers = with lib.maintainers; [ edef zimbatm ];
+  # uses attributes of the linked package
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/services/web-apps/jirafeau.nix b/nixos/modules/services/web-apps/jirafeau.nix
index 83cf224f7d2..a95e2b4f82a 100644
--- a/nixos/modules/services/web-apps/jirafeau.nix
+++ b/nixos/modules/services/web-apps/jirafeau.nix
@@ -167,4 +167,7 @@ in
       "d ${cfg.dataDir}/async/ 0750 ${user} ${group} - -"
     ];
   };
+
+  # uses attributes of the linked package
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index 6692d67081c..e04b30a7d62 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -932,4 +932,6 @@ in {
   ]);
 
   meta.doc = ./nextcloud.xml;
+  # uses relatedPackages
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/services/web-apps/powerdns-admin.nix b/nixos/modules/services/web-apps/powerdns-admin.nix
index ce99b606c31..4661ba80c5d 100644
--- a/nixos/modules/services/web-apps/powerdns-admin.nix
+++ b/nixos/modules/services/web-apps/powerdns-admin.nix
@@ -146,4 +146,7 @@ in
       group = "powerdnsadmin";
     };
   };
+
+  # uses attributes of the linked package
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index 24d92573442..f0cabdd4465 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -865,4 +865,6 @@ in
 
   };
 
+  # uses relatedPackages
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix
index 501998fa399..2efe0f05e0c 100644
--- a/nixos/modules/system/activation/top-level.nix
+++ b/nixos/modules/system/activation/top-level.nix
@@ -317,4 +317,6 @@ in
 
   };
 
+  # uses extendModules to generate a type
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index fa3e25afb03..29e3aa024df 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -999,4 +999,7 @@ in
       ];
 
   };
+
+  # uses types of services/x11/xserver.nix
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/virtualisation/xen-dom0.nix b/nixos/modules/virtualisation/xen-dom0.nix
index f8f4af4f6b8..fc640bd947b 100644
--- a/nixos/modules/virtualisation/xen-dom0.nix
+++ b/nixos/modules/virtualisation/xen-dom0.nix
@@ -451,4 +451,7 @@ in
 
   };
 
+
+  # uses relatedPackages
+  meta.buildDocsInSandbox = false;
 }