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