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