Skip to content

manpages: generate translated manpages at build time with po4a - #94

Merged
got3nks merged 1 commit into
amule-org:masterfrom
got3nks:feat/manpages-build-time-po4a
Jun 11, 2026
Merged

manpages: generate translated manpages at build time with po4a#94
got3nks merged 1 commit into
amule-org:masterfrom
got3nks:feat/manpages-build-time-po4a

Conversation

@got3nks

@got3nks got3nks commented Jun 11, 2026

Copy link
Copy Markdown

Closes #69.

Summary

Stop tracking the ~100 pre-rendered docs/man/*.{lang}.1.in and src/utils/*/docs/*.{lang}.1.in. Generate them at build time from the English masters + docs/man/po/manpages-*.po via po4a, then install them under <prefix>/share/man/<lang>/man1/.

Why

Weblate edits docs/man/po/manpages-*.po but cannot run po4a, so every Weblate translation PR left the tracked renders stale and turned the manpages-sync CI red (e.g. #66). The stopgap was to comment that check out, which let the tracked renders deliberately lag behind the translations. Tracking generated files also forced CI to pin po4a 0.74 from Debian trixie because po4a's output wrapping changes between versions and any mismatch surfaced as spurious drift.

Pipeline

  • docs/man/po4a.config.in — CMake-templated config. @CMAKE_*_DIR@ references resolve to absolute build-dir output paths at configure time so po4a writes straight into the build tree.
  • docs/man/CMakeLists.txt — adds cmake_dependent_option(TRANSLATED_MANPAGES ... ON ENABLE_NLS OFF). find_program(po4a) — if missing, install only English masters and surface a STATUS message; po4a is an optional build dep. A single add_custom_command runs po4a with DEPENDS on every manpages-*.po, then a per-output add_custom_command runs cmake/configure_translated_manpage.cmake to substitute @MAN_DATE@ and @PACKAGE_VERSION@ (po4a passes those through verbatim). The two-pass split is required because configure_file is configure-time only; the rendered .1.in inputs don't exist yet when CMakeLists.txt runs.
  • cmake/configure_translated_manpage.cmake — tiny helper that re-implements configure_file @ONLY semantics via file(READ) + string(CONFIGURE) + file(WRITE), invoked via cmake -P from the custom command at build time.
  • scripts/update-manpages-po.sh — counterpart to scripts/update-po.sh for translators. Runs po4a --no-translations against a temp docs/man-relative config (so #: source refs land as relative paths, not the developer's absolute filesystem path), regenerating manpages.pot and merging into manpages-*.po.
  • .github/workflows/i18n.yml — replaces the disabled manpages-sync with a manpages-pot-sync that re-runs update-manpages-po.sh and diffs the result, matching the existing pot-sync job for the application catalogs. Weblate can never break this one — Weblate only edits .po files.
  • .github/workflows/release.yml — new source-bundle job builds aMule-<TAG>-src.tar.gz containing every tracked file plus the freshly-rendered translated manpages, attached to the draft Release alongside the binary artifacts. Downstream packagers can consume that tarball instead of the GitHub-generated "Source code" archive to avoid po4a as a build-time dependency.

Mtime gotcha

The disabled manpages-sync job documented a po4a "outputs up-to-date" mtime check that broke on fresh checkouts. That goes away entirely once nothing rendered is in git: CMake's DEPENDS on the .po inputs drives incremental rebuilds rather than po4a's own mtime check.

Test plan

Executed on macOS ARM64 + Ubuntu 26.04 ARM64 (amule-dev-vm).

  • cmake -B build configures cleanly with TRANSLATED_MANPAGES=ON (Mac + Linux), po4a discovered, po4a.config materialised in build dir with absolute paths
  • cmake --build build (ALL) renders 100 .lang.1.in via po4a then substitutes into 100 .lang.1 files (verified in build/docs/man/ and build/src/utils/)
  • cmake --install build lays them under <prefix>/share/man/<lang>/man1/<binary>.1
  • French amuled.1 content correct: .TH AMULED 1 "June 2026" "aMule Daemon vGIT" "aMule Daemon", body has .SH NOM, "le client P2P eMule multiplateforme — version daemonisée"
  • English amuled.1 has matching .TH substitution
  • scripts/update-manpages-po.sh is idempotent: produces only the POT-Creation-Date timestamp diff when run with no changes to the English masters
  • #: source references in the regenerated .pot are repo-relative (amule.1.in:1), not developer-absolute
  • Per-language gating via TRANSLATION_<LANG> still works (only enabled langs install)

Migration notes for maintainers

  • After this lands, po4a becomes an optional build-time dependency when ENABLE_NLS=YES. Build hosts without po4a will skip translated manpages and emit a STATUS note. Pass -DTRANSLATED_MANPAGES=NO to opt out explicitly.
  • The 100-file deletion makes the PR diff look enormous but is mechanical — the per-file deletions are listed under D in git status.
  • The pre-rendered tarball attached to each Release supersedes the autoconf-era "ship pre-rendered docs in the dist tarball" trick.

Stop tracking the ~100 pre-rendered docs/man/*.{lang}.1.in and
src/utils/*/docs/*.{lang}.1.in. Generate them at build time from the
English masters + docs/man/po/manpages-*.po via po4a, then install
them under <prefix>/share/man/<lang>/man1/.

Why this matters:

Weblate edits docs/man/po/manpages-*.po but cannot run po4a, so every
Weblate translation PR left the tracked renders stale and turned the
manpages-sync CI red (PR amule-project#66). The stopgap commented the check out
entirely, which let the tracked renders deliberately lag behind the
translations.

Tracking generated files also forced CI to pin po4a 0.74 from
Debian trixie because po4a's output wrapping changes between versions
and any mismatch surfaced as spurious drift.

Pipeline

- docs/man/po4a.config.in: CMake-templated config; @CMAKE_*_DIR@
  references resolve to absolute build-dir output paths at configure
  time, so po4a writes straight into the build tree.
- docs/man/CMakeLists.txt:
  * cmake_dependent_option(TRANSLATED_MANPAGES ... ON ENABLE_NLS OFF)
  * find_program(po4a). If missing, install only English masters and
    surface a STATUS message (po4a is an optional build dep).
  * add_custom_command runs po4a once with DEPENDS on every
    manpages-*.po, then a second custom_command per output runs
    cmake/configure_translated_manpage.cmake to substitute @MAN_DATE@
    and @PACKAGE_VERSION@ (po4a passes those through verbatim).
  * The two-pass split is required because configure_file is
    configure-time only; the rendered .1.in inputs don't exist yet
    when CMakeLists.txt runs.
- cmake/configure_translated_manpage.cmake: tiny helper that
  re-implements configure_file @only semantics via file(READ) +
  string(CONFIGURE) + file(WRITE), invoked via cmake -P from the
  custom_command at build time.
- scripts/update-manpages-po.sh: counterpart to scripts/update-po.sh
  for translators. Runs po4a --no-translations against a temp
  docs/man-relative config (so #: source refs land as relative paths,
  not the developer's absolute fs path), regenerating manpages.pot
  and merging into manpages-*.po.
- .github/workflows/i18n.yml: replace the disabled manpages-sync
  with a manpages-pot-sync that re-runs update-manpages-po.sh and
  diffs the result, matching the existing pot-sync job for the
  application catalogs. Weblate can never break this one — Weblate
  only edits .po files.
- .github/workflows/release.yml: new source-bundle job builds
  aMule-<TAG>-src.tar.gz containing every tracked file plus the
  freshly-rendered translated manpages, so downstream packagers can
  consume a tarball that doesn't require po4a at build time. Attached
  to the draft Release alongside the binary artifacts.

The mtime gotcha the disabled manpages-sync job documents goes away
once nothing rendered is in git: CMake's DEPENDS on the .po inputs
drives incremental rebuilds rather than po4a's own
"outputs up-to-date" check.

Closes amule-project#69.
@got3nks
got3nks merged commit bbc829a into amule-org:master Jun 11, 2026
10 checks passed
@got3nks
got3nks deleted the feat/manpages-build-time-po4a branch June 11, 2026 12:28
@ngosang

ngosang commented Jun 11, 2026

Copy link
Copy Markdown
Member

@got3nks
During the build process, the POT and PO files for the man pages are updated. These modified files remain in my Git repository and clutter up my workspace. Why is it necessary to modify these files? I thought that we only needed to generate the translated man pages.

@got3nks

got3nks commented Jun 11, 2026

Copy link
Copy Markdown
Author

Fixed in #101

got3nks added a commit that referenced this pull request Jun 11, 2026
… tree

PR #94's po4a invocation ran with no flags, so po4a did all three of
its default operations on every cmake --build:

  1. Regenerate docs/man/po/manpages.pot from English masters
  2. Merge the new .pot into every docs/man/po/manpages-*.po
  3. Render the translated *.lang.1.in into the build dir

Only (3) is the build's job; (1) and (2) belong to
scripts/update-manpages-po.sh which a translator runs explicitly.
The side effect was that every plain cmake --build left
docs/man/po/manpages.pot and 10x manpages-*.po showing as modified
in git status, dirtying the working tree at minimum with a refreshed
POT-Creation-Date timestamp.

Pass --no-update to po4a in both the CMake custom_command and the
release.yml source-bundle step. po4a then only renders translated
manpages and leaves po/.pot/.po untouched.

Reported by @ngosang on #94.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generate translated manpages at build time with po4a instead of tracking rendered files in git

2 participants