From 6cfa1327ba3ba010f53301ed800fb2160f6f522b Mon Sep 17 00:00:00 2001 From: MagicDoge Date: Thu, 30 Jul 2026 14:34:15 +0800 Subject: [PATCH 1/5] ci: route sim Linux CI to self-hosted CPU runners Use the self-hosted CPU runner pool for sim Linux jobs, avoid sudo, and run those jobs through the repository venv. --- .github/workflows/ci.yml | 168 +++++++++++++++++++++++++++++++-------- docs/ci.md | 32 ++++---- 2 files changed, 150 insertions(+), 50 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98b14508a9..18f021dd8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -164,14 +164,18 @@ jobs: st-sim-a2a3: needs: [detect-changes, pre-commit] if: needs.detect-changes.outputs.a2a3_changed == 'true' - runs-on: ${{ matrix.os }} + runs-on: ${{ fromJSON(matrix.runner) }} timeout-minutes: 30 env: SIMPLER_SCHEDULER_TIMEOUT_MS: "5000" strategy: + fail-fast: false matrix: - os: [ubuntu-latest, macos-latest] - python-version: ['3.10'] + include: + - runner: '["self-hosted", "cpu"]' + python-version: '3.10' + - runner: '["macos-latest"]' + python-version: '3.10' steps: - name: Checkout repository @@ -179,25 +183,47 @@ jobs: - name: Set up C++ compiler run: | if [[ "${{ runner.os }}" == "Linux" ]]; then - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test - sudo apt-get update - sudo apt-get install -y ninja-build - sudo apt-get install -y g++-15 || sudo apt-get install -y g++ - if ! command -v g++-15; then sudo ln -s $(which g++) /usr/local/bin/g++-15; fi - # graphviz: required by the dep_gen smoke (deps_viewer -> dot). - # Both a2a3 and a5 sim jobs run dep_gen, so install it in both. - sudo apt-get install -y graphviz + runner_spec='${{ matrix.runner }}' + if [[ "$runner_spec" == *self-hosted* ]]; then + command -v ninja + command -v g++ + command -v dot + if ! command -v g++-15 >/dev/null 2>&1; then + mkdir -p "$RUNNER_TEMP/bin" + ln -sf "$(command -v g++)" "$RUNNER_TEMP/bin/g++-15" + echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH" + fi + else + sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test + sudo apt-get update + sudo apt-get install -y ninja-build + sudo apt-get install -y g++-15 || sudo apt-get install -y g++ + if ! command -v g++-15; then sudo ln -s $(which g++) /usr/local/bin/g++-15; fi + # graphviz: required by the dep_gen smoke (deps_viewer -> dot). + # Both a2a3 and a5 sim jobs run dep_gen, so install it in both. + sudo apt-get install -y graphviz + fi else brew install ninja brew install gcc@15 || brew install gcc brew install graphviz fi - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python ${{ matrix.python-version }} (GitHub-hosted) + if: ${{ !contains(matrix.runner, 'self-hosted') }} uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} + - name: Set up Python ${{ matrix.python-version }} (self-hosted) + if: ${{ contains(matrix.runner, 'self-hosted') }} + run: | + python3 -m venv --system-site-packages .venv + source .venv/bin/activate + pip install --upgrade pip + pip install torch --index-url https://download.pytorch.org/whl/cpu + pip install '.[test]' + - name: Cache pip packages uses: actions/cache@v5 with: @@ -207,13 +233,18 @@ jobs: ${{ runner.os }}-pip- - name: Install dependencies + if: ${{ !contains(matrix.runner, 'self-hosted') }} run: | pip install torch --index-url https://download.pytorch.org/whl/cpu pip install '.[test]' - name: Run pytest scene tests (a2a3sim) run: | - pytest examples tests/st --platform a2a3sim --device 0-15 -v \ + PYTHON=python + if [[ '${{ matrix.runner }}' == *self-hosted* ]]; then + PYTHON=.venv/bin/python + fi + "$PYTHON" -m pytest examples tests/st --platform a2a3sim --device 0-15 -v \ --pto-session-timeout 600 --require-pto-isa # DFX per-feature smokes — the default pytest above passes no --enable-* @@ -222,7 +253,11 @@ jobs: # CI step rather than buried in a combined run. - name: dep_gen smoke run: | - pytest tests/st/a2a3/tensormap_and_ringbuffer/dfx/dep_gen/test_dep_gen.py \ + PYTHON=python + if [[ '${{ matrix.runner }}' == *self-hosted* ]]; then + PYTHON=.venv/bin/python + fi + "$PYTHON" -m pytest tests/st/a2a3/tensormap_and_ringbuffer/dfx/dep_gen/test_dep_gen.py \ --platform a2a3sim --device 0-15 -p no:xdist --pto-session-timeout 600 \ --require-pto-isa --enable-dep-gen @@ -233,39 +268,59 @@ jobs: # children take xdist args that `-p no:xdist` has disabled. - name: dep_gen smoke (host_build_graph) run: | - pytest tests/st/a2a3/host_build_graph/dfx/dep_gen/test_dep_gen.py \ + PYTHON=python + if [[ '${{ matrix.runner }}' == *self-hosted* ]]; then + PYTHON=.venv/bin/python + fi + "$PYTHON" -m pytest tests/st/a2a3/host_build_graph/dfx/dep_gen/test_dep_gen.py \ --platform a2a3sim --device 0-15 -p no:xdist --pto-session-timeout 600 \ --require-pto-isa --enable-dep-gen - name: l2_swimlane smoke run: | - pytest tests/st/a2a3/tensormap_and_ringbuffer/dfx/l2_swimlane/ \ + PYTHON=python + if [[ '${{ matrix.runner }}' == *self-hosted* ]]; then + PYTHON=.venv/bin/python + fi + "$PYTHON" -m pytest tests/st/a2a3/tensormap_and_ringbuffer/dfx/l2_swimlane/ \ --platform a2a3sim --device 0-15 -p no:xdist --pto-session-timeout 600 \ --require-pto-isa --enable-l2-swimlane --enable-dep-gen - name: PMU smoke run: | - pytest tests/st/a2a3/tensormap_and_ringbuffer/dfx/pmu/test_pmu.py \ + PYTHON=python + if [[ '${{ matrix.runner }}' == *self-hosted* ]]; then + PYTHON=.venv/bin/python + fi + "$PYTHON" -m pytest tests/st/a2a3/tensormap_and_ringbuffer/dfx/pmu/test_pmu.py \ --platform a2a3sim --device 0-15 -p no:xdist --pto-session-timeout 600 \ --require-pto-isa --enable-pmu 2 - name: args_dump smoke run: | - pytest tests/st/a2a3/tensormap_and_ringbuffer/dfx/args_dump/test_args_dump.py \ + PYTHON=python + if [[ '${{ matrix.runner }}' == *self-hosted* ]]; then + PYTHON=.venv/bin/python + fi + "$PYTHON" -m pytest tests/st/a2a3/tensormap_and_ringbuffer/dfx/args_dump/test_args_dump.py \ --platform a2a3sim --device 0-15 -p no:xdist --pto-session-timeout 600 \ --require-pto-isa --dump-args st-sim-a5: needs: [detect-changes, pre-commit] if: needs.detect-changes.outputs.a5_changed == 'true' - runs-on: ${{ matrix.os }} + runs-on: ${{ fromJSON(matrix.runner) }} timeout-minutes: 30 env: SIMPLER_SCHEDULER_TIMEOUT_MS: "5000" strategy: + fail-fast: false matrix: - os: [ubuntu-latest, macos-latest] - python-version: ['3.10'] + include: + - runner: '["self-hosted", "cpu"]' + python-version: '3.10' + - runner: '["macos-latest"]' + python-version: '3.10' steps: - name: Checkout repository @@ -273,25 +328,47 @@ jobs: - name: Set up C++ compiler run: | if [[ "${{ runner.os }}" == "Linux" ]]; then - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test - sudo apt-get update - sudo apt-get install -y ninja-build - sudo apt-get install -y g++-15 || sudo apt-get install -y g++ - if ! command -v g++-15; then sudo ln -s $(which g++) /usr/local/bin/g++-15; fi - # graphviz: required by the dep_gen smoke (deps_viewer -> dot). - # Both a2a3 and a5 sim jobs run dep_gen, so install it in both. - sudo apt-get install -y graphviz + runner_spec='${{ matrix.runner }}' + if [[ "$runner_spec" == *self-hosted* ]]; then + command -v ninja + command -v g++ + command -v dot + if ! command -v g++-15 >/dev/null 2>&1; then + mkdir -p "$RUNNER_TEMP/bin" + ln -sf "$(command -v g++)" "$RUNNER_TEMP/bin/g++-15" + echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH" + fi + else + sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test + sudo apt-get update + sudo apt-get install -y ninja-build + sudo apt-get install -y g++-15 || sudo apt-get install -y g++ + if ! command -v g++-15; then sudo ln -s $(which g++) /usr/local/bin/g++-15; fi + # graphviz: required by the dep_gen smoke (deps_viewer -> dot). + # Both a2a3 and a5 sim jobs run dep_gen, so install it in both. + sudo apt-get install -y graphviz + fi else brew install ninja brew install gcc@15 || brew install gcc brew install graphviz fi - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python ${{ matrix.python-version }} (GitHub-hosted) + if: ${{ !contains(matrix.runner, 'self-hosted') }} uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} + - name: Set up Python ${{ matrix.python-version }} (self-hosted) + if: ${{ contains(matrix.runner, 'self-hosted') }} + run: | + python3 -m venv --system-site-packages .venv + source .venv/bin/activate + pip install --upgrade pip + pip install torch --index-url https://download.pytorch.org/whl/cpu + pip install '.[test]' + - name: Cache pip packages uses: actions/cache@v5 with: @@ -301,13 +378,18 @@ jobs: ${{ runner.os }}-pip- - name: Install dependencies + if: ${{ !contains(matrix.runner, 'self-hosted') }} run: | pip install torch --index-url https://download.pytorch.org/whl/cpu pip install '.[test]' - name: Run pytest scene tests (a5sim) run: | - pytest examples tests/st --platform a5sim --device 0-15 -v \ + PYTHON=python + if [[ '${{ matrix.runner }}' == *self-hosted* ]]; then + PYTHON=.venv/bin/python + fi + "$PYTHON" -m pytest examples tests/st --platform a5sim --device 0-15 -v \ --pto-session-timeout 600 --require-pto-isa # DFX per-feature smokes — the default pytest above passes no --enable-* @@ -316,25 +398,41 @@ jobs: # CI step rather than buried in a combined run. Mirrors st-sim-a2a3. - name: dep_gen smoke run: | - pytest tests/st/a5/tensormap_and_ringbuffer/dfx/dep_gen/test_dep_gen.py \ + PYTHON=python + if [[ '${{ matrix.runner }}' == *self-hosted* ]]; then + PYTHON=.venv/bin/python + fi + "$PYTHON" -m pytest tests/st/a5/tensormap_and_ringbuffer/dfx/dep_gen/test_dep_gen.py \ --platform a5sim --device 0-15 -p no:xdist --pto-session-timeout 600 \ --require-pto-isa --enable-dep-gen - name: l2_swimlane smoke run: | - pytest tests/st/a5/tensormap_and_ringbuffer/dfx/l2_swimlane/ \ + PYTHON=python + if [[ '${{ matrix.runner }}' == *self-hosted* ]]; then + PYTHON=.venv/bin/python + fi + "$PYTHON" -m pytest tests/st/a5/tensormap_and_ringbuffer/dfx/l2_swimlane/ \ --platform a5sim --device 0-15 -p no:xdist --pto-session-timeout 600 \ --require-pto-isa --enable-l2-swimlane --enable-dep-gen - name: PMU smoke run: | - pytest tests/st/a5/tensormap_and_ringbuffer/dfx/pmu/test_pmu.py \ + PYTHON=python + if [[ '${{ matrix.runner }}' == *self-hosted* ]]; then + PYTHON=.venv/bin/python + fi + "$PYTHON" -m pytest tests/st/a5/tensormap_and_ringbuffer/dfx/pmu/test_pmu.py \ --platform a5sim --device 0-15 -p no:xdist --pto-session-timeout 600 \ --require-pto-isa --enable-pmu 2 - name: args_dump smoke run: | - pytest tests/st/a5/tensormap_and_ringbuffer/dfx/args_dump/test_args_dump.py \ + PYTHON=python + if [[ '${{ matrix.runner }}' == *self-hosted* ]]; then + PYTHON=.venv/bin/python + fi + "$PYTHON" -m pytest tests/st/a5/tensormap_and_ringbuffer/dfx/args_dump/test_args_dump.py \ --platform a5sim --device 0-15 -p no:xdist --pto-session-timeout 600 \ --require-pto-isa --dump-args diff --git a/docs/ci.md b/docs/ci.md index fc35d33aa9..02f6383f1f 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -9,7 +9,7 @@ Design principles: 1. **Merge by runner, not by language** — Python and C++ unit tests share setup cost and run as steps within a single job per runner tier (`ut`, `ut-a2a3`, `ut-a5`). 2. **Runner matches hardware tier** — no-hardware tests run on `ubuntu-latest`; platform-specific tests run on self-hosted runners with the matching label (`a2a3`, `a5`). 3. **`--platform` is the only filter** — pytest uses `--platform` + the `requires_hardware` marker; ctest uses label `-LE` exclusion. No `-m st`, no `-m "not requires_hardware"`. -4. **sim = no hardware** — `a2a3sim`/`a5sim` jobs run on github-hosted runners alongside unit tests. +4. **sim = no hardware** — `a2a3sim`/`a5sim` keep the macOS leg on GitHub-hosted runners, while the Linux leg can be routed to the local `cpu` self-hosted pool to escape the 4-vCPU ceiling. 5. **Skip irrelevant platforms for scene tests** — `detect-changes` gates `st-sim-*` and `st-onboard-*` so pure-a5 PRs skip a2a3 scene-test runs and vice versa. **UT jobs (`ut`, `ut-a2a3`, `ut-a5`) are not gated by platform** — unit tests cover shared contracts and the cost of a falsely-skipped regression outweighs the savings. 6. **Markdown-only PRs run pre-commit and nothing else** — `detect-changes` sets `docs_only` when *every* changed file ends in `.md`. That is the one case where skipping the UT jobs carries no risk: there is no code delta to regress, and markdownlint inside pre-commit is the only check that reads the files at all. @@ -30,8 +30,8 @@ PullRequest ├── packaging-matrix (ubuntu + macOS) — [skipped iff docs_only] ├── ut (ubuntu + macOS) — Python + C++ UT, no hardware [skipped iff docs_only] ├── detect-changes (ubuntu-latest) — outputs a{2a3,5}_changed + docs_only - ├── st-sim-a2a3 (ubuntu + macOS) — gated by a2a3_changed - ├── st-sim-a5 (ubuntu + macOS) — gated by a5_changed + ├── st-sim-a2a3 (cpu/Linux + macOS) — gated by a2a3_changed + ├── st-sim-a5 (cpu/Linux + macOS) — gated by a5_changed ├── ut-a2a3 (a2a3 self-hosted) — Python + C++ UT, a2a3 hardware [skipped iff docs_only] ├── st-onboard-a2a3 (a2a3 self-hosted) — gated by a2a3_changed ├── ut-a5 (a5 self-hosted) — Python + C++ UT, a5 hardware [skipped iff docs_only] @@ -41,8 +41,8 @@ PullRequest | Job | Runner | What it runs | | --- | ------ | ------------ | | `ut` | `ubuntu-latest`, `macos-latest` | `pytest tests/ut` + `ctest -LE requires_hardware` | -| `st-sim-a2a3` | `ubuntu-latest`, `macos-latest` | `pytest examples tests/st --platform a2a3sim` | -| `st-sim-a5` | `ubuntu-latest`, `macos-latest` | `pytest examples tests/st --platform a5sim` | +| `st-sim-a2a3` | `self-hosted [cpu]`, `macos-latest` | `pytest examples tests/st --platform a2a3sim` | +| `st-sim-a5` | `self-hosted [cpu]`, `macos-latest` | `pytest examples tests/st --platform a5sim` | | `ut-a2a3` | a2a3 self-hosted | `pytest tests/ut --platform a2a3` + `ctest -L "^requires_hardware(_a2a3)?$" --resource-spec-file ...` + build `tools/cann-examples/query` and run `query version` (no device) + build `tools/cann-examples/aicpu-device-query` and `tools/cann-examples/aicpu-kernel-launch` (host + cross-compiled device SO, link smoke only) | | `st-onboard-a2a3` | a2a3 self-hosted | `pytest examples tests/st --platform a2a3 --device ...` | | `ut-a5` | a5 self-hosted | `pytest tests/ut --platform a5` + `ctest -L "^requires_hardware(_a5)?$"` + build `tools/cann-examples/query` and run `query version` (no device) + build `tools/cann-examples/aicpu-device-query` and `tools/cann-examples/aicpu-kernel-launch` (link smoke only) | @@ -101,16 +101,18 @@ profiling-vs-parallelism trade-off. ### Sim jobs on CPU-constrained runners -Sim jobs (`st-sim-a2a3`, `st-sim-a5`) run on `ubuntu-latest`, whose standard -GitHub-hosted runner currently has **4 vCPUs**. `--device 0-15` is still the -right choice for the **pool size** (some L3 cases need several virtual ids), but -the default `--max-parallel auto` caps the in-flight subprocess count to -`min(nproc, len(--device))` — on a 4-core runner that becomes `4`. Note -`os.cpu_count()` reports the host's logical CPUs and ignores any cgroup CPU -quota, so this is the true core count, not a container limit. +Sim jobs (`st-sim-a2a3`, `st-sim-a5`) are no-hardware jobs. The macOS leg still +runs on GitHub-hosted runners, whose standard Linux equivalent is the +4-vCPU `ubuntu-latest` box; the Linux leg is better pointed at a local `cpu` +self-hosted runner if you want to escape that ceiling. `--device 0-15` is still +the right choice for the **pool size** (some L3 cases need several virtual ids), +but the default `--max-parallel auto` caps the in-flight subprocess count to +`min(nproc, len(--device))` — so the runner's actual core count determines the +parallelism budget. `os.cpu_count()` reports the host's logical CPUs and ignores +any cgroup CPU quota, so this is the true core count, not a container limit. ```bash -# Sim: --max-parallel auto resolves to 4 on a standard ubuntu-latest runner +# Sim on a 4-vCPU runner: --max-parallel auto resolves to 4 pytest examples tests/st --platform a2a3sim --device 0-15 # Throttle further on a CPU-starved runner: 4 concurrent cases (each forking @@ -127,7 +129,7 @@ not need `--max-parallel` manually. ### Scheduling constraints -- Sim scene tests and no-hardware unit tests run on github-hosted runners (no hardware). +- Sim scene tests and no-hardware unit tests run on no-hardware runners; the sim Linux leg is now pointed at the `cpu` self-hosted pool when available, while macOS stays GitHub-hosted. - `detect-changes` computes three flags (`a2a3_changed`, `a5_changed`, `docs_only`) from the PR diff. Each flag is `false` only when *every* changed file is in the opposite platform's tree (`src/{arch}/`, `examples/{arch}/`, `tests/{st,ut/cpp}/{arch}/`) or in the `NON_CODE` set (`docs/`, `.docs/`, `.claude/`, `.gitignore`, `.pre-commit-config.yaml`, and any `*.md` file anywhere). Anything else — shared C++ (`src/common/`), Python (`python/`, `simpler_setup/`), build files (`CMakeLists.txt`, `pyproject.toml`), shared test infra (`tests/ut/py/`, `tests/lint/`), tooling (`tools/`), or workflow files (`.github/`) — flips both flags to `true`. - **Gated jobs (scene tests only):** `st-sim-{a2a3,a5}`, `st-onboard-{a2a3,a5}` run iff their platform's flag is `true`. - **Platform-independent jobs (all UT + packaging):** `ut`, `ut-a2a3`, `ut-a5`, `packaging-matrix` ignore the platform flags — unit tests exercise shared contracts (nanobind bindings, RuntimeBuilder, ring buffers, etc.) and the risk of silently skipping a regression outweighs the CI minutes saved. The `tests/ut/cpp/{arch}/` entry in the gating regex only *attributes* an arch-specific C++ UT change to that platform (so it does not spuriously flip the other arch's scene-test flag); it does not gate the UT jobs themselves. @@ -139,7 +141,7 @@ Three hardware tiers, applied to all test categories. See [testing.md](testing.m | Tier | CI Runner | Job examples | | ---- | --------- | ------------ | -| No hardware | `ubuntu-latest` | `ut`, `st-sim-*` | +| No hardware | `ubuntu-latest`, `self-hosted [cpu]` | `ut`, `st-sim-*` | | Platform-specific (a2a3) | `[self-hosted, a2a3]` | `ut-a2a3`, `st-onboard-a2a3` | | Platform-specific (a5) | `[self-hosted, a5]` | `ut-a5`, `st-onboard-a5` | From 84ad526f052c7007728028cb4beed16bd9fc3146 Mon Sep 17 00:00:00 2001 From: MagicDoge Date: Thu, 30 Jul 2026 15:22:33 +0800 Subject: [PATCH 2/5] docs: add CI trigger note --- docs/ci.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/ci.md b/docs/ci.md index 02f6383f1f..a1e5c1dd24 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -3,6 +3,7 @@ ## Overview The CI pipeline maps test categories (st, ut-py, ut-cpp) × hardware tiers to GitHub Actions jobs. See [testing.md](testing.md) for full test organization and hardware classification. + Design principles: From 035704a1b87991c7f7b5ea86ecf5cc7c9bc7a441 Mon Sep 17 00:00:00 2001 From: MagicDoge Date: Thu, 30 Jul 2026 15:36:25 +0800 Subject: [PATCH 3/5] docs: refresh CI trigger note --- docs/ci.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ci.md b/docs/ci.md index a1e5c1dd24..acb6161a75 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -3,7 +3,7 @@ ## Overview The CI pipeline maps test categories (st, ut-py, ut-cpp) × hardware tiers to GitHub Actions jobs. See [testing.md](testing.md) for full test organization and hardware classification. - + Design principles: From b76e1d89e9409cd8860646cbaf9e102c505d0645 Mon Sep 17 00:00:00 2001 From: MagicDoge Date: Thu, 30 Jul 2026 16:21:07 +0800 Subject: [PATCH 4/5] chore: nudge CI trigger --- simpler_setup/runtime_compiler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/simpler_setup/runtime_compiler.py b/simpler_setup/runtime_compiler.py index 30f0e32703..2f4fb19567 100644 --- a/simpler_setup/runtime_compiler.py +++ b/simpler_setup/runtime_compiler.py @@ -25,6 +25,8 @@ _WORKSPACE_TRUTHY = {"1", "ON", "TRUE", "YES"} +# Keep this in sync with the CI bootstrap docs for self-hosted sim runners. + def _sdma_workspace_enabled() -> bool: """Whether the a5 PTO SDMA workspace overlay is opted in. From cdfeb1252e968e29dddb1335a4faaf4e6c6a6bc9 Mon Sep 17 00:00:00 2001 From: MagicDoge Date: Thu, 30 Jul 2026 16:26:50 +0800 Subject: [PATCH 5/5] ci: add workflow_dispatch fallback --- .github/workflows/ci.yml | 45 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18f021dd8c..be6aa9114b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,14 +7,54 @@ permissions: on: pull_request: branches: [main] + workflow_dispatch: + inputs: + base_sha: + description: "Base SHA for manual runs; defaults to merge-base with origin/main." + required: false + type: string + head_sha: + description: "Head SHA for manual runs; defaults to the checked-out commit." + required: false + type: string concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: + diff-refs: + runs-on: ubuntu-latest + outputs: + base_sha: ${{ steps.refs.outputs.base_sha }} + head_sha: ${{ steps.refs.outputs.head_sha }} + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Resolve diff refs + id: refs + run: | + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + base_sha='${{ github.event.pull_request.base.sha }}' + head_sha='${{ github.event.pull_request.head.sha }}' + elif [[ -n "${{ github.event.inputs.base_sha }}" && -n "${{ github.event.inputs.head_sha }}" ]]; then + base_sha='${{ github.event.inputs.base_sha }}' + head_sha='${{ github.event.inputs.head_sha }}' + else + git fetch --no-tags origin main + base_sha=$(git merge-base origin/main HEAD) + head_sha='${{ github.sha }}' + fi + + echo "base_sha=$base_sha" >> "$GITHUB_OUTPUT" + echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT" + # ---------- Pre-commit hooks (format, lint, clang-tidy) ---------- pre-commit: + needs: [diff-refs] runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -53,7 +93,7 @@ jobs: - name: Run pre-commit uses: pre-commit/action@v3.0.0 with: - extra_args: --from-ref ${{ github.event.pull_request.base.sha }} --to-ref ${{ github.event.pull_request.head.sha }} + extra_args: --from-ref ${{ needs['diff-refs'].outputs.base_sha }} --to-ref ${{ needs['diff-refs'].outputs.head_sha }} # ---------- Packaging matrix: 5 install paths × 4 entry points, on macOS + Ubuntu ---------- # Smoke-checks that every supported install path exposes the four user entry points. @@ -826,6 +866,7 @@ jobs: # ---------- Detect platform-specific changes (runs on GitHub server) ---------- detect-changes: + needs: [diff-refs] runs-on: ubuntu-latest outputs: a2a3_changed: ${{ steps.check.outputs.a2a3_changed }} @@ -839,7 +880,7 @@ jobs: - name: Check file changes id: check run: | - FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}) + FILES=$(git diff --name-only ${{ needs['diff-refs'].outputs.base_sha }}...${{ needs['diff-refs'].outputs.head_sha }}) NON_CODE='^(docs/|\.docs/|\.claude/|\.gitignore$|\.pre-commit-config\.yaml$)|\.md$'