| name: Reusable Docs | |
| on: | |
| workflow_call: | |
| workflow_dispatch: | |
| # Pushes to CPython branches seed the pip caches under the exact keys every | |
| # docs PR restores from. Without a branch-scoped copy, each PR saves a | |
| # duplicate into its own refs/pull/N/merge scope that nothing else can read. | |
| push: | |
| branches: | |
| - main | |
| - '3.*' | |
| paths: | |
| - 'Doc/pylock.toml' | |
| - 'Doc/requirements.txt' | |
| - '.github/workflows/reusable-docs.yml' | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} | |
| cancel-in-progress: true | |
| env: | |
| FORCE_COLOR: 1 | |
| jobs: | |
| build-doc: | |
| name: 'Docs' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| env: | |
| branch_base: 'origin/${{ github.event.pull_request.base.ref }}' | |
| branch_pr: 'origin/${{ github.event.pull_request.head.ref }}' | |
| commits: ${{ github.event.pull_request.commits }} | |
| refspec_base: '+${{ github.event.pull_request.base.sha }}:remotes/origin/${{ github.event.pull_request.base.ref }}' | |
| refspec_pr: '+${{ github.event.pull_request.head.sha }}:remotes/origin/${{ github.event.pull_request.head.ref }}' | |
| steps: | |
| - name: 'Check out latest PR branch commit' | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| ref: >- | |
| ${{ | |
| github.event_name == 'pull_request' | |
| && github.event.pull_request.head.sha | |
| || '' | |
| }} | |
| # Adapted from https://github.com/actions/checkout/issues/520#issuecomment-1167205721 | |
| - name: 'Fetch commits to get branch diff' | |
| if: github.event_name == 'pull_request' | |
| run: | | |
| # Fetch enough history to find a common ancestor commit (aka merge-base): | |
| git fetch origin "${refspec_pr}" --depth=$(( commits + 1 )) \ | |
| --no-tags --prune --no-recurse-submodules | |
| # This should get the oldest commit in the local fetched history (which may not be the commit the PR branched from): | |
| COMMON_ANCESTOR=$( git rev-list --first-parent --max-parents=0 --max-count=1 "${branch_pr}" ) | |
| DATE=$( git log --date=iso8601 --format=%cd "${COMMON_ANCESTOR}" ) | |
| # Get all commits since that commit date from the base branch (eg: master or main): | |
| git fetch origin "${refspec_base}" --shallow-since="${DATE}" \ | |
| --no-tags --prune --no-recurse-submodules | |
| - name: 'Set up Python' | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: '3' | |
| cache: 'pip' | |
| cache-dependency-path: 'Doc/pylock.toml' | |
| - name: 'Install build dependencies' | |
| run: make -C Doc/ venv | |
| # To annotate PRs with Sphinx nitpicks (missing references) | |
| - name: 'Build HTML documentation' | |
| continue-on-error: true | |
| run: | | |
| set -Eeuo pipefail | |
| # Build docs with the nit-picky option; write warnings to file | |
| make -C Doc/ PYTHON=../python SPHINXOPTS="--quiet --nitpicky --warning-file sphinx-warnings.txt" html | |
| - name: 'Check warnings' | |
| if: github.event_name == 'pull_request' | |
| run: | | |
| python Doc/tools/check-warnings.py \ | |
| --annotate-diff "${branch_base}" "${branch_pr}" \ | |
| --fail-if-regression \ | |
| --fail-if-improved \ | |
| --fail-if-new-news-nit | |
| - name: 'Build list of changes' | |
| run: | | |
| make -C Doc/ PYTHON=../python changes | |
| - name: 'Collect HTML IDs' | |
| if: github.event_name == 'pull_request' | |
| run: python Doc/tools/check-html-ids.py collect Doc/build/html -o Doc/build/html-ids-head.json.gz | |
| - name: 'Upload HTML IDs' | |
| if: github.event_name == 'pull_request' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: html-ids-head | |
| path: Doc/build/html-ids-head.json.gz | |
| archive: false | |
| check-html-ids: | |
| name: 'Check for removed HTML IDs' | |
| needs: build-doc | |
| if: github.event_name == 'pull_request' | |
| uses: ./.github/workflows/reusable-check-html-ids.yml | |
| # Run "doctest" on HEAD as new syntax doesn't exist in the latest stable release | |
| doctest: | |
| name: 'Doctest' | |
| runs-on: ubuntu-26.04 | |
| timeout-minutes: 60 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ~/.cache/pip | |
| key: ubuntu-doc-${{ hashFiles('Doc/requirements.txt') }} | |
| restore-keys: | | |
| ubuntu-doc- | |
| - name: 'Install Dependencies' | |
| run: sudo ./.github/workflows/posix-deps-apt.sh && sudo apt-get install --no-install-recommends wamerican | |
| - name: 'Configure CPython' | |
| run: ./configure --with-pydebug | |
| - name: 'Build CPython' | |
| run: make -j4 | |
| - name: 'Install build dependencies' | |
| run: make -C Doc/ PYTHON=../python venv | |
| # Use "xvfb-run" since some doctest tests open GUI windows | |
| - name: 'Run documentation doctest' | |
| run: xvfb-run make -C Doc/ PYTHON=../python SPHINXERRORHANDLING="--fail-on-warning" doctest | |
| check-epub: | |
| name: 'Check EPUB' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: 'Set up Python' | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: '3' | |
| cache: 'pip' | |
| cache-dependency-path: 'Doc/requirements.txt' | |
| - name: 'Install build dependencies' | |
| run: | | |
| make -C Doc/ venv | |
| python -m pip install epubcheck | |
| - name: 'Build EPUB documentation' | |
| run: make -C Doc/ PYTHON=../python epub | |
| - name: 'Run epubcheck' | |
| continue-on-error: true | |
| run: epubcheck Doc/build/epub/Python.epub &> Doc/epubcheck.txt | |
| - run: cat Doc/epubcheck.txt | |
| - name: 'Check for fatal errors in EPUB' | |
| run: python Doc/tools/check-epub.py |