From 47fbaa2b1231179e1a1ab2d3aa022819b7c0f12b Mon Sep 17 00:00:00 2001 From: Yaoyao Ding Date: Sun, 12 Apr 2026 18:54:03 -0400 Subject: [PATCH] [CI] Add Python compat tests, merge docs workflows, refactor - Add test-python-compat job to run matmul_v2 smoke test across Python 3.10, 3.11, 3.12, 3.13 on a single GPU node - Merge deploy-docs.yaml into docs.yaml with separate build and deploy jobs (build runs on all PRs, deploy only on main/tags) - Fail docs build on Sphinx warnings with -W flag - Extract gh-pages version update logic into standalone script at .github/workflows/scripts/update_gh_pages.py - Remove docs job from tests.yaml Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Yaoyao Ding --- .../workflows/{deploy-docs.yaml => docs.yaml} | 83 ++++++++----------- .github/workflows/scripts/update_gh_pages.py | 59 +++++++++++++ .github/workflows/tests.yaml | 78 +++++++++-------- 3 files changed, 136 insertions(+), 84 deletions(-) rename .github/workflows/{deploy-docs.yaml => docs.yaml} (57%) create mode 100644 .github/workflows/scripts/update_gh_pages.py diff --git a/.github/workflows/deploy-docs.yaml b/.github/workflows/docs.yaml similarity index 57% rename from .github/workflows/deploy-docs.yaml rename to .github/workflows/docs.yaml index 903695ad..dfba4ce9 100644 --- a/.github/workflows/deploy-docs.yaml +++ b/.github/workflows/docs.yaml @@ -1,22 +1,23 @@ -name: Deploy Docs +name: Docs on: push: branches: - main + - "pull-request/[0-9]+" tags: - "v*" workflow_dispatch: concurrency: - group: deploy-docs - cancel-in-progress: false + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') && github.ref != 'refs/heads/main' }} permissions: contents: write jobs: - build-and-deploy: + build: if: github.repository == 'NVIDIA/tilus' runs-on: ubuntu-latest steps: @@ -34,7 +35,30 @@ jobs: - name: Build docs run: | cd docs - sphinx-build -W source build/html + make html SPHINXOPTS="-W" # -W treats warnings as errors + + - name: Upload docs artifact + if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') + uses: actions/upload-artifact@v4 + with: + name: docs-html + path: docs/build/html/ + + deploy: + needs: build + if: github.repository == 'NVIDIA/tilus' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Download docs artifact + uses: actions/download-artifact@v4 + with: + name: docs-html + path: docs/build/html/ - name: Determine version directory id: version @@ -83,51 +107,10 @@ jobs: # Update versions.json and generate root index.html cd "$DEPLOY_DIR" - python3 << PYEOF - import json, pathlib - - versions_file = pathlib.Path("versions.json") - versions = json.loads(versions_file.read_text()) if versions_file.exists() else [] - # versions.json format: [{"slug": "stable", "label": "v0.2.0 (Stable)"}, ...] - slugs = {v["slug"] for v in versions} - - version = "${VERSION_DIR}" - is_tag = "${IS_TAG}" == "true" - - # Add the version entry if new - if version not in slugs: - versions.append({"slug": version, "label": version}) - slugs.add(version) - - # For tag releases, add or update the "stable" entry - if is_tag: - # Remove old stable entry - versions = [v for v in versions if v["slug"] != "stable"] - versions.append({"slug": "stable", "label": version + " (Stable)"}) - - # Sort: latest first, stable second, then tags in reverse order - def sort_key(v): - s = v["slug"] - if s == "latest": - return (0, "") - if s == "stable": - return (1, "") - return (2, s) - versions.sort(key=sort_key) - versions_file.write_text(json.dumps(versions, indent=2) + "\n") - - # Generate root index.html - # Redirect to stable if it exists, otherwise latest - has_stable = any(v["slug"] == "stable" for v in versions) - target = "stable" if has_stable else "latest" - pathlib.Path("index.html").write_text( - "\\n" - "\\n" - f'\\n' - f"

Redirecting to {target} documentation...

\\n" - "\\n" - ) - PYEOF + TAG_FLAG="" + if [[ "$IS_TAG" == "true" ]]; then TAG_FLAG="--is-tag"; fi + python3 "$GITHUB_WORKSPACE/.github/workflows/scripts/update_gh_pages.py" \ + --version-dir "$VERSION_DIR" $TAG_FLAG # Add .nojekyll to prevent GitHub Pages from ignoring _static touch .nojekyll diff --git a/.github/workflows/scripts/update_gh_pages.py b/.github/workflows/scripts/update_gh_pages.py new file mode 100644 index 00000000..0b39cefa --- /dev/null +++ b/.github/workflows/scripts/update_gh_pages.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"""Update versions.json and root index.html for gh-pages deployment.""" + +import argparse +import json +import pathlib + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--version-dir", required=True, help="Version directory name (e.g., 'latest' or 'v0.2.0')") + parser.add_argument("--is-tag", action="store_true", help="Whether this is a tag release") + args = parser.parse_args() + + version = args.version_dir + is_tag = args.is_tag + + # Update versions.json + versions_file = pathlib.Path("versions.json") + versions = json.loads(versions_file.read_text()) if versions_file.exists() else [] + # versions.json format: [{"slug": "stable", "label": "v0.2.0 (Stable)"}, ...] + slugs = {v["slug"] for v in versions} + + # Add the version entry if new + if version not in slugs: + versions.append({"slug": version, "label": version}) + slugs.add(version) + + # For tag releases, add or update the "stable" entry + if is_tag: + versions = [v for v in versions if v["slug"] != "stable"] + versions.append({"slug": "stable", "label": version + " (Stable)"}) + + # Sort: latest first, stable second, then tags in reverse order + def sort_key(v): + s = v["slug"] + if s == "latest": + return (0, "") + if s == "stable": + return (1, "") + return (2, s) + + versions.sort(key=sort_key) + versions_file.write_text(json.dumps(versions, indent=2) + "\n") + + # Generate root index.html — redirect to stable if it exists, otherwise latest + has_stable = any(v["slug"] == "stable" for v in versions) + target = "stable" if has_stable else "latest" + pathlib.Path("index.html").write_text( + "\n" + "\n" + f'\n' + f'

Redirecting to {target} documentation...

\n' + "\n" + ) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 1700f8de..c4f84106 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -16,7 +16,6 @@ jobs: outputs: should_run_tests: ${{ steps.changed-tests.outputs.any_changed }} should_run_examples: ${{ steps.changed-examples.outputs.any_changed }} - should_run_docs: ${{ steps.changed-docs.outputs.any_changed }} steps: - name: Checkout Repository uses: actions/checkout@v4 @@ -44,15 +43,47 @@ jobs: pyproject.toml base_sha: 'origin/main' - - name: Check for changes in docs - id: changed-docs - uses: step-security/changed-files@v46 + test-python-compat: + needs: check-changes + if: github.repository == 'NVIDIA/tilus' && needs.check-changes.outputs.should_run_tests == 'true' + runs-on: linux-amd64-gpu-l4-latest-1 + container: + image: nvidia/cuda:12.6.2-devel-ubuntu22.04 + options: --gpus all + steps: + - name: Checkout Repository + uses: actions/checkout@v4 with: - files: | - python/** - docs/** - pyproject.toml - base_sha: 'origin/main' + fetch-depth: 0 + + - name: Install dependencies + run: | + apt-get update && apt-get install -y git cmake nodejs software-properties-common + add-apt-repository -y ppa:deadsnakes/ppa + apt-get update && apt-get install -y python3.10 python3.10-venv python3.10-dev \ + python3.11 python3.11-venv python3.11-dev \ + python3.12 python3.12-venv python3.12-dev \ + python3.13 python3.13-venv python3.13-dev + + - name: Mark repo as safe for git + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Run smoke tests across Python versions + run: | + for pyver in 3.10 3.11 3.12 3.13; do + echo "=========================================" + echo "Testing Python $pyver" + echo "=========================================" + python${pyver} -m venv /tmp/venv-${pyver} + source /tmp/venv-${pyver}/bin/activate + pip install --upgrade pip + pip install build + python -m build . + pip install dist/*.whl[dev] + pytest tests/kernels/matmul/test_matmul_v2.py -v + deactivate + rm -rf /tmp/venv-${pyver} dist/ + done test-core: needs: check-changes @@ -81,27 +112,6 @@ jobs: - name: Run main tests run: pytest ./tests --ignore=tests/examples/ - docs: - needs: check-changes - if: github.repository == 'NVIDIA/tilus' && needs.check-changes.outputs.should_run_docs == 'true' - runs-on: ubuntu-latest - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup and Install Tilus - id: setup-and-install - uses: ./.github/actions/setup-environment - with: - python-version: '3.10' - - - name: Build docs - run: | - cd docs - sphinx-build -W source build/html - test-examples: needs: check-changes if: github.repository == 'NVIDIA/tilus' && needs.check-changes.outputs.should_run_examples == 'true' @@ -127,21 +137,21 @@ jobs: all-functional-tests: name: All Functional Tests - needs: [test-core, docs, test-examples] + needs: [test-python-compat, test-core, test-examples] if: always() && github.repository == 'NVIDIA/tilus' runs-on: ubuntu-latest steps: - name: Check job results run: | + echo "Python compat result: ${{ needs.test-python-compat.result }}" echo "Core tests result: ${{ needs.test-core.result }}" - echo "Docs result: ${{ needs.docs.result }}" echo "Example tests result: ${{ needs.test-examples.result }}" # Check if any required job failed - if [[ "${{ needs.test-core.result }}" == "failure" ]] || [[ "${{ needs.docs.result }}" == "failure" ]] || [[ "${{ needs.test-examples.result }}" == "failure" ]]; then + if [[ "${{ needs.test-python-compat.result }}" == "failure" ]] || [[ "${{ needs.test-core.result }}" == "failure" ]] || [[ "${{ needs.test-examples.result }}" == "failure" ]]; then echo "One or more functional tests failed" exit 1 - elif [[ "${{ needs.test-core.result }}" == "cancelled" ]] || [[ "${{ needs.docs.result }}" == "cancelled" ]] || [[ "${{ needs.test-examples.result }}" == "cancelled" ]]; then + elif [[ "${{ needs.test-python-compat.result }}" == "cancelled" ]] || [[ "${{ needs.test-core.result }}" == "cancelled" ]] || [[ "${{ needs.test-examples.result }}" == "cancelled" ]]; then echo "One or more functional tests were cancelled" exit 1 else