{ pkgs, options, config, version, revision, extraSources ? [] }: with pkgs; let lib = pkgs.lib; # Remove invisible and internal options. optionsListVisible = lib.filter (opt: opt.visible && !opt.internal) (lib.optionAttrSetToDocList options); # Replace functions by the string substFunction = x: if builtins.isAttrs x then lib.mapAttrs (name: substFunction) x else if builtins.isList x then map substFunction x else if lib.isFunction x then "" else x; # Generate DocBook documentation for a list of packages. This is # what `relatedPackages` option of `mkOption` from # ../../../lib/options.nix influences. # # Each element of `relatedPackages` can be either # - a string: that will be interpreted as an attribute name from `pkgs`, # - a list: that will be interpreted as an attribute path from `pkgs`, # - an attrset: that can specify `name`, `path`, `package`, `comment` # (either of `name`, `path` is required, the rest are optional). genRelatedPackages = packages: let unpack = p: if lib.isString p then { name = p; } else if lib.isList p then { path = p; } else p; describe = args: let name = args.name or (lib.concatStringsSep "." args.path); path = args.path or [ args.name ]; package = args.package or (lib.attrByPath path (throw "Invalid package attribute path `${toString path}'") pkgs); in "" + "pkgs.${name} (${package.meta.name})" + lib.optionalString (!package.meta.available) " [UNAVAILABLE]" + ": ${package.meta.description or "???"}." + lib.optionalString (args ? comment) "\n${args.comment}" # Lots of `longDescription's break DocBook, so we just wrap them into + lib.optionalString (package.meta ? longDescription) "\n${package.meta.longDescription}" + ""; in "${lib.concatStringsSep "\n" (map (p: describe (unpack p)) packages)}"; optionsListDesc = lib.flip map optionsListVisible (opt: opt // { # Clean up declaration sites to not refer to the NixOS source tree. declarations = map stripAnyPrefixes opt.declarations; } // lib.optionalAttrs (opt ? example) { example = substFunction opt.example; } // lib.optionalAttrs (opt ? default) { default = substFunction opt.default; } // lib.optionalAttrs (opt ? type) { type = substFunction opt.type; } // lib.optionalAttrs (opt ? relatedPackages) { relatedPackages = genRelatedPackages opt.relatedPackages; }); # We need to strip references to /nix/store/* from options, # including any `extraSources` if some modules came from elsewhere, # or else the build will fail. # # E.g. if some `options` came from modules in ${pkgs.customModules}/nix, # you'd need to include `extraSources = [ pkgs.customModules ]` prefixesToStrip = map (p: "${toString p}/") ([ ./.. ] ++ extraSources); stripAnyPrefixes = lib.flip (lib.fold lib.removePrefix) prefixesToStrip; # Custom "less" that pushes up all the things ending in ".enable*" # and ".package*" optionLess = a: b: let ise = lib.hasPrefix "enable"; isp = lib.hasPrefix "package"; cmp = lib.splitByAndCompare ise lib.compare (lib.splitByAndCompare isp lib.compare lib.compare); in lib.compareLists cmp a.loc b.loc < 0; # Customly sort option list for the man page. optionsList = lib.sort optionLess optionsListDesc; # Convert the list of options into an XML file. optionsXML = builtins.toFile "options.xml" (builtins.toXML optionsList); optionsDocBook = runCommand "options-db.xml" { nativeBuildInputs = [ buildPackages.libxslt.bin ]; } '' optionsXML=${optionsXML} xsltproc \ --stringparam program 'home-manager' \ --stringparam revision '${revision}' \ -o $out ${./options-to-docbook.xsl} $optionsXML ''; sources = lib.sourceFilesBySuffices ./. [".xml"]; modulesDoc = builtins.toFile "modules.xml" ''
${(lib.concatMapStrings (path: '' '') (lib.catAttrs "value" config.meta.doc))}
''; generatedSources = runCommand "generated-docbook" {} '' mkdir $out ln -s ${modulesDoc} $out/modules.xml ln -s ${optionsDocBook} $out/options-db.xml printf "%s" "${version}" > $out/version ''; copySources = '' cp -prd $sources/* . # */ ln -s ${generatedSources} ./generated chmod -R u+w . ''; toc = builtins.toFile "toc.xml" '' ''; manualXsltprocOptions = toString [ "--param section.autolabel 1" "--param section.label.includes.component.label 1" "--stringparam html.stylesheet 'style.css overrides.css highlightjs/mono-blue.css'" "--stringparam html.script './highlightjs/highlight.pack.js ./highlightjs/loader.js'" "--param xref.with.number.and.title 1" "--param toc.section.depth 3" "--stringparam admon.style ''" "--stringparam callout.graphics.extension .svg" "--stringparam current.docid manual" "--param chunk.section.depth 0" "--param chunk.first.sections 1" "--param use.id.as.filename 1" "--stringparam generate.toc 'book toc appendix toc'" "--stringparam chunk.toc ${toc}" ]; manual-combined = runCommand "home-manager-manual-combined" { inherit sources; nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin ]; meta.description = "The Home Manager manual as plain docbook XML"; } '' ${copySources} xmllint --xinclude --output ./manual-combined.xml ./manual.xml xmllint --xinclude --noxincludenode \ --output ./man-pages-combined.xml ./man-pages.xml # outputs the context of an xmllint error output # LEN lines around the failing line are printed function context { # length of context local LEN=6 # lines to print before error line local BEFORE=4 # xmllint output lines are: # file.xml:1234: there was an error on line 1234 while IFS=':' read -r file line rest; do echo if [[ -n "$rest" ]]; then echo "$file:$line:$rest" local FROM=$(($line>$BEFORE ? $line - $BEFORE : 1)) # number lines & filter context nl --body-numbering=a "$file" | sed -n "$FROM,+$LEN p" else if [[ -n "$line" ]]; then echo "$file:$line" else echo "$file" fi fi done } function lintrng { xmllint --debug --noout --nonet \ --relaxng ${docbook5}/xml/rng/docbook/docbook.rng \ "$1" \ 2>&1 | context 1>&2 # ^ redirect assumes xmllint doesn’t print to stdout } lintrng manual-combined.xml lintrng man-pages-combined.xml mkdir $out cp manual-combined.xml $out/ cp man-pages-combined.xml $out/ ''; olinkDB = runCommand "manual-olinkdb" { inherit sources; nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin ]; } '' xsltproc \ ${manualXsltprocOptions} \ --stringparam collect.xref.targets only \ --stringparam targets.filename "$out/manual.db" \ --nonet \ ${docbook5_xsl}/xml/xsl/docbook/xhtml/chunktoc.xsl \ ${manual-combined}/manual-combined.xml cat > "$out/olinkdb.xml" < ]> Allows for cross-referencing olinks between the manpages and manual. &manualtargets; EOF ''; in rec { inherit generatedSources; # The Home Manager options in JSON format. optionsJSON = runCommand "options-json" { meta.description = "List of Home Manager options in JSON format"; } '' # Export list of options in different format. dst=$out/share/doc/home-manager mkdir -p $dst cp ${builtins.toFile "options.json" (builtins.unsafeDiscardStringContext (builtins.toJSON (builtins.listToAttrs (map (o: { name = o.name; value = removeAttrs o ["name" "visible" "internal"]; }) optionsList)))) } $dst/options.json mkdir -p $out/nix-support echo "file json $dst/options.json" >> $out/nix-support/hydra-build-products ''; # */ # Generate the Home Manager manual. manual = runCommand "home-manager-manual" { inherit sources; nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin ]; meta.description = "The Home Manager manual in HTML format"; allowedReferences = ["out"]; } '' # Generate the HTML manual. dst=$out/share/doc/home-manager mkdir -p $dst xsltproc \ ${manualXsltprocOptions} \ --stringparam target.database.document "${olinkDB}/olinkdb.xml" \ --nonet --output $dst/ \ ${docbook5_xsl}/xml/xsl/docbook/xhtml/chunktoc.xsl \ ${manual-combined}/manual-combined.xml mkdir -p $dst/images/callouts cp ${docbook5_xsl}/xml/xsl/docbook/images/callouts/*.svg $dst/images/callouts/ cp ${./style.css} $dst/style.css cp ${./overrides.css} $dst/overrides.css cp -r ${pkgs.documentation-highlighter} $dst/highlightjs mkdir -p $out/nix-support echo "nix-build out $out" >> $out/nix-support/hydra-build-products echo "doc manual $dst" >> $out/nix-support/hydra-build-products ''; # */ manualEpub = runCommand "home-manager-manual-epub" { inherit sources; buildInputs = [ libxml2.bin libxslt.bin zip ]; } '' # Generate the epub manual. dst=$out/share/doc/home-manager xsltproc \ ${manualXsltprocOptions} \ --stringparam target.database.document "${olinkDB}/olinkdb.xml" \ --nonet --xinclude --output $dst/epub/ \ ${docbook5_xsl}/xml/xsl/docbook/epub/docbook.xsl \ ${manual-combined}/manual-combined.xml mkdir -p $dst/epub/OEBPS/images/callouts cp -r ${docbook5_xsl}/xml/xsl/docbook/images/callouts/*.svg $dst/epub/OEBPS/images/callouts # */ echo "application/epub+zip" > mimetype manual="$dst/home-manager-manual.epub" zip -0Xq "$manual" mimetype cd $dst/epub && zip -Xr9D "$manual" * rm -rf $dst/epub mkdir -p $out/nix-support echo "doc-epub manual $manual" >> $out/nix-support/hydra-build-products ''; # Generate the Home Manager manpages. manpages = runCommand "home-manager-manpages" { inherit sources; nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin ]; allowedReferences = ["out"]; } '' # Generate manpages. mkdir -p $out/share/man xsltproc --nonet \ --param man.output.in.separate.dir 1 \ --param man.output.base.dir "'$out/share/man/'" \ --param man.endnotes.are.numbered 0 \ --param man.break.after.slash 1 \ --stringparam target.database.document "${olinkDB}/olinkdb.xml" \ ${docbook5_xsl}/xml/xsl/docbook/manpages/docbook.xsl \ ${manual-combined}/man-pages-combined.xml ''; }