diff options
author | pennae <github@quasiparticle.net> | 2021-11-19 00:26:27 +0100 |
---|---|---|
committer | pennae <github@quasiparticle.net> | 2022-01-02 19:46:13 +0100 |
commit | fc614c37c653637e5475a0b0a987489b4d1f351d (patch) | |
tree | 7b2470a3a67979ddcc51f18bb0d424f8e95908b1 /nixos/modules/misc/documentation.nix | |
parent | 55daffc1c943bddb71dc89a606f8284f6d50f5bd (diff) |
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/modules/misc/documentation.nix')
-rw-r--r-- | nixos/modules/misc/documentation.nix | 95 |
1 files changed, 85 insertions, 10 deletions
diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix index 64b1c15086fc8..f868e4b709a68 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; |