From 26518ab64e5c9408ddad41fedbfc2f064520219e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 15:26:13 +0000 Subject: [PATCH 01/29] Add translation matrix and config-driven sphinxopts to docs builds Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/a72a4fa7-534f-4313-8fff-989a4f4eec5e Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 461 ++++++++++++++++++++++++++++++-- .github/workflows/schedule.yaml | 51 +++- 2 files changed, 481 insertions(+), 31 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 03c74e4..15fbadf 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -18,6 +18,14 @@ on: description: 'Python version used to generate docs' required: false default: '3' + language: + description: 'Language to build' + required: false + default: 'en' + sphinxopts_json: + description: 'JSON array of language-specific sphinxopts' + required: false + default: '[]' workflow_call: inputs: reference: @@ -36,6 +44,14 @@ on: description: 'Python version used to generate docs' default: '3' type: string + language: + description: 'Language to build' + default: 'en' + type: string + sphinxopts_json: + description: 'JSON array of language-specific sphinxopts' + default: '[]' + type: string permissions: contents: read pages: write @@ -56,8 +72,8 @@ jobs: ref: ${{ inputs.reference }} - id: get-version run: | - echo "dist_version=$(python tools/extensions/patchlevel.py --short)" >> $GITHUB_OUTPUT - echo "major_minor=$(python -c 'from tools.extensions.patchlevel import get_version_info; print(get_version_info()[0])')" >> $GITHUB_OUTPUT + echo "dist_version=$(python tools/extensions/patchlevel.py --short)" >> "$GITHUB_OUTPUT" + echo "major_minor=$(python -c 'from tools.extensions.patchlevel import get_version_info; print(get_version_info()[0])')" >> "$GITHUB_OUTPUT" working-directory: ./Doc build-html: needs: prepare @@ -70,21 +86,104 @@ jobs: with: repository: ${{ inputs.repository }} ref: ${{ inputs.reference }} + - name: Sync translation sources + if: ${{ inputs.language != 'en' }} + env: + LANGUAGE_TAG: ${{ inputs.language }} + TARGET_VERSION: ${{ needs.prepare.outputs.major_minor }} + run: | + python - <<'PY' + import bisect + import os + import pathlib + import re + import subprocess + + language = os.environ["LANGUAGE_TAG"] + target_version = os.environ["TARGET_VERSION"] + repo_tag = language.replace("_", "-").lower() + repo_url = f"https://github.com/python/python-docs-{repo_tag}.git" + output_file = os.environ["GITHUB_OUTPUT"] + + remote_branches = subprocess.check_output( + ["git", "ls-remote", "--heads", repo_url], text=True + ) + branches = sorted( + { + match.group(1) + for match in re.finditer(r"refs/heads/([0-9]+\.[0-9]+)$", remote_branches, re.M) + }, + key=lambda value: tuple(map(int, value.split("."))), + ) + if not branches: + raise SystemExit(f"No numeric translation branches found for {repo_url}") + + branch_tuples = [tuple(map(int, value.split("."))) for value in branches] + target_tuple = tuple(map(int, target_version.split("."))) + index = bisect.bisect_left(branch_tuples, target_tuple) + chosen = branches[index] if index < len(branches) else branches[-1] + + destination = pathlib.Path("Doc") / "locale" / language / "LC_MESSAGES" + destination.parent.mkdir(parents=True, exist_ok=True) + subprocess.check_call( + ["git", "clone", "--branch", chosen, "--single-branch", repo_url, str(destination)] + ) + with open(output_file, "a", encoding="utf-8") as file: + file.write(f"translation_branch={chosen}\n") + PY + - id: build-settings + env: + LANGUAGE_TAG: ${{ inputs.language }} + SPHINXOPTS_JSON: ${{ inputs.sphinxopts_json }} + run: | + python - <<'PY' + import json + import os + import shlex + + language = os.environ["LANGUAGE_TAG"] + config_sphinxopts = json.loads(os.environ["SPHINXOPTS_JSON"]) + common = [] + if language != "en": + common = [ + "-D locale_dirs=../locale", + f"-D language={language}", + "-D gettext_compact=0", + "-D translation_progress_classes=1", + ] + artifact_suffix = "" if language == "en" else f"-{language}" + pdf = [*common, *config_sphinxopts] + + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as file: + file.write(f"artifact_suffix={artifact_suffix}\n") + file.write(f"common_sphinxopts={shlex.join(common)}\n") + file.write(f"pdf_sphinxopts={shlex.join(pdf)}\n") + PY - run: make venv working-directory: ./Doc - - run: make dist-html + - run: make dist-html SPHINXOPTS="${{ steps.build-settings.outputs.common_sphinxopts }}" working-directory: ./Doc + - name: Rename distribution files for translations + if: ${{ inputs.language != 'en' }} + run: | + version='${{ needs.prepare.outputs.dist_version }}' + language='${{ inputs.language }}' + for archive in ./Doc/dist/python-"$version"*; do + [ -f "$archive" ] || continue + renamed="${archive/python-$version/python-$version-$language}" + mv "$archive" "$renamed" + done - uses: actions/upload-artifact@master if: always() with: - name: python-${{ needs.prepare.outputs.dist_version }}-docs-html.zip - path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}*-docs-html.zip + name: python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}-docs-html.zip + path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}*-docs-html.zip if-no-files-found: ignore - uses: actions/upload-artifact@master if: always() with: - name: python-${{ needs.prepare.outputs.dist_version }}-docs-html.tar.bz2 - path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}*-docs-html.tar.bz2 + name: python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}-docs-html.tar.bz2 + path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}*-docs-html.tar.bz2 if-no-files-found: ignore build-text: needs: prepare @@ -97,21 +196,98 @@ jobs: with: repository: ${{ inputs.repository }} ref: ${{ inputs.reference }} + - name: Sync translation sources + if: ${{ inputs.language != 'en' }} + env: + LANGUAGE_TAG: ${{ inputs.language }} + TARGET_VERSION: ${{ needs.prepare.outputs.major_minor }} + run: | + python - <<'PY' + import bisect + import os + import pathlib + import re + import subprocess + + language = os.environ["LANGUAGE_TAG"] + target_version = os.environ["TARGET_VERSION"] + repo_tag = language.replace("_", "-").lower() + repo_url = f"https://github.com/python/python-docs-{repo_tag}.git" + remote_branches = subprocess.check_output( + ["git", "ls-remote", "--heads", repo_url], text=True + ) + branches = sorted( + { + match.group(1) + for match in re.finditer(r"refs/heads/([0-9]+\.[0-9]+)$", remote_branches, re.M) + }, + key=lambda value: tuple(map(int, value.split("."))), + ) + if not branches: + raise SystemExit(f"No numeric translation branches found for {repo_url}") + branch_tuples = [tuple(map(int, value.split("."))) for value in branches] + target_tuple = tuple(map(int, target_version.split("."))) + index = bisect.bisect_left(branch_tuples, target_tuple) + chosen = branches[index] if index < len(branches) else branches[-1] + destination = pathlib.Path("Doc") / "locale" / language / "LC_MESSAGES" + destination.parent.mkdir(parents=True, exist_ok=True) + subprocess.check_call( + ["git", "clone", "--branch", chosen, "--single-branch", repo_url, str(destination)] + ) + PY + - id: build-settings + env: + LANGUAGE_TAG: ${{ inputs.language }} + SPHINXOPTS_JSON: ${{ inputs.sphinxopts_json }} + run: | + python - <<'PY' + import json + import os + import shlex + + language = os.environ["LANGUAGE_TAG"] + config_sphinxopts = json.loads(os.environ["SPHINXOPTS_JSON"]) + common = [] + if language != "en": + common = [ + "-D locale_dirs=../locale", + f"-D language={language}", + "-D gettext_compact=0", + "-D translation_progress_classes=1", + ] + artifact_suffix = "" if language == "en" else f"-{language}" + pdf = [*common, *config_sphinxopts] + + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as file: + file.write(f"artifact_suffix={artifact_suffix}\n") + file.write(f"common_sphinxopts={shlex.join(common)}\n") + file.write(f"pdf_sphinxopts={shlex.join(pdf)}\n") + PY - run: make venv working-directory: ./Doc - - run: make dist-text + - run: make dist-text SPHINXOPTS="${{ steps.build-settings.outputs.common_sphinxopts }}" working-directory: ./Doc + - name: Rename distribution files for translations + if: ${{ inputs.language != 'en' }} + run: | + version='${{ needs.prepare.outputs.dist_version }}' + language='${{ inputs.language }}' + for archive in ./Doc/dist/python-"$version"*; do + [ -f "$archive" ] || continue + renamed="${archive/python-$version/python-$version-$language}" + mv "$archive" "$renamed" + done - uses: actions/upload-artifact@master if: always() with: - name: python-${{ needs.prepare.outputs.dist_version }}-docs-text.zip - path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}*-docs-text.zip + name: python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}-docs-text.zip + path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}*-docs-text.zip if-no-files-found: ignore - uses: actions/upload-artifact@master if: always() with: - name: python-${{ needs.prepare.outputs.dist_version }}-docs-text.tar.bz2 - path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}*-docs-text.tar.bz2 + name: python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}-docs-text.tar.bz2 + path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}*-docs-text.tar.bz2 if-no-files-found: ignore build-texinfo: needs: prepare @@ -124,22 +300,99 @@ jobs: with: repository: ${{ inputs.repository }} ref: ${{ inputs.reference }} + - name: Sync translation sources + if: ${{ inputs.language != 'en' }} + env: + LANGUAGE_TAG: ${{ inputs.language }} + TARGET_VERSION: ${{ needs.prepare.outputs.major_minor }} + run: | + python - <<'PY' + import bisect + import os + import pathlib + import re + import subprocess + + language = os.environ["LANGUAGE_TAG"] + target_version = os.environ["TARGET_VERSION"] + repo_tag = language.replace("_", "-").lower() + repo_url = f"https://github.com/python/python-docs-{repo_tag}.git" + remote_branches = subprocess.check_output( + ["git", "ls-remote", "--heads", repo_url], text=True + ) + branches = sorted( + { + match.group(1) + for match in re.finditer(r"refs/heads/([0-9]+\.[0-9]+)$", remote_branches, re.M) + }, + key=lambda value: tuple(map(int, value.split("."))), + ) + if not branches: + raise SystemExit(f"No numeric translation branches found for {repo_url}") + branch_tuples = [tuple(map(int, value.split("."))) for value in branches] + target_tuple = tuple(map(int, target_version.split("."))) + index = bisect.bisect_left(branch_tuples, target_tuple) + chosen = branches[index] if index < len(branches) else branches[-1] + destination = pathlib.Path("Doc") / "locale" / language / "LC_MESSAGES" + destination.parent.mkdir(parents=True, exist_ok=True) + subprocess.check_call( + ["git", "clone", "--branch", chosen, "--single-branch", repo_url, str(destination)] + ) + PY + - id: build-settings + env: + LANGUAGE_TAG: ${{ inputs.language }} + SPHINXOPTS_JSON: ${{ inputs.sphinxopts_json }} + run: | + python - <<'PY' + import json + import os + import shlex + + language = os.environ["LANGUAGE_TAG"] + config_sphinxopts = json.loads(os.environ["SPHINXOPTS_JSON"]) + common = [] + if language != "en": + common = [ + "-D locale_dirs=../locale", + f"-D language={language}", + "-D gettext_compact=0", + "-D translation_progress_classes=1", + ] + artifact_suffix = "" if language == "en" else f"-{language}" + pdf = [*common, *config_sphinxopts] + + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as file: + file.write(f"artifact_suffix={artifact_suffix}\n") + file.write(f"common_sphinxopts={shlex.join(common)}\n") + file.write(f"pdf_sphinxopts={shlex.join(pdf)}\n") + PY - run: make venv working-directory: ./Doc - run: sudo apt-get update && sudo apt-get install -y texinfo - - run: make dist-texinfo + - run: make dist-texinfo SPHINXOPTS="${{ steps.build-settings.outputs.common_sphinxopts }}" working-directory: ./Doc + - name: Rename distribution files for translations + if: ${{ inputs.language != 'en' }} + run: | + version='${{ needs.prepare.outputs.dist_version }}' + language='${{ inputs.language }}' + for archive in ./Doc/dist/python-"$version"*; do + [ -f "$archive" ] || continue + renamed="${archive/python-$version/python-$version-$language}" + mv "$archive" "$renamed" + done - uses: actions/upload-artifact@master if: always() with: - name: python-${{ needs.prepare.outputs.dist_version }}-docs-texinfo.zip - path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}*-docs-texinfo.zip + name: python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}-docs-texinfo.zip + path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}*-docs-texinfo.zip if-no-files-found: ignore - uses: actions/upload-artifact@master if: always() with: - name: python-${{ needs.prepare.outputs.dist_version }}-docs-texinfo.tar.bz2 - path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}*-docs-texinfo.tar.bz2 + name: python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}-docs-texinfo.tar.bz2 + path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}*-docs-texinfo.tar.bz2 if-no-files-found: ignore build-epub: needs: prepare @@ -152,15 +405,92 @@ jobs: with: repository: ${{ inputs.repository }} ref: ${{ inputs.reference }} + - name: Sync translation sources + if: ${{ inputs.language != 'en' }} + env: + LANGUAGE_TAG: ${{ inputs.language }} + TARGET_VERSION: ${{ needs.prepare.outputs.major_minor }} + run: | + python - <<'PY' + import bisect + import os + import pathlib + import re + import subprocess + + language = os.environ["LANGUAGE_TAG"] + target_version = os.environ["TARGET_VERSION"] + repo_tag = language.replace("_", "-").lower() + repo_url = f"https://github.com/python/python-docs-{repo_tag}.git" + remote_branches = subprocess.check_output( + ["git", "ls-remote", "--heads", repo_url], text=True + ) + branches = sorted( + { + match.group(1) + for match in re.finditer(r"refs/heads/([0-9]+\.[0-9]+)$", remote_branches, re.M) + }, + key=lambda value: tuple(map(int, value.split("."))), + ) + if not branches: + raise SystemExit(f"No numeric translation branches found for {repo_url}") + branch_tuples = [tuple(map(int, value.split("."))) for value in branches] + target_tuple = tuple(map(int, target_version.split("."))) + index = bisect.bisect_left(branch_tuples, target_tuple) + chosen = branches[index] if index < len(branches) else branches[-1] + destination = pathlib.Path("Doc") / "locale" / language / "LC_MESSAGES" + destination.parent.mkdir(parents=True, exist_ok=True) + subprocess.check_call( + ["git", "clone", "--branch", chosen, "--single-branch", repo_url, str(destination)] + ) + PY + - id: build-settings + env: + LANGUAGE_TAG: ${{ inputs.language }} + SPHINXOPTS_JSON: ${{ inputs.sphinxopts_json }} + run: | + python - <<'PY' + import json + import os + import shlex + + language = os.environ["LANGUAGE_TAG"] + config_sphinxopts = json.loads(os.environ["SPHINXOPTS_JSON"]) + common = [] + if language != "en": + common = [ + "-D locale_dirs=../locale", + f"-D language={language}", + "-D gettext_compact=0", + "-D translation_progress_classes=1", + ] + artifact_suffix = "" if language == "en" else f"-{language}" + pdf = [*common, *config_sphinxopts] + + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as file: + file.write(f"artifact_suffix={artifact_suffix}\n") + file.write(f"common_sphinxopts={shlex.join(common)}\n") + file.write(f"pdf_sphinxopts={shlex.join(pdf)}\n") + PY - run: make venv working-directory: ./Doc - - run: make dist-epub + - run: make dist-epub SPHINXOPTS="${{ steps.build-settings.outputs.common_sphinxopts }}" working-directory: ./Doc + - name: Rename distribution files for translations + if: ${{ inputs.language != 'en' }} + run: | + version='${{ needs.prepare.outputs.dist_version }}' + language='${{ inputs.language }}' + for archive in ./Doc/dist/python-"$version"*; do + [ -f "$archive" ] || continue + renamed="${archive/python-$version/python-$version-$language}" + mv "$archive" "$renamed" + done - uses: actions/upload-artifact@master if: always() with: - name: python-${{ needs.prepare.outputs.dist_version }}-docs.epub - path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}*-docs.epub + name: python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}-docs.epub + path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}*-docs.epub if-no-files-found: ignore build-pdf: needs: prepare @@ -173,32 +503,109 @@ jobs: with: repository: ${{ inputs.repository }} ref: ${{ inputs.reference }} + - name: Sync translation sources + if: ${{ inputs.language != 'en' }} + env: + LANGUAGE_TAG: ${{ inputs.language }} + TARGET_VERSION: ${{ needs.prepare.outputs.major_minor }} + run: | + python - <<'PY' + import bisect + import os + import pathlib + import re + import subprocess + + language = os.environ["LANGUAGE_TAG"] + target_version = os.environ["TARGET_VERSION"] + repo_tag = language.replace("_", "-").lower() + repo_url = f"https://github.com/python/python-docs-{repo_tag}.git" + remote_branches = subprocess.check_output( + ["git", "ls-remote", "--heads", repo_url], text=True + ) + branches = sorted( + { + match.group(1) + for match in re.finditer(r"refs/heads/([0-9]+\.[0-9]+)$", remote_branches, re.M) + }, + key=lambda value: tuple(map(int, value.split("."))), + ) + if not branches: + raise SystemExit(f"No numeric translation branches found for {repo_url}") + branch_tuples = [tuple(map(int, value.split("."))) for value in branches] + target_tuple = tuple(map(int, target_version.split("."))) + index = bisect.bisect_left(branch_tuples, target_tuple) + chosen = branches[index] if index < len(branches) else branches[-1] + destination = pathlib.Path("Doc") / "locale" / language / "LC_MESSAGES" + destination.parent.mkdir(parents=True, exist_ok=True) + subprocess.check_call( + ["git", "clone", "--branch", chosen, "--single-branch", repo_url, str(destination)] + ) + PY + - id: build-settings + env: + LANGUAGE_TAG: ${{ inputs.language }} + SPHINXOPTS_JSON: ${{ inputs.sphinxopts_json }} + run: | + python - <<'PY' + import json + import os + import shlex + + language = os.environ["LANGUAGE_TAG"] + config_sphinxopts = json.loads(os.environ["SPHINXOPTS_JSON"]) + common = [] + if language != "en": + common = [ + "-D locale_dirs=../locale", + f"-D language={language}", + "-D gettext_compact=0", + "-D translation_progress_classes=1", + ] + artifact_suffix = "" if language == "en" else f"-{language}" + pdf = [*common, *config_sphinxopts] + + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as file: + file.write(f"artifact_suffix={artifact_suffix}\n") + file.write(f"common_sphinxopts={shlex.join(common)}\n") + file.write(f"pdf_sphinxopts={shlex.join(pdf)}\n") + PY - run: make venv working-directory: ./Doc - run: sudo apt-get update && sudo apt-get install -y latexmk texlive-xetex fonts-freefont-otf xindy librsvg2-bin - - run: make dist-pdf + - run: make dist-pdf SPHINXOPTS="${{ steps.build-settings.outputs.pdf_sphinxopts }}" working-directory: ./Doc + - name: Rename distribution files for translations + if: ${{ inputs.language != 'en' }} + run: | + version='${{ needs.prepare.outputs.dist_version }}' + language='${{ inputs.language }}' + for archive in ./Doc/dist/python-"$version"*; do + [ -f "$archive" ] || continue + renamed="${archive/python-$version/python-$version-$language}" + mv "$archive" "$renamed" + done - uses: actions/upload-artifact@master if: always() with: - name: python-${{ needs.prepare.outputs.dist_version }}-pdf-logs.zip + name: python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}-pdf-logs.zip path: | ./Doc/build/latex/*.log ./Doc/build/latex/*.tex - uses: actions/upload-artifact@master if: always() with: - name: python-${{ needs.prepare.outputs.dist_version }}-docs-pdf-a4.zip - path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}*-docs-pdf-a4.zip + name: python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}-docs-pdf-a4.zip + path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}*-docs-pdf-a4.zip if-no-files-found: ignore - uses: actions/upload-artifact@master if: always() with: - name: python-${{ needs.prepare.outputs.dist_version }}-docs-pdf-a4.tar.bz2 - path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}*-docs-pdf-a4.tar.bz2 + name: python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}-docs-pdf-a4.tar.bz2 + path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}*-docs-pdf-a4.tar.bz2 if-no-files-found: ignore publish: - needs: [build-html, build-text, build-texinfo, build-epub, build-pdf] + needs: [prepare, build-html, build-text, build-texinfo, build-epub, build-pdf] if: ${{ !cancelled() && inputs.publish == 'true' }} runs-on: ubuntu-latest environment: diff --git a/.github/workflows/schedule.yaml b/.github/workflows/schedule.yaml index d3b89d1..3524e10 100644 --- a/.github/workflows/schedule.yaml +++ b/.github/workflows/schedule.yaml @@ -23,12 +23,53 @@ jobs: outputs: versions: ${{ steps.get-versions.outputs.versions }} steps: - - name: Get supported Python versions + - name: Get supported Python versions and translations id: get-versions run: | - versions=$(curl -sf https://peps.python.org/api/release-cycle.json | \ - jq -c '[to_entries[] | select(.value.status != "end-of-life" and .value.status != "planned") | {branch: (.value.branch // .key), python_version: (if .key == "3.10" then "3.12" else "3" end)}]') - echo "versions=$versions" >> "$GITHUB_OUTPUT" + python - <<'PY' >> "$GITHUB_OUTPUT" + import json + import tomllib + from urllib.request import urlopen + + with urlopen("https://peps.python.org/api/release-cycle.json", timeout=30) as response: + release_cycle = json.load(response) + + versions = [ + { + "branch": release.get("branch") or version, + "python_version": "3.12" if version == "3.10" else "3", + } + for version, release in release_cycle.items() + if release.get("status") not in {"end-of-life", "planned"} + ] + + with urlopen( + "https://raw.githubusercontent.com/python/docsbuild-scripts/main/config.toml", + timeout=30, + ) as response: + config = tomllib.loads(response.read().decode("utf-8")) + + defaults = config.get("defaults", {}) + default_in_prod = defaults.get("in_prod", True) + default_sphinxopts = defaults.get("sphinxopts", []) + + languages = [] + for language, language_config in config.get("languages", {}).items(): + if not language_config.get("in_prod", default_in_prod): + continue + languages.append( + { + "language": language, + "sphinxopts_json": json.dumps( + language_config.get("sphinxopts", default_sphinxopts), + ensure_ascii=False, + ), + } + ) + + matrix = [{**version, **language} for version in versions for language in languages] + print(f"versions={json.dumps(matrix, ensure_ascii=False)}") + PY build: needs: get-versions strategy: @@ -42,6 +83,8 @@ jobs: with: reference: ${{ matrix.branch }} python_version: ${{ matrix.python_version }} + language: ${{ matrix.language }} + sphinxopts_json: ${{ matrix.sphinxopts_json }} publish: ${{ 'false' }} deploy: From adf63057d1e7b47815583ee72ca5de1c6ae5f530 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 16:24:00 +0000 Subject: [PATCH 02/29] Use actions/checkout for language repo; resolve translation branch once in prepare Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/22650f24-fed8-48df-870d-6d2777a9e279 Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 253 +++++++++-------------------------- 1 file changed, 64 insertions(+), 189 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 15fbadf..6444e74 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -62,6 +62,8 @@ jobs: outputs: dist_version: ${{ steps.get-version.outputs.dist_version }} major_minor: ${{ steps.get-version.outputs.major_minor }} + translation_ref: ${{ steps.resolve-translation.outputs.translation_ref }} + translation_repository: ${{ steps.resolve-translation.outputs.translation_repository }} steps: - uses: actions/setup-python@master with: @@ -75,36 +77,24 @@ jobs: echo "dist_version=$(python tools/extensions/patchlevel.py --short)" >> "$GITHUB_OUTPUT" echo "major_minor=$(python -c 'from tools.extensions.patchlevel import get_version_info; print(get_version_info()[0])')" >> "$GITHUB_OUTPUT" working-directory: ./Doc - build-html: - needs: prepare - runs-on: ubuntu-latest - steps: - - uses: actions/setup-python@master - with: - python-version: ${{ inputs.python_version }} - - uses: actions/checkout@master - with: - repository: ${{ inputs.repository }} - ref: ${{ inputs.reference }} - - name: Sync translation sources + - name: Resolve translation branch + id: resolve-translation if: ${{ inputs.language != 'en' }} env: LANGUAGE_TAG: ${{ inputs.language }} - TARGET_VERSION: ${{ needs.prepare.outputs.major_minor }} + MAJOR_MINOR: ${{ steps.get-version.outputs.major_minor }} run: | python - <<'PY' import bisect import os - import pathlib import re import subprocess - + language = os.environ["LANGUAGE_TAG"] - target_version = os.environ["TARGET_VERSION"] + target_version = os.environ["MAJOR_MINOR"] repo_tag = language.replace("_", "-").lower() repo_url = f"https://github.com/python/python-docs-{repo_tag}.git" - output_file = os.environ["GITHUB_OUTPUT"] - + remote_branches = subprocess.check_output( ["git", "ls-remote", "--heads", repo_url], text=True ) @@ -117,20 +107,33 @@ jobs: ) if not branches: raise SystemExit(f"No numeric translation branches found for {repo_url}") - branch_tuples = [tuple(map(int, value.split("."))) for value in branches] target_tuple = tuple(map(int, target_version.split("."))) index = bisect.bisect_left(branch_tuples, target_tuple) chosen = branches[index] if index < len(branches) else branches[-1] - - destination = pathlib.Path("Doc") / "locale" / language / "LC_MESSAGES" - destination.parent.mkdir(parents=True, exist_ok=True) - subprocess.check_call( - ["git", "clone", "--branch", chosen, "--single-branch", repo_url, str(destination)] - ) - with open(output_file, "a", encoding="utf-8") as file: - file.write(f"translation_branch={chosen}\n") + + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as file: + file.write(f"translation_ref={chosen}\n") + file.write(f"translation_repository=python/python-docs-{repo_tag}\n") PY + build-html: + needs: prepare + runs-on: ubuntu-latest + steps: + - uses: actions/setup-python@master + with: + python-version: ${{ inputs.python_version }} + - uses: actions/checkout@master + with: + repository: ${{ inputs.repository }} + ref: ${{ inputs.reference }} + - name: Checkout translation sources + if: ${{ inputs.language != 'en' }} + uses: actions/checkout@master + with: + repository: ${{ needs.prepare.outputs.translation_repository }} + ref: ${{ needs.prepare.outputs.translation_ref }} + path: Doc/locale/${{ inputs.language }}/LC_MESSAGES - id: build-settings env: LANGUAGE_TAG: ${{ inputs.language }} @@ -140,7 +143,7 @@ jobs: import json import os import shlex - + language = os.environ["LANGUAGE_TAG"] config_sphinxopts = json.loads(os.environ["SPHINXOPTS_JSON"]) common = [] @@ -153,7 +156,7 @@ jobs: ] artifact_suffix = "" if language == "en" else f"-{language}" pdf = [*common, *config_sphinxopts] - + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as file: file.write(f"artifact_suffix={artifact_suffix}\n") file.write(f"common_sphinxopts={shlex.join(common)}\n") @@ -196,45 +199,13 @@ jobs: with: repository: ${{ inputs.repository }} ref: ${{ inputs.reference }} - - name: Sync translation sources + - name: Checkout translation sources if: ${{ inputs.language != 'en' }} - env: - LANGUAGE_TAG: ${{ inputs.language }} - TARGET_VERSION: ${{ needs.prepare.outputs.major_minor }} - run: | - python - <<'PY' - import bisect - import os - import pathlib - import re - import subprocess - - language = os.environ["LANGUAGE_TAG"] - target_version = os.environ["TARGET_VERSION"] - repo_tag = language.replace("_", "-").lower() - repo_url = f"https://github.com/python/python-docs-{repo_tag}.git" - remote_branches = subprocess.check_output( - ["git", "ls-remote", "--heads", repo_url], text=True - ) - branches = sorted( - { - match.group(1) - for match in re.finditer(r"refs/heads/([0-9]+\.[0-9]+)$", remote_branches, re.M) - }, - key=lambda value: tuple(map(int, value.split("."))), - ) - if not branches: - raise SystemExit(f"No numeric translation branches found for {repo_url}") - branch_tuples = [tuple(map(int, value.split("."))) for value in branches] - target_tuple = tuple(map(int, target_version.split("."))) - index = bisect.bisect_left(branch_tuples, target_tuple) - chosen = branches[index] if index < len(branches) else branches[-1] - destination = pathlib.Path("Doc") / "locale" / language / "LC_MESSAGES" - destination.parent.mkdir(parents=True, exist_ok=True) - subprocess.check_call( - ["git", "clone", "--branch", chosen, "--single-branch", repo_url, str(destination)] - ) - PY + uses: actions/checkout@master + with: + repository: ${{ needs.prepare.outputs.translation_repository }} + ref: ${{ needs.prepare.outputs.translation_ref }} + path: Doc/locale/${{ inputs.language }}/LC_MESSAGES - id: build-settings env: LANGUAGE_TAG: ${{ inputs.language }} @@ -244,7 +215,7 @@ jobs: import json import os import shlex - + language = os.environ["LANGUAGE_TAG"] config_sphinxopts = json.loads(os.environ["SPHINXOPTS_JSON"]) common = [] @@ -257,7 +228,7 @@ jobs: ] artifact_suffix = "" if language == "en" else f"-{language}" pdf = [*common, *config_sphinxopts] - + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as file: file.write(f"artifact_suffix={artifact_suffix}\n") file.write(f"common_sphinxopts={shlex.join(common)}\n") @@ -300,45 +271,13 @@ jobs: with: repository: ${{ inputs.repository }} ref: ${{ inputs.reference }} - - name: Sync translation sources + - name: Checkout translation sources if: ${{ inputs.language != 'en' }} - env: - LANGUAGE_TAG: ${{ inputs.language }} - TARGET_VERSION: ${{ needs.prepare.outputs.major_minor }} - run: | - python - <<'PY' - import bisect - import os - import pathlib - import re - import subprocess - - language = os.environ["LANGUAGE_TAG"] - target_version = os.environ["TARGET_VERSION"] - repo_tag = language.replace("_", "-").lower() - repo_url = f"https://github.com/python/python-docs-{repo_tag}.git" - remote_branches = subprocess.check_output( - ["git", "ls-remote", "--heads", repo_url], text=True - ) - branches = sorted( - { - match.group(1) - for match in re.finditer(r"refs/heads/([0-9]+\.[0-9]+)$", remote_branches, re.M) - }, - key=lambda value: tuple(map(int, value.split("."))), - ) - if not branches: - raise SystemExit(f"No numeric translation branches found for {repo_url}") - branch_tuples = [tuple(map(int, value.split("."))) for value in branches] - target_tuple = tuple(map(int, target_version.split("."))) - index = bisect.bisect_left(branch_tuples, target_tuple) - chosen = branches[index] if index < len(branches) else branches[-1] - destination = pathlib.Path("Doc") / "locale" / language / "LC_MESSAGES" - destination.parent.mkdir(parents=True, exist_ok=True) - subprocess.check_call( - ["git", "clone", "--branch", chosen, "--single-branch", repo_url, str(destination)] - ) - PY + uses: actions/checkout@master + with: + repository: ${{ needs.prepare.outputs.translation_repository }} + ref: ${{ needs.prepare.outputs.translation_ref }} + path: Doc/locale/${{ inputs.language }}/LC_MESSAGES - id: build-settings env: LANGUAGE_TAG: ${{ inputs.language }} @@ -348,7 +287,7 @@ jobs: import json import os import shlex - + language = os.environ["LANGUAGE_TAG"] config_sphinxopts = json.loads(os.environ["SPHINXOPTS_JSON"]) common = [] @@ -361,7 +300,7 @@ jobs: ] artifact_suffix = "" if language == "en" else f"-{language}" pdf = [*common, *config_sphinxopts] - + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as file: file.write(f"artifact_suffix={artifact_suffix}\n") file.write(f"common_sphinxopts={shlex.join(common)}\n") @@ -405,45 +344,13 @@ jobs: with: repository: ${{ inputs.repository }} ref: ${{ inputs.reference }} - - name: Sync translation sources + - name: Checkout translation sources if: ${{ inputs.language != 'en' }} - env: - LANGUAGE_TAG: ${{ inputs.language }} - TARGET_VERSION: ${{ needs.prepare.outputs.major_minor }} - run: | - python - <<'PY' - import bisect - import os - import pathlib - import re - import subprocess - - language = os.environ["LANGUAGE_TAG"] - target_version = os.environ["TARGET_VERSION"] - repo_tag = language.replace("_", "-").lower() - repo_url = f"https://github.com/python/python-docs-{repo_tag}.git" - remote_branches = subprocess.check_output( - ["git", "ls-remote", "--heads", repo_url], text=True - ) - branches = sorted( - { - match.group(1) - for match in re.finditer(r"refs/heads/([0-9]+\.[0-9]+)$", remote_branches, re.M) - }, - key=lambda value: tuple(map(int, value.split("."))), - ) - if not branches: - raise SystemExit(f"No numeric translation branches found for {repo_url}") - branch_tuples = [tuple(map(int, value.split("."))) for value in branches] - target_tuple = tuple(map(int, target_version.split("."))) - index = bisect.bisect_left(branch_tuples, target_tuple) - chosen = branches[index] if index < len(branches) else branches[-1] - destination = pathlib.Path("Doc") / "locale" / language / "LC_MESSAGES" - destination.parent.mkdir(parents=True, exist_ok=True) - subprocess.check_call( - ["git", "clone", "--branch", chosen, "--single-branch", repo_url, str(destination)] - ) - PY + uses: actions/checkout@master + with: + repository: ${{ needs.prepare.outputs.translation_repository }} + ref: ${{ needs.prepare.outputs.translation_ref }} + path: Doc/locale/${{ inputs.language }}/LC_MESSAGES - id: build-settings env: LANGUAGE_TAG: ${{ inputs.language }} @@ -453,7 +360,7 @@ jobs: import json import os import shlex - + language = os.environ["LANGUAGE_TAG"] config_sphinxopts = json.loads(os.environ["SPHINXOPTS_JSON"]) common = [] @@ -466,7 +373,7 @@ jobs: ] artifact_suffix = "" if language == "en" else f"-{language}" pdf = [*common, *config_sphinxopts] - + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as file: file.write(f"artifact_suffix={artifact_suffix}\n") file.write(f"common_sphinxopts={shlex.join(common)}\n") @@ -503,45 +410,13 @@ jobs: with: repository: ${{ inputs.repository }} ref: ${{ inputs.reference }} - - name: Sync translation sources + - name: Checkout translation sources if: ${{ inputs.language != 'en' }} - env: - LANGUAGE_TAG: ${{ inputs.language }} - TARGET_VERSION: ${{ needs.prepare.outputs.major_minor }} - run: | - python - <<'PY' - import bisect - import os - import pathlib - import re - import subprocess - - language = os.environ["LANGUAGE_TAG"] - target_version = os.environ["TARGET_VERSION"] - repo_tag = language.replace("_", "-").lower() - repo_url = f"https://github.com/python/python-docs-{repo_tag}.git" - remote_branches = subprocess.check_output( - ["git", "ls-remote", "--heads", repo_url], text=True - ) - branches = sorted( - { - match.group(1) - for match in re.finditer(r"refs/heads/([0-9]+\.[0-9]+)$", remote_branches, re.M) - }, - key=lambda value: tuple(map(int, value.split("."))), - ) - if not branches: - raise SystemExit(f"No numeric translation branches found for {repo_url}") - branch_tuples = [tuple(map(int, value.split("."))) for value in branches] - target_tuple = tuple(map(int, target_version.split("."))) - index = bisect.bisect_left(branch_tuples, target_tuple) - chosen = branches[index] if index < len(branches) else branches[-1] - destination = pathlib.Path("Doc") / "locale" / language / "LC_MESSAGES" - destination.parent.mkdir(parents=True, exist_ok=True) - subprocess.check_call( - ["git", "clone", "--branch", chosen, "--single-branch", repo_url, str(destination)] - ) - PY + uses: actions/checkout@master + with: + repository: ${{ needs.prepare.outputs.translation_repository }} + ref: ${{ needs.prepare.outputs.translation_ref }} + path: Doc/locale/${{ inputs.language }}/LC_MESSAGES - id: build-settings env: LANGUAGE_TAG: ${{ inputs.language }} @@ -551,7 +426,7 @@ jobs: import json import os import shlex - + language = os.environ["LANGUAGE_TAG"] config_sphinxopts = json.loads(os.environ["SPHINXOPTS_JSON"]) common = [] @@ -564,7 +439,7 @@ jobs: ] artifact_suffix = "" if language == "en" else f"-{language}" pdf = [*common, *config_sphinxopts] - + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as file: file.write(f"artifact_suffix={artifact_suffix}\n") file.write(f"common_sphinxopts={shlex.join(common)}\n") From dc1f59f14cdbee8ce9a8402f41d524470312e8bd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 16:38:59 +0000 Subject: [PATCH 03/29] Deploy to // layout; filenames without language tag Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/3ab50848-fa91-4a70-a81c-8eac790c6904 Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 137 +++++++++++------------------------ 1 file changed, 44 insertions(+), 93 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 6444e74..1de1da4 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -166,27 +166,17 @@ jobs: working-directory: ./Doc - run: make dist-html SPHINXOPTS="${{ steps.build-settings.outputs.common_sphinxopts }}" working-directory: ./Doc - - name: Rename distribution files for translations - if: ${{ inputs.language != 'en' }} - run: | - version='${{ needs.prepare.outputs.dist_version }}' - language='${{ inputs.language }}' - for archive in ./Doc/dist/python-"$version"*; do - [ -f "$archive" ] || continue - renamed="${archive/python-$version/python-$version-$language}" - mv "$archive" "$renamed" - done - uses: actions/upload-artifact@master if: always() with: name: python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}-docs-html.zip - path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}*-docs-html.zip + path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}*-docs-html.zip if-no-files-found: ignore - uses: actions/upload-artifact@master if: always() with: name: python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}-docs-html.tar.bz2 - path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}*-docs-html.tar.bz2 + path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}*-docs-html.tar.bz2 if-no-files-found: ignore build-text: needs: prepare @@ -238,27 +228,17 @@ jobs: working-directory: ./Doc - run: make dist-text SPHINXOPTS="${{ steps.build-settings.outputs.common_sphinxopts }}" working-directory: ./Doc - - name: Rename distribution files for translations - if: ${{ inputs.language != 'en' }} - run: | - version='${{ needs.prepare.outputs.dist_version }}' - language='${{ inputs.language }}' - for archive in ./Doc/dist/python-"$version"*; do - [ -f "$archive" ] || continue - renamed="${archive/python-$version/python-$version-$language}" - mv "$archive" "$renamed" - done - uses: actions/upload-artifact@master if: always() with: name: python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}-docs-text.zip - path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}*-docs-text.zip + path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}*-docs-text.zip if-no-files-found: ignore - uses: actions/upload-artifact@master if: always() with: name: python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}-docs-text.tar.bz2 - path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}*-docs-text.tar.bz2 + path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}*-docs-text.tar.bz2 if-no-files-found: ignore build-texinfo: needs: prepare @@ -311,27 +291,17 @@ jobs: - run: sudo apt-get update && sudo apt-get install -y texinfo - run: make dist-texinfo SPHINXOPTS="${{ steps.build-settings.outputs.common_sphinxopts }}" working-directory: ./Doc - - name: Rename distribution files for translations - if: ${{ inputs.language != 'en' }} - run: | - version='${{ needs.prepare.outputs.dist_version }}' - language='${{ inputs.language }}' - for archive in ./Doc/dist/python-"$version"*; do - [ -f "$archive" ] || continue - renamed="${archive/python-$version/python-$version-$language}" - mv "$archive" "$renamed" - done - uses: actions/upload-artifact@master if: always() with: name: python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}-docs-texinfo.zip - path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}*-docs-texinfo.zip + path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}*-docs-texinfo.zip if-no-files-found: ignore - uses: actions/upload-artifact@master if: always() with: name: python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}-docs-texinfo.tar.bz2 - path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}*-docs-texinfo.tar.bz2 + path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}*-docs-texinfo.tar.bz2 if-no-files-found: ignore build-epub: needs: prepare @@ -383,21 +353,11 @@ jobs: working-directory: ./Doc - run: make dist-epub SPHINXOPTS="${{ steps.build-settings.outputs.common_sphinxopts }}" working-directory: ./Doc - - name: Rename distribution files for translations - if: ${{ inputs.language != 'en' }} - run: | - version='${{ needs.prepare.outputs.dist_version }}' - language='${{ inputs.language }}' - for archive in ./Doc/dist/python-"$version"*; do - [ -f "$archive" ] || continue - renamed="${archive/python-$version/python-$version-$language}" - mv "$archive" "$renamed" - done - uses: actions/upload-artifact@master if: always() with: name: python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}-docs.epub - path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}*-docs.epub + path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}*-docs.epub if-no-files-found: ignore build-pdf: needs: prepare @@ -450,16 +410,6 @@ jobs: - run: sudo apt-get update && sudo apt-get install -y latexmk texlive-xetex fonts-freefont-otf xindy librsvg2-bin - run: make dist-pdf SPHINXOPTS="${{ steps.build-settings.outputs.pdf_sphinxopts }}" working-directory: ./Doc - - name: Rename distribution files for translations - if: ${{ inputs.language != 'en' }} - run: | - version='${{ needs.prepare.outputs.dist_version }}' - language='${{ inputs.language }}' - for archive in ./Doc/dist/python-"$version"*; do - [ -f "$archive" ] || continue - renamed="${archive/python-$version/python-$version-$language}" - mv "$archive" "$renamed" - done - uses: actions/upload-artifact@master if: always() with: @@ -471,13 +421,13 @@ jobs: if: always() with: name: python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}-docs-pdf-a4.zip - path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}*-docs-pdf-a4.zip + path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}*-docs-pdf-a4.zip if-no-files-found: ignore - uses: actions/upload-artifact@master if: always() with: name: python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}-docs-pdf-a4.tar.bz2 - path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}${{ steps.build-settings.outputs.artifact_suffix }}*-docs-pdf-a4.tar.bz2 + path: ./Doc/dist/python-${{ needs.prepare.outputs.dist_version }}*-docs-pdf-a4.tar.bz2 if-no-files-found: ignore publish: needs: [prepare, build-html, build-text, build-texinfo, build-epub, build-pdf] @@ -500,7 +450,7 @@ jobs: - name: Prepare site directory run: | - mkdir -p _site/${{ needs.prepare.outputs.major_minor }} + mkdir -p _site/${{ inputs.language }}/${{ needs.prepare.outputs.major_minor }} # Remove git metadata; safe even if checkout above did not succeed rm -rf _site/.git @@ -512,11 +462,11 @@ jobs: - name: Copy new archives into site directory run: | - # Copy generated archives (zip, tar.bz2, epub) into _site//, + # Copy generated archives (zip, tar.bz2, epub) into _site///, # excluding PDF build logs which are for debugging only. find artifacts/ -type f \( -name "*.zip" -o -name "*.tar.bz2" -o -name "*.epub" \) \ ! -name "python-*-pdf-logs.zip" \ - -exec cp {} _site/${{ needs.prepare.outputs.major_minor }}/ \; + -exec cp {} _site/${{ inputs.language }}/${{ needs.prepare.outputs.major_minor }}/ \; - name: Generate per-version directory listing run: | @@ -529,39 +479,40 @@ jobs: root = Path("_site") version_pattern = re.compile(r"^\d+\.\d+$") - for version_dir in sorted( - p for p in root.iterdir() if p.is_dir() and version_pattern.match(p.name) - ): - files = sorted( - p for p in version_dir.iterdir() if p.is_file() and p.name != "index.html" - ) - rows = [] - for file_path in files: - stat = file_path.stat() - timestamp = datetime.fromtimestamp(stat.st_mtime, timezone.utc).strftime( - "%Y-%m-%d %H:%M:%S UTC" - ) - rows.append( - f'{escape(file_path.name)}{timestamp}{stat.st_size}' + for lang_dir in sorted(p for p in root.iterdir() if p.is_dir()): + for version_dir in sorted( + p for p in lang_dir.iterdir() if p.is_dir() and version_pattern.match(p.name) + ): + files = sorted( + p for p in version_dir.iterdir() if p.is_file() and p.name != "index.html" ) + rows = [] + for file_path in files: + stat = file_path.stat() + timestamp = datetime.fromtimestamp(stat.st_mtime, timezone.utc).strftime( + "%Y-%m-%d %H:%M:%S UTC" + ) + rows.append( + f'{escape(file_path.name)}{timestamp}{stat.st_size}' + ) - relative_path = f"/{version_dir.relative_to(root).as_posix()}/" - html = [ - "", - '', - 'Directory listing', - "", - f"

Path: {escape(relative_path)}

", - "", - '', - "", - *rows, - "", - "
FilenameTimestamp (UTC)Size (bytes)
", - "", - "", - ] - (version_dir / "index.html").write_text("\n".join(html) + "\n", encoding="utf-8") + relative_path = f"/{version_dir.relative_to(root).as_posix()}/" + html = [ + "", + '', + 'Directory listing', + "", + f"

Path: {escape(relative_path)}

", + "", + '', + "", + *rows, + "", + "
FilenameTimestamp (UTC)Size (bytes)
", + "", + "", + ] + (version_dir / "index.html").write_text("\n".join(html) + "\n", encoding="utf-8") PY - name: Upload Pages artifact From 92e7e0013aaa2c87dc9c4ea6e4c03aa23e4c5097 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:20:53 +0000 Subject: [PATCH 04/29] fix: split -D sphinx opts into separate list items to avoid leading-space parse error Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/26f2978c-3a6a-46a2-a47d-9748069226ea Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 40 ++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1de1da4..1f28ede 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -149,10 +149,10 @@ jobs: common = [] if language != "en": common = [ - "-D locale_dirs=../locale", - f"-D language={language}", - "-D gettext_compact=0", - "-D translation_progress_classes=1", + "-D", "locale_dirs=../locale", + "-D", f"language={language}", + "-D", "gettext_compact=0", + "-D", "translation_progress_classes=1", ] artifact_suffix = "" if language == "en" else f"-{language}" pdf = [*common, *config_sphinxopts] @@ -211,10 +211,10 @@ jobs: common = [] if language != "en": common = [ - "-D locale_dirs=../locale", - f"-D language={language}", - "-D gettext_compact=0", - "-D translation_progress_classes=1", + "-D", "locale_dirs=../locale", + "-D", f"language={language}", + "-D", "gettext_compact=0", + "-D", "translation_progress_classes=1", ] artifact_suffix = "" if language == "en" else f"-{language}" pdf = [*common, *config_sphinxopts] @@ -273,10 +273,10 @@ jobs: common = [] if language != "en": common = [ - "-D locale_dirs=../locale", - f"-D language={language}", - "-D gettext_compact=0", - "-D translation_progress_classes=1", + "-D", "locale_dirs=../locale", + "-D", f"language={language}", + "-D", "gettext_compact=0", + "-D", "translation_progress_classes=1", ] artifact_suffix = "" if language == "en" else f"-{language}" pdf = [*common, *config_sphinxopts] @@ -336,10 +336,10 @@ jobs: common = [] if language != "en": common = [ - "-D locale_dirs=../locale", - f"-D language={language}", - "-D gettext_compact=0", - "-D translation_progress_classes=1", + "-D", "locale_dirs=../locale", + "-D", f"language={language}", + "-D", "gettext_compact=0", + "-D", "translation_progress_classes=1", ] artifact_suffix = "" if language == "en" else f"-{language}" pdf = [*common, *config_sphinxopts] @@ -392,10 +392,10 @@ jobs: common = [] if language != "en": common = [ - "-D locale_dirs=../locale", - f"-D language={language}", - "-D gettext_compact=0", - "-D translation_progress_classes=1", + "-D", "locale_dirs=../locale", + "-D", f"language={language}", + "-D", "gettext_compact=0", + "-D", "translation_progress_classes=1", ] artifact_suffix = "" if language == "en" else f"-{language}" pdf = [*common, *config_sphinxopts] From d164d2507e0808c10d47315485d68a5c5873ae4f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Apr 2026 14:30:58 +0000 Subject: [PATCH 05/29] fix: shlex.split config_sphinxopts entries to avoid leading-space in latex_engine override Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/1fd2e033-293a-45c6-93d8-06b89a3f10a8 Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1f28ede..3ff49ba 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -145,7 +145,7 @@ jobs: import shlex language = os.environ["LANGUAGE_TAG"] - config_sphinxopts = json.loads(os.environ["SPHINXOPTS_JSON"]) + config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt)] common = [] if language != "en": common = [ @@ -207,7 +207,7 @@ jobs: import shlex language = os.environ["LANGUAGE_TAG"] - config_sphinxopts = json.loads(os.environ["SPHINXOPTS_JSON"]) + config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt)] common = [] if language != "en": common = [ @@ -269,7 +269,7 @@ jobs: import shlex language = os.environ["LANGUAGE_TAG"] - config_sphinxopts = json.loads(os.environ["SPHINXOPTS_JSON"]) + config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt)] common = [] if language != "en": common = [ @@ -332,7 +332,7 @@ jobs: import shlex language = os.environ["LANGUAGE_TAG"] - config_sphinxopts = json.loads(os.environ["SPHINXOPTS_JSON"]) + config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt)] common = [] if language != "en": common = [ @@ -388,7 +388,7 @@ jobs: import shlex language = os.environ["LANGUAGE_TAG"] - config_sphinxopts = json.loads(os.environ["SPHINXOPTS_JSON"]) + config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt)] common = [] if language != "en": common = [ From 51580075e713862f242dc625f9c4114478737bf9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Apr 2026 15:34:24 +0000 Subject: [PATCH 06/29] fix: correct locale_dirs from ../locale to locale so Sphinx finds translation files Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/83105c4e-e0e6-47ff-8974-744ccb96b270 Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 3ff49ba..21c77e7 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -149,7 +149,7 @@ jobs: common = [] if language != "en": common = [ - "-D", "locale_dirs=../locale", + "-D", "locale_dirs=locale", "-D", f"language={language}", "-D", "gettext_compact=0", "-D", "translation_progress_classes=1", @@ -211,7 +211,7 @@ jobs: common = [] if language != "en": common = [ - "-D", "locale_dirs=../locale", + "-D", "locale_dirs=locale", "-D", f"language={language}", "-D", "gettext_compact=0", "-D", "translation_progress_classes=1", @@ -273,7 +273,7 @@ jobs: common = [] if language != "en": common = [ - "-D", "locale_dirs=../locale", + "-D", "locale_dirs=locale", "-D", f"language={language}", "-D", "gettext_compact=0", "-D", "translation_progress_classes=1", @@ -336,7 +336,7 @@ jobs: common = [] if language != "en": common = [ - "-D", "locale_dirs=../locale", + "-D", "locale_dirs=locale", "-D", f"language={language}", "-D", "gettext_compact=0", "-D", "translation_progress_classes=1", @@ -392,7 +392,7 @@ jobs: common = [] if language != "en": common = [ - "-D", "locale_dirs=../locale", + "-D", "locale_dirs=locale", "-D", f"language={language}", "-D", "gettext_compact=0", "-D", "translation_progress_classes=1", From 66c2ba4a2ed2450ac82730814916dc48ec55b89e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Apr 2026 17:55:35 +0000 Subject: [PATCH 07/29] fix: exclude locale RST files and gate translation_progress_classes on Sphinx version Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/207833af-6022-4aed-bf78-59b3741731d5 Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 55 ++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 21c77e7..948b2f9 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -148,12 +148,21 @@ jobs: config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt)] common = [] if language != "en": + try: + import re + with open("Doc/requirements.txt") as f: + m = re.search(r"(?i)^sphinx[=<>!~\s]*(\d+)\.(\d+)", f.read(), re.MULTILINE) + sphinx_ver = (int(m.group(1)), int(m.group(2))) if m else (99, 0) + except FileNotFoundError: + sphinx_ver = (99, 0) common = [ "-D", "locale_dirs=locale", "-D", f"language={language}", "-D", "gettext_compact=0", - "-D", "translation_progress_classes=1", + "-D", "exclude_patterns=locale/**", ] + if sphinx_ver >= (7, 1): + common += ["-D", "translation_progress_classes=1"] artifact_suffix = "" if language == "en" else f"-{language}" pdf = [*common, *config_sphinxopts] @@ -210,12 +219,21 @@ jobs: config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt)] common = [] if language != "en": + try: + import re + with open("Doc/requirements.txt") as f: + m = re.search(r"(?i)^sphinx[=<>!~\s]*(\d+)\.(\d+)", f.read(), re.MULTILINE) + sphinx_ver = (int(m.group(1)), int(m.group(2))) if m else (99, 0) + except FileNotFoundError: + sphinx_ver = (99, 0) common = [ "-D", "locale_dirs=locale", "-D", f"language={language}", "-D", "gettext_compact=0", - "-D", "translation_progress_classes=1", + "-D", "exclude_patterns=locale/**", ] + if sphinx_ver >= (7, 1): + common += ["-D", "translation_progress_classes=1"] artifact_suffix = "" if language == "en" else f"-{language}" pdf = [*common, *config_sphinxopts] @@ -272,12 +290,21 @@ jobs: config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt)] common = [] if language != "en": + try: + import re + with open("Doc/requirements.txt") as f: + m = re.search(r"(?i)^sphinx[=<>!~\s]*(\d+)\.(\d+)", f.read(), re.MULTILINE) + sphinx_ver = (int(m.group(1)), int(m.group(2))) if m else (99, 0) + except FileNotFoundError: + sphinx_ver = (99, 0) common = [ "-D", "locale_dirs=locale", "-D", f"language={language}", "-D", "gettext_compact=0", - "-D", "translation_progress_classes=1", + "-D", "exclude_patterns=locale/**", ] + if sphinx_ver >= (7, 1): + common += ["-D", "translation_progress_classes=1"] artifact_suffix = "" if language == "en" else f"-{language}" pdf = [*common, *config_sphinxopts] @@ -335,12 +362,21 @@ jobs: config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt)] common = [] if language != "en": + try: + import re + with open("Doc/requirements.txt") as f: + m = re.search(r"(?i)^sphinx[=<>!~\s]*(\d+)\.(\d+)", f.read(), re.MULTILINE) + sphinx_ver = (int(m.group(1)), int(m.group(2))) if m else (99, 0) + except FileNotFoundError: + sphinx_ver = (99, 0) common = [ "-D", "locale_dirs=locale", "-D", f"language={language}", "-D", "gettext_compact=0", - "-D", "translation_progress_classes=1", + "-D", "exclude_patterns=locale/**", ] + if sphinx_ver >= (7, 1): + common += ["-D", "translation_progress_classes=1"] artifact_suffix = "" if language == "en" else f"-{language}" pdf = [*common, *config_sphinxopts] @@ -391,12 +427,21 @@ jobs: config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt)] common = [] if language != "en": + try: + import re + with open("Doc/requirements.txt") as f: + m = re.search(r"(?i)^sphinx[=<>!~\s]*(\d+)\.(\d+)", f.read(), re.MULTILINE) + sphinx_ver = (int(m.group(1)), int(m.group(2))) if m else (99, 0) + except FileNotFoundError: + sphinx_ver = (99, 0) common = [ "-D", "locale_dirs=locale", "-D", f"language={language}", "-D", "gettext_compact=0", - "-D", "translation_progress_classes=1", + "-D", "exclude_patterns=locale/**", ] + if sphinx_ver >= (7, 1): + common += ["-D", "translation_progress_classes=1"] artifact_suffix = "" if language == "en" else f"-{language}" pdf = [*common, *config_sphinxopts] From 18325c7305399c2f36af3895b37c592f299075fa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Apr 2026 20:04:50 +0000 Subject: [PATCH 08/29] fix: include default exclude_patterns when overriding for locale Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/f0fb8aea-7cf2-4fdf-99b7-d83d3745c980 Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 948b2f9..d382625 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -159,7 +159,7 @@ jobs: "-D", "locale_dirs=locale", "-D", f"language={language}", "-D", "gettext_compact=0", - "-D", "exclude_patterns=locale/**", + "-D", "exclude_patterns=includes/*.rst,venv/*,README.rst,locale/**", ] if sphinx_ver >= (7, 1): common += ["-D", "translation_progress_classes=1"] @@ -230,7 +230,7 @@ jobs: "-D", "locale_dirs=locale", "-D", f"language={language}", "-D", "gettext_compact=0", - "-D", "exclude_patterns=locale/**", + "-D", "exclude_patterns=includes/*.rst,venv/*,README.rst,locale/**", ] if sphinx_ver >= (7, 1): common += ["-D", "translation_progress_classes=1"] @@ -301,7 +301,7 @@ jobs: "-D", "locale_dirs=locale", "-D", f"language={language}", "-D", "gettext_compact=0", - "-D", "exclude_patterns=locale/**", + "-D", "exclude_patterns=includes/*.rst,venv/*,README.rst,locale/**", ] if sphinx_ver >= (7, 1): common += ["-D", "translation_progress_classes=1"] @@ -373,7 +373,7 @@ jobs: "-D", "locale_dirs=locale", "-D", f"language={language}", "-D", "gettext_compact=0", - "-D", "exclude_patterns=locale/**", + "-D", "exclude_patterns=includes/*.rst,venv/*,README.rst,locale/**", ] if sphinx_ver >= (7, 1): common += ["-D", "translation_progress_classes=1"] @@ -438,7 +438,7 @@ jobs: "-D", "locale_dirs=locale", "-D", f"language={language}", "-D", "gettext_compact=0", - "-D", "exclude_patterns=locale/**", + "-D", "exclude_patterns=includes/*.rst,venv/*,README.rst,locale/**", ] if sphinx_ver >= (7, 1): common += ["-D", "translation_progress_classes=1"] From 126f22c8e543ca8548d4e42da04fa1c6928d7634 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Apr 2026 21:35:58 +0000 Subject: [PATCH 09/29] fix: override SPHINXERRORHANDLING to avoid treating warnings as errors Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/307e8acc-650b-42e6-a2e9-e39842abeade Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d382625..80bd66b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -173,7 +173,7 @@ jobs: PY - run: make venv working-directory: ./Doc - - run: make dist-html SPHINXOPTS="${{ steps.build-settings.outputs.common_sphinxopts }}" + - run: make dist-html SPHINXERRORHANDLING= SPHINXOPTS="${{ steps.build-settings.outputs.common_sphinxopts }}" working-directory: ./Doc - uses: actions/upload-artifact@master if: always() @@ -244,7 +244,7 @@ jobs: PY - run: make venv working-directory: ./Doc - - run: make dist-text SPHINXOPTS="${{ steps.build-settings.outputs.common_sphinxopts }}" + - run: make dist-text SPHINXERRORHANDLING= SPHINXOPTS="${{ steps.build-settings.outputs.common_sphinxopts }}" working-directory: ./Doc - uses: actions/upload-artifact@master if: always() @@ -316,7 +316,7 @@ jobs: - run: make venv working-directory: ./Doc - run: sudo apt-get update && sudo apt-get install -y texinfo - - run: make dist-texinfo SPHINXOPTS="${{ steps.build-settings.outputs.common_sphinxopts }}" + - run: make dist-texinfo SPHINXERRORHANDLING= SPHINXOPTS="${{ steps.build-settings.outputs.common_sphinxopts }}" working-directory: ./Doc - uses: actions/upload-artifact@master if: always() @@ -387,7 +387,7 @@ jobs: PY - run: make venv working-directory: ./Doc - - run: make dist-epub SPHINXOPTS="${{ steps.build-settings.outputs.common_sphinxopts }}" + - run: make dist-epub SPHINXERRORHANDLING= SPHINXOPTS="${{ steps.build-settings.outputs.common_sphinxopts }}" working-directory: ./Doc - uses: actions/upload-artifact@master if: always() @@ -453,7 +453,7 @@ jobs: - run: make venv working-directory: ./Doc - run: sudo apt-get update && sudo apt-get install -y latexmk texlive-xetex fonts-freefont-otf xindy librsvg2-bin - - run: make dist-pdf SPHINXOPTS="${{ steps.build-settings.outputs.pdf_sphinxopts }}" + - run: make dist-pdf SPHINXERRORHANDLING= SPHINXOPTS="${{ steps.build-settings.outputs.pdf_sphinxopts }}" working-directory: ./Doc - uses: actions/upload-artifact@master if: always() From b12c4c2eb14d7198bf3f9ecffd3956b5b1ff19e9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 08:55:48 +0000 Subject: [PATCH 10/29] feat: install language-specific texlive packages for PDF builds Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/3e456ad8-89a6-444b-b3d2-38913d2e7c37 Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 80bd66b..4465e28 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -453,6 +453,16 @@ jobs: - run: make venv working-directory: ./Doc - run: sudo apt-get update && sudo apt-get install -y latexmk texlive-xetex fonts-freefont-otf xindy librsvg2-bin + - name: Install language-specific texlive packages + env: + LANGUAGE_TAG: ${{ inputs.language }} + run: | + case "$LANGUAGE_TAG" in + pt-br) sudo apt-get install -y texlive-lang-portuguese ;; + zh-tw|zh-cn) sudo apt-get install -y texlive-lang-chinese ;; + ko) sudo apt-get install -y texlive-lang-korean ;; + ja) sudo apt-get install -y texlive-lang-japanese ;; + esac - run: make dist-pdf SPHINXERRORHANDLING= SPHINXOPTS="${{ steps.build-settings.outputs.pdf_sphinxopts }}" working-directory: ./Doc - uses: actions/upload-artifact@master From 0dd66e4ae7d6552ee042fea7ef6fea0b1987afc9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 10:31:08 +0000 Subject: [PATCH 11/29] feat: dump LaTeX log tails on failure after make dist-pdf Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/2611dcc8-d8af-4048-8b27-a106eff342f0 Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4465e28..7aaf874 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -465,6 +465,14 @@ jobs: esac - run: make dist-pdf SPHINXERRORHANDLING= SPHINXOPTS="${{ steps.build-settings.outputs.pdf_sphinxopts }}" working-directory: ./Doc + - name: Dump LaTeX log tails on failure + if: failure() + run: | + set -euxo pipefail + for f in Doc/build/latex/*.log; do + echo "===== $f (tail) =====" + tail -n 80 "$f" || true + done - uses: actions/upload-artifact@master if: always() with: From cdad0b0898f18d435eb5d95e8610144a5db77c51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 13:42:06 +0000 Subject: [PATCH 12/29] fix: normalize language tag for texlive matching and publishing; add ja luaotfload packages Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/1260f0b8-d528-44d5-a046-1241a8aec3fe Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 7aaf874..1ff7ee0 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -457,11 +457,12 @@ jobs: env: LANGUAGE_TAG: ${{ inputs.language }} run: | + LANGUAGE_TAG=$(echo "$LANGUAGE_TAG" | tr '_' '-' | tr '[:upper:]' '[:lower:]') case "$LANGUAGE_TAG" in pt-br) sudo apt-get install -y texlive-lang-portuguese ;; zh-tw|zh-cn) sudo apt-get install -y texlive-lang-chinese ;; ko) sudo apt-get install -y texlive-lang-korean ;; - ja) sudo apt-get install -y texlive-lang-japanese ;; + ja) sudo apt-get install -y texlive-lang-japanese luaotfload texlive-plain-generic ;; esac - run: make dist-pdf SPHINXERRORHANDLING= SPHINXOPTS="${{ steps.build-settings.outputs.pdf_sphinxopts }}" working-directory: ./Doc @@ -511,9 +512,13 @@ jobs: path: _site continue-on-error: true + - name: Normalize language tag for publishing + id: normalize-lang + run: echo "tag=$(echo '${{ inputs.language }}' | tr '_' '-' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" + - name: Prepare site directory run: | - mkdir -p _site/${{ inputs.language }}/${{ needs.prepare.outputs.major_minor }} + mkdir -p _site/${{ steps.normalize-lang.outputs.tag }}/${{ needs.prepare.outputs.major_minor }} # Remove git metadata; safe even if checkout above did not succeed rm -rf _site/.git @@ -529,7 +534,7 @@ jobs: # excluding PDF build logs which are for debugging only. find artifacts/ -type f \( -name "*.zip" -o -name "*.tar.bz2" -o -name "*.epub" \) \ ! -name "python-*-pdf-logs.zip" \ - -exec cp {} _site/${{ inputs.language }}/${{ needs.prepare.outputs.major_minor }}/ \; + -exec cp {} _site/${{ steps.normalize-lang.outputs.tag }}/${{ needs.prepare.outputs.major_minor }}/ \; - name: Generate per-version directory listing run: | From 82f2880441c98966bb0f68ec51889e315a8f32a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 13:49:56 +0000 Subject: [PATCH 13/29] fix: replace luaotfload with texlive-luatex for Japanese PDF build Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/3df7fda7-5ae1-44c5-a393-fddf69bb9805 Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1ff7ee0..6d8085b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -462,7 +462,7 @@ jobs: pt-br) sudo apt-get install -y texlive-lang-portuguese ;; zh-tw|zh-cn) sudo apt-get install -y texlive-lang-chinese ;; ko) sudo apt-get install -y texlive-lang-korean ;; - ja) sudo apt-get install -y texlive-lang-japanese luaotfload texlive-plain-generic ;; + ja) sudo apt-get install -y texlive-lang-japanese texlive-luatex texlive-plain-generic ;; esac - run: make dist-pdf SPHINXERRORHANDLING= SPHINXOPTS="${{ steps.build-settings.outputs.pdf_sphinxopts }}" working-directory: ./Doc From 771243819c15c54e2abce8ed68da6601f8776f3a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 16:15:57 +0000 Subject: [PATCH 14/29] fix: add fonts-noto-cjk for Japanese PDF build Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/2b1c5cbb-5c89-4a0d-93a1-1987a35ea289 Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 6d8085b..a7678d9 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -462,7 +462,7 @@ jobs: pt-br) sudo apt-get install -y texlive-lang-portuguese ;; zh-tw|zh-cn) sudo apt-get install -y texlive-lang-chinese ;; ko) sudo apt-get install -y texlive-lang-korean ;; - ja) sudo apt-get install -y texlive-lang-japanese texlive-luatex texlive-plain-generic ;; + ja) sudo apt-get install -y texlive-lang-japanese texlive-luatex texlive-plain-generic fonts-noto-cjk ;; esac - run: make dist-pdf SPHINXERRORHANDLING= SPHINXOPTS="${{ steps.build-settings.outputs.pdf_sphinxopts }}" working-directory: ./Doc From 4d5b7927a27f518bdc654db38fceabf83f5f08a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 21:58:39 +0000 Subject: [PATCH 15/29] fix: replace fonts-noto-cjk with texlive-latex-extra for Japanese PDF build Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/5cd722c7-1ea5-4f90-a44d-532c286e9e36 Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a7678d9..d6d382f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -462,7 +462,7 @@ jobs: pt-br) sudo apt-get install -y texlive-lang-portuguese ;; zh-tw|zh-cn) sudo apt-get install -y texlive-lang-chinese ;; ko) sudo apt-get install -y texlive-lang-korean ;; - ja) sudo apt-get install -y texlive-lang-japanese texlive-luatex texlive-plain-generic fonts-noto-cjk ;; + ja) sudo apt-get install -y texlive-lang-japanese texlive-luatex texlive-plain-generic texlive-latex-extra ;; esac - run: make dist-pdf SPHINXERRORHANDLING= SPHINXOPTS="${{ steps.build-settings.outputs.pdf_sphinxopts }}" working-directory: ./Doc From 5e4ae8143ea32f5b6d5df154746376711d3ea8a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 22:46:00 +0000 Subject: [PATCH 16/29] fix: add fonts-noto-cjk back for Japanese PDF build Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/4155fe9c-bc4b-4768-a8d3-e2a3b2d9dfe3 Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d6d382f..be65030 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -462,7 +462,7 @@ jobs: pt-br) sudo apt-get install -y texlive-lang-portuguese ;; zh-tw|zh-cn) sudo apt-get install -y texlive-lang-chinese ;; ko) sudo apt-get install -y texlive-lang-korean ;; - ja) sudo apt-get install -y texlive-lang-japanese texlive-luatex texlive-plain-generic texlive-latex-extra ;; + ja) sudo apt-get install -y texlive-lang-japanese texlive-luatex texlive-plain-generic texlive-latex-extra fonts-noto-cjk ;; esac - run: make dist-pdf SPHINXERRORHANDLING= SPHINXOPTS="${{ steps.build-settings.outputs.pdf_sphinxopts }}" working-directory: ./Doc From 92d0e7b83fec7aef70b2c1b02b8c24eb370cca0a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Apr 2026 00:17:52 +0000 Subject: [PATCH 17/29] fix: add texlive-fonts-extra/recommended and texlive-latex-recommended for Japanese Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/0a27e315-dfe4-4f36-9754-7966d60ca232 Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index be65030..e06252c 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -462,7 +462,7 @@ jobs: pt-br) sudo apt-get install -y texlive-lang-portuguese ;; zh-tw|zh-cn) sudo apt-get install -y texlive-lang-chinese ;; ko) sudo apt-get install -y texlive-lang-korean ;; - ja) sudo apt-get install -y texlive-lang-japanese texlive-luatex texlive-plain-generic texlive-latex-extra fonts-noto-cjk ;; + ja) sudo apt-get install -y texlive-lang-japanese texlive-luatex texlive-plain-generic texlive-latex-extra texlive-fonts-extra texlive-fonts-recommended texlive-latex-recommended fonts-noto-cjk ;; esac - run: make dist-pdf SPHINXERRORHANDLING= SPHINXOPTS="${{ steps.build-settings.outputs.pdf_sphinxopts }}" working-directory: ./Doc From 16b55397d95925d7090cb24981673cc6dd8b15aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 09:12:54 +0000 Subject: [PATCH 18/29] fix: ensure all required packages are installed for Japanese PDFs Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/529d3641-6ea9-4a04-b53b-0aa97241fc7e Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e06252c..cc32d5a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -462,7 +462,7 @@ jobs: pt-br) sudo apt-get install -y texlive-lang-portuguese ;; zh-tw|zh-cn) sudo apt-get install -y texlive-lang-chinese ;; ko) sudo apt-get install -y texlive-lang-korean ;; - ja) sudo apt-get install -y texlive-lang-japanese texlive-luatex texlive-plain-generic texlive-latex-extra texlive-fonts-extra texlive-fonts-recommended texlive-latex-recommended fonts-noto-cjk ;; + ja) sudo apt-get install -y latexmk texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended texlive-lang-japanese texlive-luatex texlive-plain-generic texlive-fonts-extra fonts-noto-cjk fonts-noto-core fonts-dejavu ;; esac - run: make dist-pdf SPHINXERRORHANDLING= SPHINXOPTS="${{ steps.build-settings.outputs.pdf_sphinxopts }}" working-directory: ./Doc From 3a850a5ac208fed1373e04aa0c42f93a5725ed2d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 10:41:07 +0000 Subject: [PATCH 19/29] fix: add fonts-freefont-otf to Japanese PDF apt install Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/e7bbafdc-8d7f-40c8-b0b0-2ed90295be92 Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index cc32d5a..b23d9b4 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -462,7 +462,7 @@ jobs: pt-br) sudo apt-get install -y texlive-lang-portuguese ;; zh-tw|zh-cn) sudo apt-get install -y texlive-lang-chinese ;; ko) sudo apt-get install -y texlive-lang-korean ;; - ja) sudo apt-get install -y latexmk texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended texlive-lang-japanese texlive-luatex texlive-plain-generic texlive-fonts-extra fonts-noto-cjk fonts-noto-core fonts-dejavu ;; + ja) sudo apt-get install -y latexmk texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended texlive-lang-japanese texlive-luatex texlive-plain-generic texlive-fonts-extra fonts-noto-cjk fonts-noto-core fonts-dejavu fonts-freefont-otf ;; esac - run: make dist-pdf SPHINXERRORHANDLING= SPHINXOPTS="${{ steps.build-settings.outputs.pdf_sphinxopts }}" working-directory: ./Doc From 28aca5e4b93511adb4409119973a97dda38995b7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 21:19:14 +0000 Subject: [PATCH 20/29] fix: add missing packages for Japanese PDF builds (xindy, texinfo, texlive-xetex, fonts-noto-cjk-extra) Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/61215c01-1eab-49fb-a22c-bd616a7c1014 Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b23d9b4..dffd95c 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -462,7 +462,7 @@ jobs: pt-br) sudo apt-get install -y texlive-lang-portuguese ;; zh-tw|zh-cn) sudo apt-get install -y texlive-lang-chinese ;; ko) sudo apt-get install -y texlive-lang-korean ;; - ja) sudo apt-get install -y latexmk texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended texlive-lang-japanese texlive-luatex texlive-plain-generic texlive-fonts-extra fonts-noto-cjk fonts-noto-core fonts-dejavu fonts-freefont-otf ;; + ja) sudo apt-get install -y latexmk xindy texinfo texlive-xetex texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended texlive-lang-japanese texlive-luatex texlive-plain-generic texlive-fonts-extra fonts-noto-cjk fonts-noto-cjk-extra fonts-noto-core fonts-dejavu fonts-freefont-otf ;; esac - run: make dist-pdf SPHINXERRORHANDLING= SPHINXOPTS="${{ steps.build-settings.outputs.pdf_sphinxopts }}" working-directory: ./Doc From 057be18d98951f478fa71defa63beb0647ce40e4 Mon Sep 17 00:00:00 2001 From: Maciej Olko Date: Tue, 21 Apr 2026 00:46:04 +0200 Subject: [PATCH 21/29] fix: simplify Japanese PDF build dependencies --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index dffd95c..ccd5a63 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -462,7 +462,7 @@ jobs: pt-br) sudo apt-get install -y texlive-lang-portuguese ;; zh-tw|zh-cn) sudo apt-get install -y texlive-lang-chinese ;; ko) sudo apt-get install -y texlive-lang-korean ;; - ja) sudo apt-get install -y latexmk xindy texinfo texlive-xetex texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended texlive-lang-japanese texlive-luatex texlive-plain-generic texlive-fonts-extra fonts-noto-cjk fonts-noto-cjk-extra fonts-noto-core fonts-dejavu fonts-freefont-otf ;; + ja) sudo apt-get install -y texlive-lang-japanese texlive-luatex fonts-noto-cjk-extra ;; esac - run: make dist-pdf SPHINXERRORHANDLING= SPHINXOPTS="${{ steps.build-settings.outputs.pdf_sphinxopts }}" working-directory: ./Doc From 5b3a4d138313d47f91b1183f005a711a66161177 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:29:10 +0000 Subject: [PATCH 22/29] docs: update README with project description, formats, versions, and how it works Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/e2ac249b-d85c-4bb8-a859-73da4bbb285b Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- README.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2580ae6..738a820 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,34 @@ # python-docs-offline -Offline builds of Python documentation in various formats: + +Automated daily builds of Python documentation for offline use, published to +[GitHub Pages](https://m-aciek.github.io/python-docs-offline/). + +## Available formats + * PDF * Zipped offline HTML * Plain text * Texinfo * EPUB +## Supported versions and languages + +Builds are generated for all currently supported CPython versions (fetched +dynamically from the [Python release cycle](https://peps.python.org/api/release-cycle.json)) +and all languages that are active in production on [docs.python.org](https://docs.python.org) +(fetched dynamically from [docsbuild-scripts config](https://github.com/python/docsbuild-scripts/blob/main/config.toml)). + +## How it works + +A scheduled GitHub Actions workflow runs daily. It queries the CPython release +cycle API and the docsbuild-scripts configuration to determine which +(version, language) combinations to build. Each combination is built using +the [build.yaml](.github/workflows/build.yaml) reusable workflow, which checks +out the CPython source, builds the documentation with Sphinx, and uploads the +resulting archives as artifacts. After all builds complete, a single deploy job +merges the artifacts into the `gh-pages` branch and publishes them to GitHub +Pages. + ## Rejected idea I wanted to present a waiting page in place(s) of file(s) that are being generated, that would render similarly to 404 page. From fd5dfb615e0077539ab45c058438ade6d5aa3ccb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:39:13 +0000 Subject: [PATCH 23/29] fix: preserve backslashes in sphinxopts by using shlex.split(opt, posix=False) Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/f702db87-f295-4b02-8155-318f2ac25d0b Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/build.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ccd5a63..4abfd57 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -145,7 +145,7 @@ jobs: import shlex language = os.environ["LANGUAGE_TAG"] - config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt)] + config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt, posix=False)] common = [] if language != "en": try: @@ -216,7 +216,7 @@ jobs: import shlex language = os.environ["LANGUAGE_TAG"] - config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt)] + config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt, posix=False)] common = [] if language != "en": try: @@ -287,7 +287,7 @@ jobs: import shlex language = os.environ["LANGUAGE_TAG"] - config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt)] + config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt, posix=False)] common = [] if language != "en": try: @@ -359,7 +359,7 @@ jobs: import shlex language = os.environ["LANGUAGE_TAG"] - config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt)] + config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt, posix=False)] common = [] if language != "en": try: @@ -424,7 +424,7 @@ jobs: import shlex language = os.environ["LANGUAGE_TAG"] - config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt)] + config_sphinxopts = [token for opt in json.loads(os.environ["SPHINXOPTS_JSON"]) for token in shlex.split(opt, posix=False)] common = [] if language != "en": try: From 4255ea6968ce9a7b62aefc10e4bbe9624813528d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Apr 2026 01:43:50 +0000 Subject: [PATCH 24/29] fix: organize deploy output by language to prevent cross-language overwrites Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/dec1a8ed-0cc9-42c4-bc71-e4d02fa3b64a Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/schedule.yaml | 109 +++++++++++++++++++------------- 1 file changed, 66 insertions(+), 43 deletions(-) diff --git a/.github/workflows/schedule.yaml b/.github/workflows/schedule.yaml index 3524e10..2df3e4b 100644 --- a/.github/workflows/schedule.yaml +++ b/.github/workflows/schedule.yaml @@ -122,20 +122,42 @@ jobs: - name: Copy new archives into site directory run: | - # Copy generated archives (zip, tar.bz2, epub) into _site//, + # Copy generated archives (zip, tar.bz2, epub) into _site///, # excluding PDF build logs which are for debugging only. - # Extract major.minor from filenames like python-3.14.0-docs-html.zip. - find artifacts/ -type f \( -name "*.zip" -o -name "*.tar.bz2" -o -name "*.epub" \) \ - ! -name "python-*-pdf-logs.zip" | while read -r f; do - filename=$(basename "$f") - major_minor=$(echo "$filename" | sed -n 's/^python-\([0-9]*\.[0-9]*\).*/\1/p') - mkdir -p "_site/$major_minor" - cp "$f" "_site/$major_minor/" - done + # Extract major.minor and language from filenames like: + # python-3.14.0-docs-html.zip (English) + # python-3.14.0-fr-docs-html.zip (French) + python - <<'PY' + import re + import shutil + from pathlib import Path + + artifacts = Path("artifacts") + site = Path("_site") + name_re = re.compile( + r"^python-(\d+\.\d+)[\d.]*(?:-([a-zA-Z][a-zA-Z0-9_-]*))?-(?:docs|pdf)-" + ) + for f in artifacts.rglob("*"): + if not f.is_file(): + continue + name = f.name + if not (name.endswith(".zip") or name.endswith(".tar.bz2") or name.endswith(".epub")): + continue + if re.search(r"-pdf-logs\.zip$", name): + continue + m = name_re.match(name) + if not m: + continue + major_minor = m.group(1) + lang = (m.group(2) or "en").replace("_", "-").lower() + dest = site / lang / major_minor + dest.mkdir(parents=True, exist_ok=True) + shutil.copy2(f, dest / name) + PY - name: Symlink 3 to stable version run: | - # Create a relative symlink _site/3 -> (e.g. 3 -> 3.14), + # Create a relative symlink _site/en/3 -> (e.g. 3 -> 3.14), # pointing to the first "bugfix" (stable) version from release-cycle JSON. stable=$(curl -sf https://peps.python.org/api/release-cycle.json | \ jq -r '[to_entries[] | select(.value.status == "bugfix")] | first | .key') @@ -144,8 +166,8 @@ jobs: exit 1 fi # Remove existing 3 directory or symlink before creating new symlink - rm -rf _site/3 - ln -s "$stable" _site/3 + rm -rf _site/en/3 + ln -s "$stable" _site/en/3 - name: Generate per-version directory listing run: | @@ -158,39 +180,40 @@ jobs: root = Path("_site") version_pattern = re.compile(r"^\d+\.\d+$") - for version_dir in sorted( - p for p in root.iterdir() if p.is_dir() and version_pattern.match(p.name) - ): - files = sorted( - p for p in version_dir.iterdir() if p.is_file() and p.name != "index.html" - ) - rows = [] - for file_path in files: - stat = file_path.stat() - timestamp = datetime.fromtimestamp(stat.st_mtime, timezone.utc).strftime( - "%Y-%m-%d %H:%M:%S UTC" - ) - rows.append( - f'{escape(file_path.name)}{timestamp}{stat.st_size}' + for lang_dir in sorted(p for p in root.iterdir() if p.is_dir()): + for version_dir in sorted( + p for p in lang_dir.iterdir() if p.is_dir() and version_pattern.match(p.name) + ): + files = sorted( + p for p in version_dir.iterdir() if p.is_file() and p.name != "index.html" ) + rows = [] + for file_path in files: + stat = file_path.stat() + timestamp = datetime.fromtimestamp(stat.st_mtime, timezone.utc).strftime( + "%Y-%m-%d %H:%M:%S UTC" + ) + rows.append( + f'{escape(file_path.name)}{timestamp}{stat.st_size}' + ) - relative_path = f"/{version_dir.relative_to(root).as_posix()}/" - html = [ - "", - '', - 'Directory listing', - "", - f"

Path: {escape(relative_path)}

", - "", - '', - "", - *rows, - "", - "
FilenameTimestamp (UTC)Size (bytes)
", - "", - "", - ] - (version_dir / "index.html").write_text("\n".join(html) + "\n", encoding="utf-8") + relative_path = f"/{version_dir.relative_to(root).as_posix()}/" + html = [ + "", + '', + 'Directory listing', + "", + f"

Path: {escape(relative_path)}

", + "", + '', + "", + *rows, + "", + "
FilenameTimestamp (UTC)Size (bytes)
", + "", + "", + ] + (version_dir / "index.html").write_text("\n".join(html) + "\n", encoding="utf-8") PY - name: Upload Pages artifact From 551b615946c8391b8168e9aed7eb3a0afa1547df Mon Sep 17 00:00:00 2001 From: Maciej Olko Date: Tue, 21 Apr 2026 09:18:36 +0200 Subject: [PATCH 25/29] fix: add textlive-fonts-extra to Japanese PDF build dependencies --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4abfd57..fdebd9c 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -462,7 +462,7 @@ jobs: pt-br) sudo apt-get install -y texlive-lang-portuguese ;; zh-tw|zh-cn) sudo apt-get install -y texlive-lang-chinese ;; ko) sudo apt-get install -y texlive-lang-korean ;; - ja) sudo apt-get install -y texlive-lang-japanese texlive-luatex fonts-noto-cjk-extra ;; + ja) sudo apt-get install -y texlive-lang-japanese texlive-luatex fonts-noto-cjk-extra texlive-fonts-extra ;; esac - run: make dist-pdf SPHINXERRORHANDLING= SPHINXOPTS="${{ steps.build-settings.outputs.pdf_sphinxopts }}" working-directory: ./Doc From d110b0b5d18dd594c954f4870e74d3f3a6111c80 Mon Sep 17 00:00:00 2001 From: Maciej Olko Date: Tue, 21 Apr 2026 16:28:57 +0200 Subject: [PATCH 26/29] Revert "fix: add textlive-fonts-extra to Japanese PDF build dependencies" This reverts commit 551b615946c8391b8168e9aed7eb3a0afa1547df. --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index fdebd9c..4abfd57 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -462,7 +462,7 @@ jobs: pt-br) sudo apt-get install -y texlive-lang-portuguese ;; zh-tw|zh-cn) sudo apt-get install -y texlive-lang-chinese ;; ko) sudo apt-get install -y texlive-lang-korean ;; - ja) sudo apt-get install -y texlive-lang-japanese texlive-luatex fonts-noto-cjk-extra texlive-fonts-extra ;; + ja) sudo apt-get install -y texlive-lang-japanese texlive-luatex fonts-noto-cjk-extra ;; esac - run: make dist-pdf SPHINXERRORHANDLING= SPHINXOPTS="${{ steps.build-settings.outputs.pdf_sphinxopts }}" working-directory: ./Doc From a9e096c3673215e4d6f68e5700142fa1503061ac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 13:49:32 +0000 Subject: [PATCH 27/29] fix: match epub filenames and prevent docs/pdf from being captured as language Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/cd79b93b-6d1a-4eff-a490-a298f00eb5a4 Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/schedule.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/schedule.yaml b/.github/workflows/schedule.yaml index 2df3e4b..1cc22a4 100644 --- a/.github/workflows/schedule.yaml +++ b/.github/workflows/schedule.yaml @@ -125,8 +125,10 @@ jobs: # Copy generated archives (zip, tar.bz2, epub) into _site///, # excluding PDF build logs which are for debugging only. # Extract major.minor and language from filenames like: - # python-3.14.0-docs-html.zip (English) - # python-3.14.0-fr-docs-html.zip (French) + # python-3.14.0-docs-html.zip (English zip) + # python-3.14.0-docs.epub (English epub) + # python-3.14.0-fr-docs-html.zip (French zip) + # python-3.14.0-fr-docs.epub (French epub) python - <<'PY' import re import shutil @@ -135,7 +137,7 @@ jobs: artifacts = Path("artifacts") site = Path("_site") name_re = re.compile( - r"^python-(\d+\.\d+)[\d.]*(?:-([a-zA-Z][a-zA-Z0-9_-]*))?-(?:docs|pdf)-" + r"^python-(\d+\.\d+)[\d.]*(?:-(?!docs|pdf)([a-zA-Z][a-zA-Z0-9_-]*))?-(?:docs|pdf)" ) for f in artifacts.rglob("*"): if not f.is_file(): From e5d68dc40362e539f987ecdf0bd819490c4f2b37 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:16:46 +0000 Subject: [PATCH 28/29] feat: add backward-compat symlinks _site/ -> en/ Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/45f4f268-2d71-465b-ab7b-04153e4758b8 Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/schedule.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/schedule.yaml b/.github/workflows/schedule.yaml index 1cc22a4..eddec87 100644 --- a/.github/workflows/schedule.yaml +++ b/.github/workflows/schedule.yaml @@ -171,6 +171,25 @@ jobs: rm -rf _site/en/3 ln -s "$stable" _site/en/3 + - name: Symlink bare version paths to en/ for backwards compatibility + run: | + # Create _site/ -> en/ symlinks so that old + # URLs like /3.14/ continue to work after content moved to /en/3.14/. + python - <<'PY' + import re + from pathlib import Path + + site = Path("_site") + en_dir = site / "en" + version_pattern = re.compile(r"^\d+\.\d+$") + for version_dir in en_dir.iterdir(): + if not version_dir.is_dir() or not version_pattern.match(version_dir.name): + continue + link = site / version_dir.name + if not link.exists() and not link.is_symlink(): + link.symlink_to(Path("en") / version_dir.name) + PY + - name: Generate per-version directory listing run: | python - <<'PY' From 1810ec4717dbd45088c8f47c9c17d7e42c591812 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 22:49:20 +0000 Subject: [PATCH 29/29] fix: remove hyphen from language capture to fix fr-docs parsing in artifact regex Agent-Logs-Url: https://github.com/m-aciek/python-docs-offline/sessions/7cf2716e-7fd0-4fec-8e64-ee88acb4f0a2 Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com> --- .github/workflows/schedule.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/schedule.yaml b/.github/workflows/schedule.yaml index eddec87..fbb149a 100644 --- a/.github/workflows/schedule.yaml +++ b/.github/workflows/schedule.yaml @@ -137,7 +137,7 @@ jobs: artifacts = Path("artifacts") site = Path("_site") name_re = re.compile( - r"^python-(\d+\.\d+)[\d.]*(?:-(?!docs|pdf)([a-zA-Z][a-zA-Z0-9_-]*))?-(?:docs|pdf)" + r"^python-(\d+\.\d+)[\d.]*(?:-(?!docs|pdf)([a-zA-Z][a-zA-Z0-9_]*))?-(?:docs|pdf)" ) for f in artifacts.rglob("*"): if not f.is_file():