1{ pkgs, options, config, version, revision, extraSources ? [] }:
2
3with pkgs;
4
5let
6 lib = pkgs.lib;
7
8 # Remove invisible and internal options.
9 optionsListVisible = lib.filter (opt: opt.visible && !opt.internal) (lib.optionAttrSetToDocList options);
10
11 # Replace functions by the string <function>
12 substFunction = x:
13 if builtins.isAttrs x then lib.mapAttrs (name: substFunction) x
14 else if builtins.isList x then map substFunction x
15 else if lib.isFunction x then "<function>"
16 else x;
17
18 # Generate DocBook documentation for a list of packages. This is
19 # what `relatedPackages` option of `mkOption` from
20 # ../../../lib/options.nix influences.
21 #
22 # Each element of `relatedPackages` can be either
23 # - a string: that will be interpreted as an attribute name from `pkgs`,
24 # - a list: that will be interpreted as an attribute path from `pkgs`,
25 # - an attrset: that can specify `name`, `path`, `package`, `comment`
26 # (either of `name`, `path` is required, the rest are optional).
27 genRelatedPackages = packages:
28 let
29 unpack = p: if lib.isString p then { name = p; }
30 else if lib.isList p then { path = p; }
31 else p;
32 describe = args:
33 let
34 name = args.name or (lib.concatStringsSep "." args.path);
35 path = args.path or [ args.name ];
36 package = args.package or (lib.attrByPath path (throw "Invalid package attribute path `${toString path}'") pkgs);
37 in "<listitem>"
38 + "<para><literal>pkgs.${name} (${package.meta.name})</literal>"
39 + lib.optionalString (!package.meta.available) " <emphasis>[UNAVAILABLE]</emphasis>"
40 + ": ${package.meta.description or "???"}.</para>"
41 + lib.optionalString (args ? comment) "\n<para>${args.comment}</para>"
42 # Lots of `longDescription's break DocBook, so we just wrap them into <programlisting>
43 + lib.optionalString (package.meta ? longDescription) "\n<programlisting>${package.meta.longDescription}</programlisting>"
44 + "</listitem>";
45 in "<itemizedlist>${lib.concatStringsSep "\n" (map (p: describe (unpack p)) packages)}</itemizedlist>";
46
47 optionsListDesc = lib.flip map optionsListVisible (opt: opt // {
48 # Clean up declaration sites to not refer to the NixOS source tree.
49 declarations = map stripAnyPrefixes opt.declarations;
50 }
51 // lib.optionalAttrs (opt ? example) { example = substFunction opt.example; }
52 // lib.optionalAttrs (opt ? default) { default = substFunction opt.default; }
53 // lib.optionalAttrs (opt ? type) { type = substFunction opt.type; }
54 // lib.optionalAttrs (opt ? relatedPackages) { relatedPackages = genRelatedPackages opt.relatedPackages; });
55
56 # We need to strip references to /nix/store/* from options,
57 # including any `extraSources` if some modules came from elsewhere,
58 # or else the build will fail.
59 #
60 # E.g. if some `options` came from modules in ${pkgs.customModules}/nix,
61 # you'd need to include `extraSources = [ pkgs.customModules ]`
62 prefixesToStrip = map (p: "${toString p}/") ([ ../../.. ] ++ extraSources);
63 stripAnyPrefixes = lib.flip (lib.fold lib.removePrefix) prefixesToStrip;
64
65 # Custom "less" that pushes up all the things ending in ".enable*"
66 # and ".package*"
67 optionLess = a: b:
68 let
69 ise = lib.hasPrefix "enable";
70 isp = lib.hasPrefix "package";
71 cmp = lib.splitByAndCompare ise lib.compare
72 (lib.splitByAndCompare isp lib.compare lib.compare);
73 in lib.compareLists cmp a.loc b.loc < 0;
74
75 # Customly sort option list for the man page.
76 optionsList = lib.sort optionLess optionsListDesc;
77
78 # Convert the list of options into an XML file.
79 optionsXML = builtins.toFile "options.xml" (builtins.toXML optionsList);
80
81 optionsDocBook = runCommand "options-db.xml" {} ''
82 optionsXML=${optionsXML}
83 if grep /nixpkgs/nixos/modules $optionsXML; then
84 echo "The manual appears to depend on the location of Nixpkgs, which is bad"
85 echo "since this prevents sharing via the NixOS channel. This is typically"
86 echo "caused by an option default that refers to a relative path (see above"
87 echo "for hints about the offending path)."
88 exit 1
89 fi
90 ${libxslt.bin}/bin/xsltproc \
91 --stringparam revision '${revision}' \
92 -o $out ${./options-to-docbook.xsl} $optionsXML
93 '';
94
95 sources = lib.sourceFilesBySuffices ./. [".xml"];
96
97 modulesDoc = builtins.toFile "modules.xml" ''
98 <section xmlns:xi="http://www.w3.org/2001/XInclude" id="modules">
99 ${(lib.concatMapStrings (path: ''
100 <xi:include href="${path}" />
101 '') (lib.catAttrs "value" config.meta.doc))}
102 </section>
103 '';
104
105 copySources =
106 ''
107 cp -prd $sources/* . # */
108 chmod -R u+w .
109 ln -s ${modulesDoc} configuration/modules.xml
110 ln -s ${optionsDocBook} options-db.xml
111 printf "%s" "${version}" > version
112 '';
113
114 toc = builtins.toFile "toc.xml"
115 ''
116 <toc role="chunk-toc">
117 <d:tocentry xmlns:d="http://docbook.org/ns/docbook" linkend="book-nixos-manual"><?dbhtml filename="index.html"?>
118 <d:tocentry linkend="ch-options"><?dbhtml filename="options.html"?></d:tocentry>
119 <d:tocentry linkend="ch-release-notes"><?dbhtml filename="release-notes.html"?></d:tocentry>
120 </d:tocentry>
121 </toc>
122 '';
123
124 manualXsltprocOptions = toString [
125 "--param section.autolabel 1"
126 "--param section.label.includes.component.label 1"
127 "--stringparam html.stylesheet style.css"
128 "--param xref.with.number.and.title 1"
129 "--param toc.section.depth 3"
130 "--stringparam admon.style ''"
131 "--stringparam callout.graphics.extension .gif"
132 "--stringparam current.docid manual"
133 "--param chunk.section.depth 0"
134 "--param chunk.first.sections 1"
135 "--param use.id.as.filename 1"
136 "--stringparam generate.toc 'book toc appendix toc'"
137 "--stringparam chunk.toc ${toc}"
138 ];
139
140 manual-combined = runCommand "nixos-manual-combined"
141 { inherit sources;
142 buildInputs = [ libxml2 libxslt ];
143 meta.description = "The NixOS manual as plain docbook XML";
144 }
145 ''
146 ${copySources}
147
148 xmllint --xinclude --output ./manual-combined.xml ./manual.xml
149 xmllint --xinclude --noxincludenode \
150 --output ./man-pages-combined.xml ./man-pages.xml
151
152 # outputs the context of an xmllint error output
153 # LEN lines around the failing line are printed
154 function context {
155 # length of context
156 local LEN=6
157 # lines to print before error line
158 local BEFORE=4
159
160 # xmllint output lines are:
161 # file.xml:1234: there was an error on line 1234
162 while IFS=':' read -r file line rest; do
163 echo
164 if [[ -n "$rest" ]]; then
165 echo "$file:$line:$rest"
166 local FROM=$(($line>$BEFORE ? $line - $BEFORE : 1))
167 # number lines & filter context
168 nl --body-numbering=a "$file" | sed -n "$FROM,+$LEN p"
169 else
170 if [[ -n "$line" ]]; then
171 echo "$file:$line"
172 else
173 echo "$file"
174 fi
175 fi
176 done
177 }
178
179 function lintrng {
180 xmllint --debug --noout --nonet \
181 --relaxng ${docbook5}/xml/rng/docbook/docbook.rng \
182 "$1" \
183 2>&1 | context 1>&2
184 # ^ redirect assumes xmllint doesn’t print to stdout
185 }
186
187 lintrng manual-combined.xml
188 lintrng man-pages-combined.xml
189
190 mkdir $out
191 cp manual-combined.xml $out/
192 cp man-pages-combined.xml $out/
193 '';
194
195 olinkDB = runCommand "manual-olinkdb"
196 { inherit sources;
197 buildInputs = [ libxml2 libxslt ];
198 }
199 ''
200 xsltproc \
201 ${manualXsltprocOptions} \
202 --stringparam collect.xref.targets only \
203 --stringparam targets.filename "$out/manual.db" \
204 --nonet \
205 ${docbook5_xsl}/xml/xsl/docbook/xhtml/chunktoc.xsl \
206 ${manual-combined}/manual-combined.xml
207
208 cat > "$out/olinkdb.xml" <<EOF
209 <?xml version="1.0" encoding="utf-8"?>
210 <!DOCTYPE targetset SYSTEM
211 "file://${docbook5_xsl}/xml/xsl/docbook/common/targetdatabase.dtd" [
212 <!ENTITY manualtargets SYSTEM "file://$out/manual.db">
213 ]>
214 <targetset>
215 <targetsetinfo>
216 Allows for cross-referencing olinks between the manpages
217 and manual.
218 </targetsetinfo>
219
220 <document targetdoc="manual">&manualtargets;</document>
221 </targetset>
222 EOF
223 '';
224
225in rec {
226
227 # The NixOS options in JSON format.
228 optionsJSON = runCommand "options-json"
229 { meta.description = "List of NixOS options in JSON format";
230 }
231 ''
232 # Export list of options in different format.
233 dst=$out/share/doc/nixos
234 mkdir -p $dst
235
236 cp ${builtins.toFile "options.json" (builtins.unsafeDiscardStringContext (builtins.toJSON
237 (builtins.listToAttrs (map (o: { name = o.name; value = removeAttrs o ["name" "visible" "internal"]; }) optionsList))))
238 } $dst/options.json
239
240 mkdir -p $out/nix-support
241 echo "file json $dst/options.json" >> $out/nix-support/hydra-build-products
242 ''; # */
243
244 # Generate the NixOS manual.
245 manual = runCommand "nixos-manual"
246 { inherit sources;
247 buildInputs = [ libxml2 libxslt ];
248 meta.description = "The NixOS manual in HTML format";
249 allowedReferences = ["out"];
250 }
251 ''
252 # Generate the HTML manual.
253 dst=$out/share/doc/nixos
254 mkdir -p $dst
255 xsltproc \
256 ${manualXsltprocOptions} \
257 --stringparam target.database.document "${olinkDB}/olinkdb.xml" \
258 --nonet --output $dst/ \
259 ${docbook5_xsl}/xml/xsl/docbook/xhtml/chunktoc.xsl \
260 ${manual-combined}/manual-combined.xml
261
262 mkdir -p $dst/images/callouts
263 cp ${docbook5_xsl}/xml/xsl/docbook/images/callouts/*.gif $dst/images/callouts/
264
265 cp ${./style.css} $dst/style.css
266
267 mkdir -p $out/nix-support
268 echo "nix-build out $out" >> $out/nix-support/hydra-build-products
269 echo "doc manual $dst" >> $out/nix-support/hydra-build-products
270 ''; # */
271
272
273 manualEpub = runCommand "nixos-manual-epub"
274 { inherit sources;
275 buildInputs = [ libxml2 libxslt zip ];
276 }
277 ''
278 # Generate the epub manual.
279 dst=$out/share/doc/nixos
280
281 xsltproc \
282 ${manualXsltprocOptions} \
283 --stringparam target.database.document "${olinkDB}/olinkdb.xml" \
284 --nonet --xinclude --output $dst/epub/ \
285 ${docbook5_xsl}/xml/xsl/docbook/epub/docbook.xsl \
286 ${manual-combined}/manual-combined.xml
287
288 mkdir -p $dst/epub/OEBPS/images/callouts
289 cp -r ${docbook5_xsl}/xml/xsl/docbook/images/callouts/*.gif $dst/epub/OEBPS/images/callouts # */
290 echo "application/epub+zip" > mimetype
291 manual="$dst/nixos-manual.epub"
292 zip -0Xq "$manual" mimetype
293 cd $dst/epub && zip -Xr9D "$manual" *
294
295 rm -rf $dst/epub
296
297 mkdir -p $out/nix-support
298 echo "doc-epub manual $manual" >> $out/nix-support/hydra-build-products
299 '';
300
301
302 # Generate the NixOS manpages.
303 manpages = runCommand "nixos-manpages"
304 { inherit sources;
305 buildInputs = [ libxml2 libxslt ];
306 allowedReferences = ["out"];
307 }
308 ''
309 # Generate manpages.
310 mkdir -p $out/share/man
311 xsltproc --nonet \
312 --param man.output.in.separate.dir 1 \
313 --param man.output.base.dir "'$out/share/man/'" \
314 --param man.endnotes.are.numbered 0 \
315 --param man.break.after.slash 1 \
316 --stringparam target.database.document "${olinkDB}/olinkdb.xml" \
317 ${docbook5_xsl}/xml/xsl/docbook/manpages/docbook.xsl \
318 ${manual-combined}/man-pages-combined.xml
319 '';
320
321}