{ pkgs, # Note, this should be "the standard library" + HM extensions. lib ? import ../modules/lib/stdlib-extended.nix pkgs.lib, release, isReleaseBranch, }: let # Recursively replace each derivation in the given attribute set # with the same derivation but with the `outPath` attribute set to # the string `"\${pkgs.attribute.path}"`. This allows the # documentation to refer to derivations through their values without # establishing an actual dependency on the derivation output. # # This is not perfect, but it seems to cover a vast majority of use # cases. # # Caveat: even if the package is reached by a different means, the # path above will be shown and not e.g. # `${config.services.foo.package}`. scrubDerivations = prefixPath: attrs: let scrubDerivation = name: value: let pkgAttrName = prefixPath + "." + name; in if lib.isAttrs value then scrubDerivations pkgAttrName value // lib.optionalAttrs (lib.isDerivation value) { outPath = "\${${pkgAttrName}}"; } else value; in lib.mapAttrs scrubDerivation attrs; # Make sure the used package is scrubbed to avoid actually # instantiating derivations. scrubbedPkgsModule = { imports = [ { _module.args = { pkgs = lib.mkForce (scrubDerivations "pkgs" pkgs); pkgs_i686 = lib.mkForce { }; }; } ]; }; dontCheckDefinitions = { _module.check = false; }; gitHubDeclaration = user: repo: subpath: let urlRef = if isReleaseBranch then "release-${release}" else "master"; in { url = "https://github.com/${user}/${repo}/blob/${urlRef}/${subpath}"; name = "<${repo}/${subpath}>"; }; hmPath = toString ./..; buildOptionsDocs = args@{ modules, includeModuleSystemOptions ? true, ... }: let # to discourage references from option descriptions and defaults. poisonModule = let poisonAttr = n: { name = n; value = abort '' error: the option documentation has a dependency on the configuration. You may, for example, have added an option attribute like default = ''${config.some.value}; Since the default value is included in the Home Manager manual, this would make the manual depend on the user's configuration. To avoid this problem in this particular case, consider changing to default = ''${config.some.value}; defaultText = lib.literalExpression "\\''${config.some.value}";''; }; in { options, ... }: { config = lib.listToAttrs (map poisonAttr (lib.filter (n: n != "_module") (lib.attrNames options))); }; options = (lib.evalModules { modules = modules ++ [ poisonModule ]; class = "homeManager"; }).options; in pkgs.buildPackages.nixosOptionsDoc ( { options = if includeModuleSystemOptions then options else builtins.removeAttrs options [ "_module" ]; transformOptions = opt: opt // { # Clean up declaration sites to not refer to the Home Manager # source tree. declarations = map ( decl: if lib.hasPrefix hmPath (toString decl) then gitHubDeclaration "nix-community" "home-manager" ( lib.removePrefix "/" (lib.removePrefix hmPath (toString decl)) ) else if decl == "lib/modules.nix" then # TODO: handle this in a better way (may require upstream # changes to nixpkgs) gitHubDeclaration "NixOS" "nixpkgs" decl else decl ) opt.declarations; }; } // builtins.removeAttrs args [ "modules" "includeModuleSystemOptions" ] ); hmOptionsDocs = buildOptionsDocs { modules = import ../modules/modules.nix { inherit lib pkgs; check = false; } ++ [ scrubbedPkgsModule ]; variablelistId = "home-manager-options"; }; nixosOptionsDocs = buildOptionsDocs { modules = [ ../nixos scrubbedPkgsModule dontCheckDefinitions ]; includeModuleSystemOptions = false; variablelistId = "nixos-options"; optionIdPrefix = "nixos-opt-"; }; nixDarwinOptionsDocs = buildOptionsDocs { modules = [ ../nix-darwin scrubbedPkgsModule dontCheckDefinitions ]; includeModuleSystemOptions = false; variablelistId = "nix-darwin-options"; optionIdPrefix = "nix-darwin-opt-"; }; release-config = builtins.fromJSON (builtins.readFile ../release.json); revision = "release-${release-config.release}"; # Generate the `man home-configuration.nix` package home-configuration-manual = pkgs.runCommand "home-configuration-reference-manpage" { nativeBuildInputs = [ pkgs.buildPackages.installShellFiles pkgs.nixos-render-docs ]; allowedReferences = [ "out" ]; } '' # Generate manpages. mkdir -p $out/share/man/man5 mkdir -p $out/share/man/man1 nixos-render-docs -j $NIX_BUILD_CORES options manpage \ --revision ${revision} \ --header ${./home-configuration-nix-header.5} \ --footer ${./home-configuration-nix-footer.5} \ ${hmOptionsDocs.optionsJSON}/share/doc/nixos/options.json \ $out/share/man/man5/home-configuration.nix.5 cp ${./home-manager.1} $out/share/man/man1/home-manager.1 ''; # Generate the HTML manual pages home-manager-manual = pkgs.callPackage ./home-manager-manual.nix { home-manager-options = { home-manager = hmOptionsDocs.optionsJSON; nixos = nixosOptionsDocs.optionsJSON; nix-darwin = nixDarwinOptionsDocs.optionsJSON; }; inherit revision; }; html = home-manager-manual; htmlOpenTool = pkgs.callPackage ./html-open-tool.nix { } { inherit html; }; in { options = { # TODO: Use `hmOptionsDocs.optionsJSON` directly once upstream # `nixosOptionsDoc` is more customizable. json = pkgs.runCommand "options.json" { meta.description = "List of Home Manager options in JSON format"; } '' mkdir -p $out/{share/doc,nix-support} cp -a ${hmOptionsDocs.optionsJSON}/share/doc/nixos $out/share/doc/home-manager substitute \ ${hmOptionsDocs.optionsJSON}/nix-support/hydra-build-products \ $out/nix-support/hydra-build-products \ --replace-fail \ '${hmOptionsDocs.optionsJSON}/share/doc/nixos' \ "$out/share/doc/home-manager" ''; }; manPages = home-configuration-manual; manual = { inherit html htmlOpenTool; }; # Unstable, mainly for CI. jsonModuleMaintainers = pkgs.writeText "hm-module-maintainers.json" ( let result = lib.evalModules { modules = import ../modules/modules.nix { inherit lib pkgs; check = false; } ++ [ scrubbedPkgsModule ]; class = "homeManager"; }; in builtins.toJSON result.config.meta.maintainers ); }