Skip to content

docs: build the package reference pages in parallel - #674

Merged
tonyandrewmeyer merged 3 commits into
canonical:mainfrom
tonyandrewmeyer:rainy/498-parallel-docs-package-build
Sep 10, 2026
Merged

docs: build the package reference pages in parallel#674
tonyandrewmeyer merged 3 commits into
canonical:mainfrom
tonyandrewmeyer:rainy/498-parallel-docs-package-build

Conversation

@tonyandrewmeyer

@tonyandrewmeyer tonyandrewmeyer commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

The docs build runs sphinx-build once per package, one after another, because the packages don't share a dependency graph. Fan those out instead.

  • .docs/extensions/package_docs.py now writes only the shared placeholder rst files, and appends the automodule directive in memory on Sphinx's source-read event. The on-disk rst is then identical for every per-package build, so there is nothing left for concurrent builds to race on.
  • docs.just's _packages recipe fans out over a ThreadPoolExecutor, after a one-shot placeholder pre-pass. Each build gets its own doctree cache and html outdir, and the only shared state is the .save/ pickles, which are already keyed per package.

On this machine (8 cores) just docs html finishes in 49 seconds, where the sequential version had not reached halfway through the package list after 8 minutes. The win depends on core count, so the number on a CI runner will be different and is worth a look.

Fixes #498

tonyandrewmeyer and others added 2 commits August 18, 2026 15:51
Split the per-package sphinx-build loop into a ThreadPoolExecutor fan-out
with a placeholder-writing pre-pass, and inject each build's automodule
directive in-memory at source-read time so parallel builds don't race on
the shared rst files. Each per-package build also gets its own doctree
cache and html outdir under _build/_pkg_pass/<pkg>/ to avoid clobbering.

Local wall-time drops from >8 min (sequential, killed mid-run) to ~1m40s
for a full `just docs html` on this box (8 cores).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019e4ws34Es9T8rKzwu3zW93
@tonyandrewmeyer
tonyandrewmeyer marked this pull request as ready for review September 7, 2026 03:48
@tonyandrewmeyer
tonyandrewmeyer requested a review from a team as a code owner September 7, 2026 03:48

@james-garner-canonical james-garner-canonical left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this should be a really nice speed-up! I'm on board with the basic approach: write out the placeholders first unconditionally, then use in-memory read interception instead of a different written tree for each pass. My main concern is with the build wrapper reaching into the extension internals to call _main -- would it be clean to refactor this into an unconditional preprocessor step instead?

Comment thread docs.just Outdated
Comment thread docs.just Outdated
Comment thread docs.just Outdated
@tonyandrewmeyer

Copy link
Copy Markdown
Contributor Author

My main concern is with the build wrapper reaching into the extension internals to call _main -- would it be clean to refactor this into an unconditional preprocessor step instead?

Done. .docs/scripts/package_docs_preprocessor.py is now a standalone preprocessor alongside diataxis_preprocessor.py, run by a _package_docs recipe that _packages depends on. The _packages wrapper no longer touches sys.path or imports the extension, and package_docs.py has dropped its builder-inited hook. It's purely the source-read/doctree save-restore extension now.

The `_packages` recipe reached into the `package_docs` extension internals, inserting
the extensions directory on `sys.path` to call `_main` directly. The placeholders it
writes are identical for every per-package build, so generating them is a one-time
preparation of the source tree rather than per-build work.

Move that logic into `.docs/scripts/package_docs_preprocessor.py`, alongside the
existing `diataxis_preprocessor.py`, and run it from a new `_package_docs` recipe that
`_packages` depends on. `package_docs.py` drops its `builder-inited` hook and is now
purely the source-read and doctree save/restore extension.

Also count failed package builds and exit with that count, and hoist `BUILD_DIR`
alongside `ROOT`.

Refs canonical#498
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V6VNKeqxRpjmNNmjRMrPoy

@james-garner-canonical james-garner-canonical left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this looks great!

@tonyandrewmeyer
tonyandrewmeyer merged commit 4187fd7 into canonical:main Sep 10, 2026
20 checks passed
@tonyandrewmeyer
tonyandrewmeyer deleted the rainy/498-parallel-docs-package-build branch September 10, 2026 03:06
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.

Investigate how to speed up the package docs reference build

2 participants