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