From a1abb3eb6da507d08aa1d14e996f8d4c094aa110 Mon Sep 17 00:00:00 2001 From: "Alexander V. Hopp" Date: Tue, 11 Aug 2026 16:27:30 +0200 Subject: [PATCH] ci: run documentation link checking as a separate job Decouple external link checking from the docs build: add a dedicated linkcheck tox environment and CI job, make check_links.py runnable standalone, and stop running examples in the build_docs CI job. --- .github/workflows/ci.yml | 21 +++++++++++++++++- docs/scripts/build_documentation.py | 33 ++++++++--------------------- docs/scripts/check_links.py | 12 +++++++++-- tox.ini | 15 ++++++++++--- 4 files changed, 51 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b8f40c73e..79713cce3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,9 +88,28 @@ jobs: - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: {python-version: "3.10"} - name: Build Docs + # Uses dummy examples for speed. Example correctness is validated by + # the fulltest job. Full example execution happens in the docs.yml + # deployment workflow on merge to main. run: | pip install tox-uv - tox -e docs-py310 -- -r + tox -e docs-py310 + + linkcheck: + name: "Link Check" + runs-on: ubuntu-latest + needs: [lint] + continue-on-error: true + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 + with: {python-version: "3.10"} + - name: Check external links + run: | + pip install tox-uv + tox -e linkcheck typecheck: needs: [lint] diff --git a/docs/scripts/build_documentation.py b/docs/scripts/build_documentation.py index 756797be50..5b7b013a24 100644 --- a/docs/scripts/build_documentation.py +++ b/docs/scripts/build_documentation.py @@ -6,7 +6,6 @@ from subprocess import check_call, run from build_examples import build_examples -from check_links import check_links from utils import adjust_pictures parser = argparse.ArgumentParser() @@ -16,16 +15,10 @@ help="Re-run the examples.", action="store_true", ) -parser.add_argument( - "-l", - "--no_linkcheck", - help="Do not check the links.", - action="store_true", -) parser.add_argument( "-r", "--full-rebuild", - help="Perform a full rebuild, independent of `-e` and `-l` flags.", + help="Perform a full rebuild, independent of the `-e` flag.", action="store_true", ) parser.add_argument( @@ -44,7 +37,6 @@ # Parse input arguments args = parser.parse_args() RUN_EXAMPLES = args.run_examples -LINKCHECK = not args.no_linkcheck FULL_REBUILD = args.full_rebuild INCLUDE_WARNINGS = args.include_warnings FORCE = args.force @@ -52,36 +44,35 @@ def build_documentation( run_examples: bool = False, - verify_links: bool = False, full_rebuild: bool = False, force: bool = False, ) -> None: """Build the documentation. A full build of the documentation consists of converting the examples into jupyter - notebooks, executing them, transforming them into markdown files, as well as - checking all links and performing the actual ``sphinx-build``. Such a full build can - be triggered using the ``full_rebuild`` flag. + notebooks, executing them, transforming them into markdown files, and performing the + actual ``sphinx-build``. Such a full build can be triggered using the + ``full_rebuild`` flag. If this flag is not set, this function tries to re-use as much of potentially existing structures like already built examples as possible. This behavior can be changed by using the other flags. + Note: + External link checking has been decoupled from the documentation build and can + be run independently via ``tox -e linkcheck``. + Args: run_examples: Fully recalculate the examples. If this is ``False`` and no folder containing an already built set of examples is found, dummy files replicating the structure of the examples are created. - verify_links: Check both internal and external links. full_rebuild: Perform a full rebuild of the documentation, including a - recalculation of the examples and checking the links. Note that this option - ignores the choices for ``run_examples`` and ``check_links`` if set to - ``True. + recalculation of the examples. force: Force-build the steps, ignoring any errors or warnings. """ examples_directory = pathlib.Path("docs/examples") examples_exist = examples_directory.is_dir() rerun_examples = run_examples or full_rebuild - perform_linkcheck = verify_links or full_rebuild if rerun_examples: build_examples( @@ -98,9 +89,6 @@ def build_documentation( remove_dir=examples_exist, ) - if perform_linkcheck: - check_links() - # Directory where the documentation is build. build_dir = pathlib.Path("docs/build") @@ -128,11 +116,8 @@ def build_documentation( if not INCLUDE_WARNINGS: os.environ["PYTHONWARNINGS"] = "ignore" - print(f"{LINKCHECK=}") - build_documentation( run_examples=RUN_EXAMPLES, - verify_links=LINKCHECK, full_rebuild=FULL_REBUILD, force=FORCE, ) diff --git a/docs/scripts/check_links.py b/docs/scripts/check_links.py index a2e3b415ac..51c3c919ba 100644 --- a/docs/scripts/check_links.py +++ b/docs/scripts/check_links.py @@ -1,10 +1,14 @@ -"""Utility for checking the links of the documentation.""" +"""Utility for checking the external links of the documentation. + +This script can be run standalone via ``tox -e linkcheck`` or directly with +``uv run --locked --extra docs docs/scripts/check_links.py``. +""" from subprocess import check_call def check_links() -> None: - """Check whether the links of the documentation are valid.""" + """Check whether the external links of the documentation are valid.""" link_call = [ "sphinx-build", "-b", @@ -14,3 +18,7 @@ def check_links() -> None: ] check_call(link_call) + + +if __name__ == "__main__": + check_links() diff --git a/tox.ini b/tox.ini index 89a820cb60..2e5e25e102 100644 --- a/tox.ini +++ b/tox.ini @@ -102,10 +102,19 @@ commands = uv run --locked --extra docs docs/scripts/build_documentation.py {posargs} [testenv:docs-quickbuild] -description = Force-build documentation, ignoring links and examples +description = Force-build documentation, ignoring examples skip_install = True setenv = SMOKE_TEST = true -commands = +commands = + python --version + uv run --locked --extra docs docs/scripts/build_documentation.py -f + +[testenv:linkcheck] +description = Check external links in the documentation +skip_install = True +setenv = + SMOKE_TEST = true +commands = python --version - uv run --locked --extra docs docs/scripts/build_documentation.py -f -l + uv run --locked --extra docs docs/scripts/check_links.py