diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml index 1e425248be0fc..cdc23ad888b18 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml @@ -163,6 +163,20 @@ ~100MB for python itself). + + + documentation.man has been refactored to + support choosing a man implementation other than GNU’s + man-db. For this, + documentation.man.manualPages has been + renamed to + documentation.man.man-db.manualPages. If + you want to use the new alternative man implementation + mandoc, add + documentation.man = { enable = true; man-db.enable = false; mandoc.enable = true; } + to your configuration. + +
diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md index ca89732fa801d..27ebceab2ec9a 100644 --- a/nixos/doc/manual/release-notes/rl-2205.section.md +++ b/nixos/doc/manual/release-notes/rl-2205.section.md @@ -60,6 +60,8 @@ In addition to numerous new and upgraded packages, this release has the followin This has the added benefit to reduce the closure size of `ipython` from ~400MB to ~160MB (including ~100MB for python itself). +- `documentation.man` has been refactored to support choosing a man implementation other than GNU's `man-db`. For this, `documentation.man.manualPages` has been renamed to `documentation.man.man-db.manualPages`. If you want to use the new alternative man implementation `mandoc`, add `documentation.man = { enable = true; man-db.enable = false; mandoc.enable = true; }` to your configuration. + ## Other Notable Changes {#sec-release-22.05-notable-changes} - The option [services.redis.servers](#opt-services.redis.servers) was added diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix index 64b1c15086fc8..bb294addccd96 100644 --- a/nixos/modules/misc/documentation.nix +++ b/nixos/modules/misc/documentation.nix @@ -74,10 +74,6 @@ let ]; }; - # list of man outputs currently active intended for use as default values - # for man-related options, thus "man" is included unconditionally. - activeManOutputs = [ "man" ] ++ lib.optionals cfg.dev.enable [ "devman" ]; - in { @@ -107,8 +103,8 @@ in type = types.bool; default = true; description = '' - Whether to install manual pages and the man command. - This also includes "man" outputs. + Whether to install manual pages. + This also includes man outputs. ''; }; @@ -116,27 +112,18 @@ in type = types.bool; default = false; description = '' - Whether to generate the manual page index caches using - mandb(8). This allows searching for a page or - keyword using utilities like apropos(1). - ''; - }; - - man.manualPages = mkOption { - type = types.path; - default = pkgs.buildEnv { - name = "man-paths"; - paths = config.environment.systemPackages; - pathsToLink = [ "/share/man" ]; - extraOutputsToInstall = activeManOutputs; - ignoreCollisions = true; - }; - defaultText = literalDocBook "all man pages in "; - description = '' - The manual pages to generate caches for if - is enabled. Must be a path to a directory with man pages under - /share/man; see the source for an example. - Advanced users can make this a content-addressed derivation to save a few rebuilds. + Whether to generate the manual page index caches. + This allows searching for a page or + keyword using utilities like + + apropos + 1 + + and the -k option of + + man + 1 + . ''; }; @@ -220,30 +207,22 @@ in }; config = mkIf cfg.enable (mkMerge [ + { + assertions = [ + { + assertion = !(cfg.man.man-db.enable && cfg.man.mandoc.enable); + message = '' + man-db and mandoc can't be used as the default man page viewer at the same time! + ''; + } + ]; + } + # The actual implementation for this lives in man-db.nix or mandoc.nix, + # depending on which backend is active. (mkIf cfg.man.enable { - environment.systemPackages = [ pkgs.man-db ]; environment.pathsToLink = [ "/share/man" ]; - environment.extraOutputsToInstall = activeManOutputs; - environment.etc."man_db.conf".text = - let - manualCache = pkgs.runCommandLocal "man-cache" { } '' - echo "MANDB_MAP ${cfg.man.manualPages}/share/man $out" > man.conf - ${pkgs.man-db}/bin/mandb -C man.conf -psc >/dev/null 2>&1 - ''; - in - '' - # Manual pages paths for NixOS - MANPATH_MAP /run/current-system/sw/bin /run/current-system/sw/share/man - MANPATH_MAP /run/wrappers/bin /run/current-system/sw/share/man - - ${optionalString cfg.man.generateCaches '' - # Generated manual pages cache for NixOS (immutable) - MANDB_MAP /run/current-system/sw/share/man ${manualCache} - ''} - # Manual pages caches for NixOS - MANDB_MAP /run/current-system/sw/share/man /var/cache/man/nixos - ''; + environment.extraOutputsToInstall = [ "man" ] ++ optional cfg.dev.enable "devman"; }) (mkIf cfg.info.enable { diff --git a/nixos/modules/misc/man-db.nix b/nixos/modules/misc/man-db.nix new file mode 100644 index 0000000000000..8bd329bc4e0c3 --- /dev/null +++ b/nixos/modules/misc/man-db.nix @@ -0,0 +1,73 @@ +{ config, pkgs, lib, ... }: + +let + cfg = config.documentation.man.man-db; +in + +{ + options = { + documentation.man.man-db = { + enable = lib.mkEnableOption "man-db as the default man page viewer" // { + default = config.documentation.man.enable; + defaultText = lib.literalExpression "config.documentation.man.enable"; + example = false; + }; + + manualPages = lib.mkOption { + type = lib.types.path; + default = pkgs.buildEnv { + name = "man-paths"; + paths = config.environment.systemPackages; + pathsToLink = [ "/share/man" ]; + extraOutputsToInstall = [ "man" ] + ++ lib.optionals config.documentation.dev.enable [ "devman" ]; + ignoreCollisions = true; + }; + defaultText = lib.literalDocBook "all man pages in "; + description = '' + The manual pages to generate caches for if + is enabled. Must be a path to a directory with man pages under + /share/man; see the source for an example. + Advanced users can make this a content-addressed derivation to save a few rebuilds. + ''; + }; + + package = lib.mkOption { + type = lib.types.package; + default = pkgs.man-db; + defaultText = lib.literalExpression "pkgs.man-db"; + description = '' + The man-db derivation to use. Useful to override + configuration options used for the package. + ''; + }; + }; + }; + + imports = [ + (lib.mkRenamedOptionModule [ "documentation" "man" "manualPages" ] [ "documentation" "man" "man-db" "manualPages" ]) + ]; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + environment.etc."man_db.conf".text = + let + manualCache = pkgs.runCommandLocal "man-cache" { } '' + echo "MANDB_MAP ${cfg.manualPages}/share/man $out" > man.conf + ${cfg.package}/bin/mandb -C man.conf -psc >/dev/null 2>&1 + ''; + in + '' + # Manual pages paths for NixOS + MANPATH_MAP /run/current-system/sw/bin /run/current-system/sw/share/man + MANPATH_MAP /run/wrappers/bin /run/current-system/sw/share/man + + ${lib.optionalString config.documentation.man.generateCaches '' + # Generated manual pages cache for NixOS (immutable) + MANDB_MAP /run/current-system/sw/share/man ${manualCache} + ''} + # Manual pages caches for NixOS + MANDB_MAP /run/current-system/sw/share/man /var/cache/man/nixos + ''; + }; +} diff --git a/nixos/modules/misc/mandoc.nix b/nixos/modules/misc/mandoc.nix new file mode 100644 index 0000000000000..3da60f2f8e65f --- /dev/null +++ b/nixos/modules/misc/mandoc.nix @@ -0,0 +1,61 @@ +{ config, lib, pkgs, ... }: + +let + makewhatis = "${lib.getBin cfg.package}/bin/makewhatis"; + + cfg = config.documentation.man.mandoc; + +in { + meta.maintainers = [ lib.maintainers.sternenseemann ]; + + options = { + documentation.man.mandoc = { + enable = lib.mkEnableOption "mandoc as the default man page viewer"; + + manPath = lib.mkOption { + type = with lib.types; listOf str; + default = [ "share/man" ]; + example = lib.literalExpression "[ \"share/man\" \"share/man/fr\" ]"; + description = '' + Change the manpath, i. e. the directories where + man1 + looks for section-specific directories of man pages. + You only need to change this setting if you want extra man pages + (e. g. in non-english languages). All values must be strings that + are a valid path from the target prefix (without including it). + The first value given takes priority. + ''; + }; + + package = lib.mkOption { + type = lib.types.package; + default = pkgs.mandoc; + defaultText = lib.literalExpression "pkgs.mandoc"; + description = '' + The mandoc derivation to use. Useful to override + configuration options used for the package. + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + environment = { + systemPackages = [ cfg.package ]; + + # tell mandoc about man pages + etc."man.conf".text = lib.concatMapStrings (path: '' + manpath /run/current-system/sw/${path} + '') cfg.manPath; + + # create mandoc.db for whatis(1), apropos(1) and man(1) -k + # TODO(@sternenseemman): fix symlinked directories not getting indexed, + # see: https://inbox.vuxu.org/mandoc-tech/20210906171231.GF83680@athene.usta.de/T/#e85f773c1781e3fef85562b2794f9cad7b2909a3c + extraSetup = lib.mkIf config.documentation.man.generateCaches '' + ${makewhatis} -T utf8 ${ + lib.concatMapStringsSep " " (path: "\"$out/${path}\"") cfg.manPath + } + ''; + }; + }; +} diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 55de017350248..33c13e3855c0c 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -108,6 +108,8 @@ ./misc/lib.nix ./misc/label.nix ./misc/locate.nix + ./misc/man-db.nix + ./misc/mandoc.nix ./misc/meta.nix ./misc/nixpkgs.nix ./misc/passthru.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 09cd26a766925..ab9ade1124a7a 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -255,6 +255,7 @@ in magnetico = handleTest ./magnetico.nix {}; mailcatcher = handleTest ./mailcatcher.nix {}; mailhog = handleTest ./mailhog.nix {}; + man = handleTest ./man.nix {}; mariadb-galera-mariabackup = handleTest ./mysql/mariadb-galera-mariabackup.nix {}; mariadb-galera-rsync = handleTest ./mysql/mariadb-galera-rsync.nix {}; matomo = handleTest ./matomo.nix {}; diff --git a/nixos/tests/man.nix b/nixos/tests/man.nix new file mode 100644 index 0000000000000..1ff5af4e80591 --- /dev/null +++ b/nixos/tests/man.nix @@ -0,0 +1,100 @@ + +import ./make-test-python.nix ({ pkgs, lib, ... }: let + manImplementations = [ + "mandoc" + "man-db" + ]; + + machineNames = builtins.map machineSafe manImplementations; + + makeConfig = useImpl: { + # Note: mandoc currently can't index symlinked section directories. + # So if a man section comes from one package exclusively (e. g. + # 1p from man-pages-posix and 2 from man-pages), it isn't searchable. + environment.systemPackages = [ + pkgs.man-pages + pkgs.openssl + pkgs.libunwind + ]; + + documentation = { + enable = true; + nixos.enable = lib.mkForce true; + dev.enable = true; + man = { + enable = true; + generateCaches = true; + } // lib.listToAttrs (builtins.map (impl: { + name = impl; + value = { + enable = useImpl == impl; + }; + }) manImplementations); + }; + }; + + machineSafe = builtins.replaceStrings [ "-" ] [ "_" ]; +in { + name = "man"; + meta.maintainers = [ lib.maintainers.sternenseemann ]; + + nodes = lib.listToAttrs (builtins.map (i: { + name = machineSafe i; + value = makeConfig i; + }) manImplementations); + + testScript = '' + import re + start_all() + + def match_man_k(page, section, haystack): + """ + Check if the man page {page}({section}) occurs in + the output of `man -k` given as haystack. Note: + This is not super reliable, e. g. it can't deal + with man pages that are in multiple sections. + """ + + for line in haystack.split("\n"): + # man -k can look like this: + # page(3) - bla + # page (3) - bla + # pagea, pageb (3, 3P) - foo + # pagea, pageb, pagec(3) - bar + pages = line.split("(")[0] + sections = re.search("\\([a-zA-Z1-9, ]+\\)", line) + if sections is None: + continue + else: + sections = sections.group(0)[1:-1] + + if page in pages and f'{section}' in sections: + return True + + return False + + '' + lib.concatMapStrings (machine: '' + with subtest("Test direct man page lookups in ${machine}"): + # man works + ${machine}.succeed("man man > /dev/null") + # devman works + ${machine}.succeed("man 3 libunwind > /dev/null") + # NixOS configuration man page is installed + ${machine}.succeed("man configuration.nix > /dev/null") + + with subtest("Test generateCaches via man -k in ${machine}"): + expected = [ + ("openssl", "ssl", 3), + ("unwind", "libunwind", 3), + ("user", "useradd", 8), + ("user", "userdel", 8), + ("mem", "free", 3), + ("mem", "free", 1), + ] + + for (keyword, page, section) in expected: + matches = ${machine}.succeed(f"man -k {keyword}") + if not match_man_k(page, section, matches): + raise Exception(f"{page}({section}) missing in matches: {matches}") + '') machineNames; +}) diff --git a/pkgs/tools/misc/man-db/default.nix b/pkgs/tools/misc/man-db/default.nix index f1739cbd50dd2..d495c912f5996 100644 --- a/pkgs/tools/misc/man-db/default.nix +++ b/pkgs/tools/misc/man-db/default.nix @@ -1,4 +1,4 @@ -{ lib, stdenv, fetchurl, pkg-config, libpipeline, db, groff, libiconv, makeWrapper, buildPackages }: +{ lib, stdenv, fetchurl, pkg-config, libpipeline, db, groff, libiconv, makeWrapper, buildPackages, nixosTests }: stdenv.mkDerivation rec { pname = "man-db"; @@ -73,6 +73,10 @@ stdenv.mkDerivation rec { doCheck = !stdenv.hostPlatform.isMusl /* iconv binary */ && !stdenv.hostPlatform.isDarwin; + passthru.tests = { + nixos = nixosTests.man; + }; + meta = with lib; { homepage = "http://man-db.nongnu.org"; description = "An implementation of the standard Unix documentation system accessed using the man command"; diff --git a/pkgs/tools/misc/mandoc/default.nix b/pkgs/tools/misc/mandoc/default.nix index 1771f6515bc41..2d974b8af63dd 100644 --- a/pkgs/tools/misc/mandoc/default.nix +++ b/pkgs/tools/misc/mandoc/default.nix @@ -1,4 +1,4 @@ -{ lib, stdenv, fetchurl, zlib, perl }: +{ lib, stdenv, fetchurl, zlib, perl, nixosTests }: let # check if we can execute binaries for the host platform on the build platform @@ -62,6 +62,10 @@ stdenv.mkDerivation rec { checkInputs = [ perl ]; preCheck = "patchShebangs --build regress/regress.pl"; + passthru.tests = { + nixos = nixosTests.man; + }; + meta = with lib; { homepage = "https://mandoc.bsd.lv/"; description = "suite of tools compiling mdoc and man";