1{ config, options, lib, pkgs, utils, modules, baseModules, extraModules, modulesPath, specialArgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.documentation;
8 allOpts = options;
9
10 canCacheDocs = m:
11 let
12 f = import m;
13 instance = f (mapAttrs (n: _: abort "evaluating ${n} for `meta` failed") (functionArgs f));
14 in
15 cfg.nixos.options.splitBuild
16 && builtins.isPath m
17 && isFunction f
18 && instance ? options
19 && instance.meta.buildDocsInSandbox or true;
20
21 docModules =
22 let
23 p = partition canCacheDocs (baseModules ++ cfg.nixos.extraModules);
24 in
25 {
26 lazy = p.right;
27 eager = p.wrong ++ optionals cfg.nixos.includeAllModules (extraModules ++ modules);
28 };
29
30 manual = import ../../doc/manual rec {
31 inherit pkgs config;
32 version = config.system.nixos.release;
33 revision = "release-${version}";
34 extraSources = cfg.nixos.extraModuleSources;
35 options =
36 let
37 scrubbedEval = evalModules {
38 modules = [ {
39 _module.check = false;
40 } ] ++ docModules.eager;
41 class = "nixos";
42 specialArgs = specialArgs // {
43 pkgs = scrubDerivations "pkgs" pkgs;
44 # allow access to arbitrary options for eager modules, eg for getting
45 # option types from lazy modules
46 options = allOpts;
47 inherit modulesPath utils;
48 };
49 };
50 scrubDerivations = namePrefix: pkgSet: mapAttrs
51 (name: value:
52 let
53 wholeName = "${namePrefix}.${name}";
54 guard = lib.warn "Attempt to evaluate package ${wholeName} in option documentation; this is not supported and will eventually be an error. Use `mkPackageOption{,MD}` or `literalExpression` instead.";
55 in if isAttrs value then
56 scrubDerivations wholeName value
57 // optionalAttrs (isDerivation value) {
58 outPath = guard "\${${wholeName}}";
59 drvPath = guard drvPath;
60 }
61 else value
62 )
63 pkgSet;
64 in scrubbedEval.options;
65
66 baseOptionsJSON =
67 let
68 filter =
69 builtins.filterSource
70 (n: t:
71 cleanSourceFilter n t
72 && (t == "directory" -> baseNameOf n != "tests")
73 && (t == "file" -> hasSuffix ".nix" n)
74 );
75 in
76 pkgs.runCommand "lazy-options.json" {
77 libPath = filter (pkgs.path + "/lib");
78 pkgsLibPath = filter (pkgs.path + "/pkgs/pkgs-lib");
79 nixosPath = filter (pkgs.path + "/nixos");
80 modules = map (p: ''"${removePrefix "${modulesPath}/" (toString p)}"'') docModules.lazy;
81 } ''
82 export NIX_STORE_DIR=$TMPDIR/store
83 export NIX_STATE_DIR=$TMPDIR/state
84 ${pkgs.buildPackages.nix}/bin/nix-instantiate \
85 --show-trace \
86 --eval --json --strict \
87 --argstr libPath "$libPath" \
88 --argstr pkgsLibPath "$pkgsLibPath" \
89 --argstr nixosPath "$nixosPath" \
90 --arg modules "[ $modules ]" \
91 --argstr stateVersion "${options.system.stateVersion.default}" \
92 --argstr release "${config.system.nixos.release}" \
93 $nixosPath/lib/eval-cacheable-options.nix > $out \
94 || {
95 echo -en "\e[1;31m"
96 echo 'Cacheable portion of option doc build failed.'
97 echo 'Usually this means that an option attribute that ends up in documentation (eg' \
98 '`default` or `description`) depends on the restricted module arguments' \
99 '`config` or `pkgs`.'
100 echo
101 echo 'Rebuild your configuration with `--show-trace` to find the offending' \
102 'location. Remove the references to restricted arguments (eg by escaping' \
103 'their antiquotations or adding a `defaultText`) or disable the sandboxed' \
104 'build for the failing module by setting `meta.buildDocsInSandbox = false`.'
105 echo -en "\e[0m"
106 exit 1
107 } >&2
108 '';
109
110 inherit (cfg.nixos.options) warningsAreErrors allowDocBook;
111 };
112
113
114 nixos-help = let
115 helpScript = pkgs.writeShellScriptBin "nixos-help" ''
116 # Finds first executable browser in a colon-separated list.
117 # (see how xdg-open defines BROWSER)
118 browser="$(
119 IFS=: ; for b in $BROWSER; do
120 [ -n "$(type -P "$b" || true)" ] && echo "$b" && break
121 done
122 )"
123 if [ -z "$browser" ]; then
124 browser="$(type -P xdg-open || true)"
125 if [ -z "$browser" ]; then
126 browser="${pkgs.w3m-nographics}/bin/w3m"
127 fi
128 fi
129 exec "$browser" ${manual.manualHTMLIndex}
130 '';
131
132 desktopItem = pkgs.makeDesktopItem {
133 name = "nixos-manual";
134 desktopName = "NixOS Manual";
135 genericName = "System Manual";
136 comment = "View NixOS documentation in a web browser";
137 icon = "nix-snowflake";
138 exec = "nixos-help";
139 categories = ["System"];
140 };
141
142 in pkgs.symlinkJoin {
143 name = "nixos-help";
144 paths = [
145 helpScript
146 desktopItem
147 ];
148 };
149
150in
151
152{
153 imports = [
154 ./man-db.nix
155 ./mandoc.nix
156 ./assertions.nix
157 ./meta.nix
158 ../config/system-path.nix
159 ../system/etc/etc.nix
160 (mkRenamedOptionModule [ "programs" "info" "enable" ] [ "documentation" "info" "enable" ])
161 (mkRenamedOptionModule [ "programs" "man" "enable" ] [ "documentation" "man" "enable" ])
162 (mkRenamedOptionModule [ "services" "nixosManual" "enable" ] [ "documentation" "nixos" "enable" ])
163 ];
164
165 options = {
166
167 documentation = {
168
169 enable = mkOption {
170 type = types.bool;
171 default = true;
172 description = lib.mdDoc ''
173 Whether to install documentation of packages from
174 {option}`environment.systemPackages` into the generated system path.
175
176 See "Multiple-output packages" chapter in the nixpkgs manual for more info.
177 '';
178 # which is at ../../../doc/multiple-output.chapter.md
179 };
180
181 man.enable = mkOption {
182 type = types.bool;
183 default = true;
184 description = lib.mdDoc ''
185 Whether to install manual pages.
186 This also includes `man` outputs.
187 '';
188 };
189
190 man.generateCaches = mkOption {
191 type = types.bool;
192 default = false;
193 description = mdDoc ''
194 Whether to generate the manual page index caches.
195 This allows searching for a page or
196 keyword using utilities like {manpage}`apropos(1)`
197 and the `-k` option of
198 {manpage}`man(1)`.
199 '';
200 };
201
202 info.enable = mkOption {
203 type = types.bool;
204 default = true;
205 description = lib.mdDoc ''
206 Whether to install info pages and the {command}`info` command.
207 This also includes "info" outputs.
208 '';
209 };
210
211 doc.enable = mkOption {
212 type = types.bool;
213 default = true;
214 description = lib.mdDoc ''
215 Whether to install documentation distributed in packages' `/share/doc`.
216 Usually plain text and/or HTML.
217 This also includes "doc" outputs.
218 '';
219 };
220
221 dev.enable = mkOption {
222 type = types.bool;
223 default = false;
224 description = mdDoc ''
225 Whether to install documentation targeted at developers.
226 * This includes man pages targeted at developers if {option}`documentation.man.enable` is
227 set (this also includes "devman" outputs).
228 * This includes info pages targeted at developers if {option}`documentation.info.enable`
229 is set (this also includes "devinfo" outputs).
230 * This includes other pages targeted at developers if {option}`documentation.doc.enable`
231 is set (this also includes "devdoc" outputs).
232 '';
233 };
234
235 nixos.enable = mkOption {
236 type = types.bool;
237 default = true;
238 description = lib.mdDoc ''
239 Whether to install NixOS's own documentation.
240
241 - This includes man pages like
242 {manpage}`configuration.nix(5)` if {option}`documentation.man.enable` is
243 set.
244 - This includes the HTML manual and the {command}`nixos-help` command if
245 {option}`documentation.doc.enable` is set.
246 '';
247 };
248
249 nixos.extraModules = mkOption {
250 type = types.listOf types.raw;
251 default = [];
252 description = lib.mdDoc ''
253 Modules for which to show options even when not imported.
254 '';
255 };
256
257 nixos.options.splitBuild = mkOption {
258 type = types.bool;
259 default = true;
260 description = lib.mdDoc ''
261 Whether to split the option docs build into a cacheable and an uncacheable part.
262 Splitting the build can substantially decrease the amount of time needed to build
263 the manual, but some user modules may be incompatible with this splitting.
264 '';
265 };
266
267 nixos.options.allowDocBook = mkOption {
268 type = types.bool;
269 default = true;
270 description = lib.mdDoc ''
271 Whether to allow DocBook option docs. When set to `false` all option using
272 DocBook documentation will cause a manual build error; additionally a new
273 renderer may be used.
274
275 ::: {.note}
276 The `false` setting for this option is not yet fully supported. While it
277 should work fine and produce the same output as the previous toolchain
278 using DocBook it may not work in all circumstances. Whether markdown option
279 documentation is allowed is independent of this option.
280 :::
281 '';
282 };
283
284 nixos.options.warningsAreErrors = mkOption {
285 type = types.bool;
286 default = true;
287 description = lib.mdDoc ''
288 Treat warning emitted during the option documentation build (eg for missing option
289 descriptions) as errors.
290 '';
291 };
292
293 nixos.includeAllModules = mkOption {
294 type = types.bool;
295 default = false;
296 description = lib.mdDoc ''
297 Whether the generated NixOS's documentation should include documentation for all
298 the options from all the NixOS modules included in the current
299 `configuration.nix`. Disabling this will make the manual
300 generator to ignore options defined outside of `baseModules`.
301 '';
302 };
303
304 nixos.extraModuleSources = mkOption {
305 type = types.listOf (types.either types.path types.str);
306 default = [ ];
307 description = lib.mdDoc ''
308 Which extra NixOS module paths the generated NixOS's documentation should strip
309 from options.
310 '';
311 example = literalExpression ''
312 # e.g. with options from modules in ''${pkgs.customModules}/nix:
313 [ pkgs.customModules ]
314 '';
315 };
316
317 };
318
319 };
320
321 config = mkIf cfg.enable (mkMerge [
322 {
323 assertions = [
324 {
325 assertion = !(cfg.man.man-db.enable && cfg.man.mandoc.enable);
326 message = ''
327 man-db and mandoc can't be used as the default man page viewer at the same time!
328 '';
329 }
330 ];
331 }
332
333 # The actual implementation for this lives in man-db.nix or mandoc.nix,
334 # depending on which backend is active.
335 (mkIf cfg.man.enable {
336 environment.pathsToLink = [ "/share/man" ];
337 environment.extraOutputsToInstall = [ "man" ] ++ optional cfg.dev.enable "devman";
338 })
339
340 (mkIf cfg.info.enable {
341 environment.systemPackages = [ pkgs.texinfoInteractive ];
342 environment.pathsToLink = [ "/share/info" ];
343 environment.extraOutputsToInstall = [ "info" ] ++ optional cfg.dev.enable "devinfo";
344 environment.extraSetup = ''
345 if [ -w $out/share/info ]; then
346 shopt -s nullglob
347 for i in $out/share/info/*.info $out/share/info/*.info.gz; do
348 ${pkgs.buildPackages.texinfo}/bin/install-info $i $out/share/info/dir
349 done
350 fi
351 '';
352 })
353
354 (mkIf cfg.doc.enable {
355 environment.pathsToLink = [ "/share/doc" ];
356 environment.extraOutputsToInstall = [ "doc" ] ++ optional cfg.dev.enable "devdoc";
357 })
358
359 (mkIf cfg.nixos.enable {
360 system.build.manual = manual;
361
362 system.activationScripts.check-manual-docbook = ''
363 if [[ $(cat ${manual.optionsUsedDocbook}) = 1 ]]; then
364 echo -e "\e[31;1mwarning\e[0m: This configuration contains option documentation in docbook." \
365 "Support for docbook is deprecated and will be removed after NixOS 23.05." \
366 "See nix-store --read-log ${builtins.unsafeDiscardStringContext manual.optionsJSON.drvPath}"
367 fi
368 '';
369
370 environment.systemPackages = []
371 ++ optional cfg.man.enable manual.manpages
372 ++ optionals cfg.doc.enable [ manual.manualHTML nixos-help ];
373 })
374
375 ]);
376
377}