diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 81ea4f054..e04af64fa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,18 +25,11 @@ jobs: if: ${{ github.repository_owner == 'pypa' || github.event_name != 'schedule' }} runs-on: ubuntu-latest timeout-minutes: 20 - continue-on-error: >- - ${{ fromJSON(matrix.continue-on-error) }} strategy: matrix: noxenv: - build - continue-on-error: - - false - include: - - noxenv: linkcheck - continue-on-error: >- # Don't block PRs on linkcheck unrelated failures - ${{ toJSON(github.event_name == 'pull_request') }} + - linkcheck steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 @@ -50,17 +43,44 @@ jobs: cache: 'pip' cache-dependency-path: 'requirements.txt' + - name: Restore linkcheck report + if: matrix.noxenv == 'linkcheck' && github.event_name == 'pull_request' + id: linkcheck-cache + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: build/output.json + key: linkcheck-${{ github.event.pull_request.base.sha }} + restore-keys: | + linkcheck- + - name: Install dependencies run: | python -m pip install --upgrade nox virtualenv - name: Nox ${{ matrix.noxenv }} + id: nox + continue-on-error: ${{ matrix.noxenv == 'linkcheck' && github.event_name == 'pull_request' }} env: # Authenticate github.com requests during linkcheck to avoid rate limits. GITHUB_TOKEN: ${{ matrix.noxenv == 'linkcheck' && github.token || '' }} run: | python -m nox -s ${{ matrix.noxenv }} + - name: Fail on newly added broken links + if: >- + matrix.noxenv == 'linkcheck' && + github.event_name == 'pull_request' && + steps.linkcheck-cache.outputs.cache-hit != '' && + steps.nox.outcome == 'failure' + run: exit 1 + + - name: Save linkcheck report + if: matrix.noxenv == 'linkcheck' && github.event_name != 'pull_request' && success() + uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: build/output.json + key: linkcheck-${{ github.sha }} + check: # This job does nothing and is only used for the branch protection diff --git a/source/conf.py b/source/conf.py index 4516880ec..171a558b6 100644 --- a/source/conf.py +++ b/source/conf.py @@ -1,8 +1,10 @@ # -- Project information --------------------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +import json import os import pathlib +import re import sys _ROOT = pathlib.Path(__file__).resolve().parent.parent @@ -167,6 +169,21 @@ ] linkcheck_retries = 2 linkcheck_timeout = 30 + +# On pull requests, ignore links that were already checked on the default +# branch. The cached JSONL report is refreshed after successful default-branch +# linkcheck runs, so newly introduced links remain blocking. +if os.getenv("GITHUB_EVENT_NAME") == "pull_request": + previous_linkcheck_report = pathlib.Path("build/output.json") + if previous_linkcheck_report.exists(): + for line in previous_linkcheck_report.read_text(encoding="utf-8").splitlines(): + try: + uri = json.loads(line)["uri"] + except (json.JSONDecodeError, KeyError, TypeError): + continue + if isinstance(uri, str): + linkcheck_ignore.append(rf"^{re.escape(uri)}$") + # Ignore anchors for common targets when we know they likely won't be found linkcheck_anchors_ignore_for_url = [ # GitHub synthesises anchors in JavaScript, so Sphinx can't find them in the HTML