diff --git a/.readthedocs.yml b/.readthedocs.yml index 4b829fc279..5776806dbd 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -8,6 +8,21 @@ build: - pip install uv post_install: - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH uv pip install -r doc/requirements.txt + build: + html: + # Custom build jobs start at the repository root, unlike Read the + # Docs-generated Sphinx command, which starts in the documentation + # directory. Preserve that working directory so the checkout does not + # shadow the installed package and hide its generated ``deepmd.lib``. + # Bound the command so a wedged extension or worker is terminated and + # reported as a failed build instead of occupying a builder forever. + # Limit Sphinx to two workers so generated API pages can be processed + # concurrently without multiplying backend import memory excessively. + - >- + cd doc && + timeout --signal=TERM --kill-after=30s 30m + python -m sphinx -T -j 2 -b html -d _build/doctrees + -D language=en . $READTHEDOCS_OUTPUT/html apt_packages: - inkscape sphinx: diff --git a/doc/conf.py b/doc/conf.py index 5753b2afed..3fa4f92af4 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -240,13 +240,24 @@ # deeper than that are left unnumbered. Only the listed pages are affected. from typing import ( TYPE_CHECKING, + Any, ) +from dargs.sphinx import ( + DargsDomain, +) from docutils import ( nodes, ) +from sphinx.domains import ( + Domain, +) if TYPE_CHECKING: + from collections.abc import ( + Iterable, + ) + from sphinx.application import ( Sphinx, ) @@ -279,7 +290,27 @@ def _cap_cli_secnumbers(app: Sphinx, doctree: nodes.document, docname: str) -> N break +def _merge_dargs_domaindata( + self: Domain, docnames: Iterable[str], otherdata: dict[str, Any] +) -> None: + """Merge ``dargs`` argument entries collected by a parallel read worker. + + ``dargs.sphinx.DargsDomain`` advertises ``parallel_read_safe`` but does not + implement ``merge_domaindata``, so Sphinx refuses to combine the per-worker + inventories and ``-j`` aborts the build. The domain's only state is a flat + ``targetid -> (docname, objtype)`` map, so merging is a plain dict update + restricted to the documents this worker actually read. + """ + docnames = set(docnames) + arguments = self.data["arguments"] + for targetid, entry in otherdata["arguments"].items(): + if entry[0] in docnames: + arguments[targetid] = entry + + def setup(app: Sphinx) -> dict[str, bool]: + if DargsDomain.merge_domaindata is Domain.merge_domaindata: + DargsDomain.merge_domaindata = _merge_dargs_domaindata # AutoAPI records exact source locations without importing backend modules. # Reuse that metadata for commit-pinned GitHub links after AutoAPI's default # priority-500 ``builder-inited`` callback has populated the environment.