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