Skip to content

Commit fd69244

Browse files
authored
fix(ci): improve mypyc build testing and failure detection (#461)
* fix(ci): build Windows + macOS mypyc wheels natively; harden smoke install v0.47.0 release (run 26351509901) failed every Windows + macOS test-wheels job because `build-wheels-mypyc` ran on ubuntu-latest, where cibuildwheel silently ignores `CIBW_ARCHS_WINDOWS` / `CIBW_ARCHS_MACOS`. Only Linux wheels have ever been published; the test step then fell back to PyPI (`uv pip install --find-links` permits index fallback when no local wheel matches), installed pure-Python 0.46.3, and `mypyc_smoke.py --require-compiled` (added in PR #446) correctly reported every module as interpreted and exit 1. Changes - publish.yml: split build-wheels-mypyc into a per-OS matrix (ubuntu × macos × windows) × (3.10-3.14). QEMU gated to Linux only. Linux + macOS keep three-stage PGO; macOS PGO blocks finally execute. Windows runs plain mypyc (MSVC PGO is not validated here). Artifacts now named `wheels-mypyc-<os>-py<ver>`. - publish.yml + test-build.yml test-wheels: install with `--no-index` so missing wheels fail loudly. Assert `sqlspec.__version__` matches the release tag (or pyproject.toml on PRs) before running smoke. - test-build.yml: mirror the per-OS matrix; PR-default (`test_matrix=subset`) stays ubuntu-only for fast feedback; `workflow_dispatch test_matrix=full` exercises all 15 build legs + 9 test legs. - All workflows: enable `setup-uv@v7` cache keyed on `uv.lock`. - publish.yml + test-build.yml: cache pip downloads + the pinned `HATCH_MYPYC_BUILD_DIR` per (OS, python). PGO profile data is not cached. - ci.yml: cache the pre-pulled docker image tarball (`docker save`/`docker load`) keyed on the ci.yml hash. Beads: sqlspec-tt2h (epic), sqlspec-8xun (T1) / sqlspec-1jv0 (T2) / sqlspec-2xh7 (T3) / sqlspec-qr1u (T4) / sqlspec-b5mq (T5) / sqlspec-v9mb (T6) * fix(ci): fall back to pyproject.toml for publish.yml version assert `Resolve expected version` reads `github.event.release.tag_name`, which is empty when publish.yml is triggered via workflow_dispatch. The empty value made the install string `sqlspec==` (malformed) and broke manual reruns. Fall back to the version in pyproject.toml when no release tag is present. The `release: published` path is unchanged. * ci: auto-promote test-build.yml to full matrix on build-infra PRs Subset mode (linux x py3.12) stays the default for fast PR feedback, but a preflight detect-mode job now promotes the build + test matrix to full (3 OS x 5 py builds, 3 OS x 3 py tests) when the PR diff touches any of: .github/workflows/*.yml pyproject.toml uv.lock tools/scripts/pgo_training.py tools/scripts/mypyc_smoke.py That's the exact set of files where a matrix-shape regression like the 0.47.0 release (Linux-only wheels published) could land. workflow_dispatch test_matrix=full still works as an explicit override. * fix(ci): use {project} placeholder in CIBW_BEFORE_BUILD on macOS cibuildwheel runs Linux builds inside a container that mounts the source at /project. macOS and Windows run natively on the host with no such mount, so `/project/build-constraints.txt` and `/project/tools/...` do not exist there. The macOS PGO BEFORE_BUILD block had four such literals; it was latent until the per-OS matrix split started actually executing on a macOS runner, where pip would fail with "Could not find file". Switch every `/project/` inside CIBW_BEFORE_BUILD_{LINUX,MACOS} to the cibuildwheel `{project}` placeholder, which resolves to /project inside the Linux container and to the host project path on macOS. Same change mirrored into test-build.yml. The PyPI publish URL (https://pypi.org/project/sqlspec/) is unrelated and untouched. * fix(ci): drop PGO from macOS + Windows; Linux keeps three-stage PGO macOS PGO consistently failed at `llvm-profdata merge` because pgo_training.py's workloads report "filters not installed" (the script's optional filter deps aren't present in the cibuildwheel BEFORE_BUILD env) → instrumented binary never emits .profraw files → merge has no input. Rather than debug the filter import path, drop PGO from macOS + Windows: both now use the base CIBW_BEFORE_BUILD (install hatch-mypyc + hatchling) and a plain mypyc env with HATCH_BUILD_HOOKS_ENABLE + MYPYC_* flags. macOS still pins HATCH_MYPYC_BUILD_DIR so the mypyc compile cache (actions/cache, T5) still hits across runs. Linux retains the full three-stage PGO build unchanged — that's where the perf benefit measured in CI is and stays. * fix(ci): unpin macOS mypyc build dir; pre-warm Defender on Windows macOS: hatch_mypyc.plugin.initialize() calls os.mkdir(BUILD_DIR/build) — not os.makedirs — so the parent must already exist. The Linux PGO BEFORE_BUILD creates it explicitly; the (now removed) macOS PGO BEFORE_BUILD did the same. With both gone, pinning HATCH_MYPYC_BUILD_DIR=/tmp/sqlspec-mypyc-build pointed hatch-mypyc at a missing parent and the build crashed with FileNotFoundError. Unpin the var — hatch-mypyc falls back to its default per-project temp dir, which it creates as needed. Also drop macOS from the actions/cache step since the pinned path is no longer used there. Windows: virtualenv's interpreter discovery times out at ~10s on the freshly extracted nuget-cpython python.exe because Windows Defender real-time protection scans the binary on its first invocation. Add Defender exclusions for cibuildwheel's cache dir, the runner temp dir, and python.exe via a Windows-only pre-step. -ErrorAction SilentlyContinue so future tamper-protection changes don't break the job. * ci: trim mypyc wheel matrix to python 3.12 across all 3 OSes 15 build legs (3 OS × 5 py) was too much CI footprint for an initial release that's first turning on Windows + macOS. Trim to 3 build legs (linux py3.12, macos py3.12, windows py3.12) + 3 test legs. Users on other Python versions install the pure-Python sqlspec-*-py3-none-any.whl fallback that build-wheels-standard still produces. Same trim mirrored into test-build.yml (full mode is now identical to subset's py3.12 baseline for python-version; only the os list differs). Expand back to 3.10–3.14 once py3.12 is known stable across all 3 OSes. * ci: split matrices — full sweep on release, single-py on PR (configurable) publish.yml (release): Restore full sweep — 3 OS × 5 py for builds (3.10–3.14), 3 OS × 3 py for test-wheels (3.10/3.12/3.14 sampling matches the pre-trim shape). Every published release exercises every supported Python. test-build.yml (PR / workflow_dispatch): Stay on a single Python version per run (default 3.12). Add a new `python_version` workflow_dispatch input with a choice of 3.10–3.14 for manual overrides when you want to validate a specific interpreter. Build + test matrices read the input, falling back to '3.12' for pull_request triggers (where workflow_dispatch inputs are empty). detect-mode's OS auto-promotion (linux subset vs full 3-OS) is unchanged — orthogonal to the python-version choice. * ci: cancel in-flight runs on the same ref to stop wasting runner minutes Pre-existing gap (main never had concurrency either): pushing a new commit to a PR branch left the previous Tests And Linting + Test Build Configuration runs queued/running alongside the new ones. With several quick pushes during a debugging session that compounded into 3–5 parallel runs per workflow per branch. Add concurrency: { group: ${{ github.workflow }}-${{ github.ref }}, cancel-in-progress: true } to ci.yml, test-build.yml, docs.yml. NOT applied to publish.yml: a release upload mid-cancel could leave PyPI in a partial state (some wheels uploaded, others not). Releases stay all-or-nothing as before — see the `needs:` chain on publish-release. pgo-validate.yml and pr-title.yml left alone too (manual-only / quick lint, low impact). * ci(windows): exclude wheelhouse + workspace from Defender Defender's real-time scan was holding the freshly-built .whl file handle when actions/upload-artifact@v7 opened it for chunked upload, causing the step to silently terminate after pre-flight validation with no error logged. macOS used the same action version and succeeded. Extend the pre-warm exclusion list to cover GITHUB_WORKSPACE and the wheelhouse output directory so the artifact file isn't being scanned when the action reads it. * ci(test-build): drop aarch64 from PR validation matrix aarch64 cross-build under QEMU + PGO three-stage hangs for >1h per leg on PR runners. publish.yml already covers the full cross-arch matrix at release time, so PR validation is pure duplication. Strip QEMU setup and force host-arch-only builds: x86_64 on Linux, arm64 on macos-latest (now Apple Silicon native), AMD64 on Windows. * ci(publish): build aarch64 on native ARM runner instead of QEMU The aarch64 cross-build under QEMU + PGO three-stage hangs for hours per leg, putting every Linux publish job at risk of the 6-hour cap. GitHub now offers ubuntu-24.04-arm (native ARM runner, free for public repos), making QEMU emulation unnecessary. Add ubuntu-24.04-arm to the build matrix and make CIBW_ARCHS_LINUX conditional on which runner is executing. Drop the QEMU setup step. Drop macOS x86_64 (macos-latest is Apple Silicon native; x86_64 would need Rosetta and isn't worth the build minutes — pure-Python wheel covers Intel Macs). Add ubuntu-24.04-arm to test-wheels so the ARM wheel is actually validated before publish. Build matrix grows from 15 → 20 legs but each leg now runs at native speed (~10–20 min) instead of QEMU-emulated hours. * ci(test-wheels): resolve transitive deps from PyPI Splitting the install into two steps preserves the no-silent-fallback guarantee for sqlspec itself while letting runtime deps like mypy-extensions, sqlglot, etc. resolve from PyPI. Step 1: --no-index --no-deps --find-links — sqlspec wheel MUST come from the local artifact bundle (this is the safety check). Step 2: same install without --no-index — fills in transitive runtime deps from PyPI; sqlspec itself is already installed and won't reresolve.
1 parent af9c0ab commit fd69244

4 files changed

Lines changed: 302 additions & 74 deletions

File tree

.github/workflows/ci.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ on:
66
branches:
77
- main
88

9+
# Cancel in-flight runs of this workflow on the same ref when a newer
10+
# commit lands. Saves runner minutes during a churn of PR pushes. Not
11+
# applied to publish.yml — releases must never be cancelled mid-upload.
12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.ref }}
14+
cancel-in-progress: true
15+
916
jobs:
1017
validate:
1118
runs-on: ubuntu-latest
@@ -14,6 +21,9 @@ jobs:
1421

1522
- name: Install uv
1623
uses: astral-sh/setup-uv@v7
24+
with:
25+
enable-cache: true
26+
cache-dependency-glob: uv.lock
1727

1828
- name: Set up Python
1929
run: uv python install 3.11
@@ -41,6 +51,9 @@ jobs:
4151

4252
- name: Install uv
4353
uses: astral-sh/setup-uv@v7
54+
with:
55+
enable-cache: true
56+
cache-dependency-glob: uv.lock
4457

4558
- name: Set up Python
4659
run: uv python install 3.11
@@ -58,6 +71,9 @@ jobs:
5871

5972
- name: Install uv
6073
uses: astral-sh/setup-uv@v7
74+
with:
75+
enable-cache: true
76+
cache-dependency-glob: uv.lock
6177

6278
- name: Set up Python
6379
run: uv python install 3.11
@@ -75,6 +91,9 @@ jobs:
7591

7692
- name: Install uv
7793
uses: astral-sh/setup-uv@v7
94+
with:
95+
enable-cache: true
96+
cache-dependency-glob: uv.lock
7897

7998
- name: Set up Python
8099
run: uv python install 3.11
@@ -98,14 +117,29 @@ jobs:
98117

99118
- name: Install uv
100119
uses: astral-sh/setup-uv@v7
120+
with:
121+
enable-cache: true
122+
cache-dependency-glob: uv.lock
101123

102124
- name: Set up Python
103125
run: uv python install ${{ matrix.python-version }}
104126

105127
- name: Install dependencies
106128
run: uv sync --all-extras --dev
107129

130+
- name: Cache docker images
131+
id: docker-image-cache
132+
uses: actions/cache@v5
133+
with:
134+
path: /tmp/docker-images
135+
key: docker-images-v1-${{ hashFiles('.github/workflows/ci.yml') }}
136+
137+
- name: Load cached docker images
138+
if: steps.docker-image-cache.outputs.cache-hit == 'true'
139+
run: docker load -i /tmp/docker-images/sqlspec-test-images.tar
140+
108141
- name: Pre-pull docker images
142+
if: steps.docker-image-cache.outputs.cache-hit != 'true'
109143
run: |
110144
set -eu
111145
images=(
@@ -121,6 +155,8 @@ jobs:
121155
rustfs/rc:latest
122156
)
123157
printf '%s\n' "${images[@]}" | xargs -P 4 -I{} docker pull {}
158+
mkdir -p /tmp/docker-images
159+
docker save "${images[@]}" -o /tmp/docker-images/sqlspec-test-images.tar
124160
125161
- name: Test
126162
id: pytest
@@ -230,6 +266,9 @@ jobs:
230266

231267
- name: Install uv
232268
uses: astral-sh/setup-uv@v7
269+
with:
270+
enable-cache: true
271+
cache-dependency-glob: uv.lock
233272

234273
- name: Set up Python
235274
run: uv python install 3.12

.github/workflows/docs.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ on:
88
permissions:
99
contents: read
1010

11+
# Cancel in-flight runs of this workflow on the same ref when a newer
12+
# commit lands. Saves runner minutes during a churn of PR pushes. Not
13+
# applied to publish.yml — releases must never be cancelled mid-upload.
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.ref }}
16+
cancel-in-progress: true
17+
1118
jobs:
1219
build:
1320
runs-on: ubuntu-latest
@@ -16,6 +23,9 @@ jobs:
1623

1724
- name: Install uv
1825
uses: astral-sh/setup-uv@v7
26+
with:
27+
enable-cache: true
28+
cache-dependency-glob: uv.lock
1929

2030
- name: Set up Python
2131
run: uv python install 3.12

.github/workflows/publish.yml

Lines changed: 101 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ jobs:
1515

1616
- name: Install uv
1717
uses: astral-sh/setup-uv@v7
18+
with:
19+
enable-cache: true
20+
cache-dependency-glob: uv.lock
1821

1922
- name: Set up Python
2023
run: uv python install 3.12
@@ -40,6 +43,9 @@ jobs:
4043

4144
- name: Install uv
4245
uses: astral-sh/setup-uv@v7
46+
with:
47+
enable-cache: true
48+
cache-dependency-glob: uv.lock
4349

4450
- name: Set up Python 3.12
4551
run: uv python install 3.12
@@ -57,65 +63,97 @@ jobs:
5763
path: dist/*.whl
5864

5965
build-wheels-mypyc:
60-
name: Build MyPyC+PGO wheels for all platforms
61-
runs-on: ubuntu-latest
66+
name: Build MyPyC wheels (${{ matrix.os }} py${{ matrix.python-version }})
67+
runs-on: ${{ matrix.os }}
6268
strategy:
69+
fail-fast: false
6370
matrix:
71+
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest]
6472
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
6573
steps:
6674
- name: Check out repository
6775
uses: actions/checkout@v6
6876

69-
- name: Set up QEMU
70-
uses: docker/setup-qemu-action@v4
71-
with:
72-
platforms: all
73-
7477
- name: Set up Python
7578
uses: actions/setup-python@v6
7679
with:
7780
python-version: "3.12"
7881

7982
- name: Install uv
8083
uses: astral-sh/setup-uv@v7
84+
with:
85+
enable-cache: true
86+
cache-dependency-glob: uv.lock
8187

8288
- name: Export build constraints from uv.lock
8389
run: uv export --frozen --no-emit-project --no-hashes --format requirements.txt --output-file build-constraints.txt
8490

91+
- name: Cache pip downloads
92+
uses: actions/cache@v5
93+
with:
94+
path: |
95+
~/.cache/pip
96+
~/Library/Caches/pip
97+
~\AppData\Local\pip\Cache
98+
key: pip-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('build-constraints.txt') }}
99+
restore-keys: |
100+
pip-${{ runner.os }}-py${{ matrix.python-version }}-
101+
102+
# mypyc compile cache only useful on PGO hosts where HATCH_MYPYC_BUILD_DIR
103+
# is pinned. Windows + macOS build without PGO and without a pinned build dir.
104+
- name: Cache mypyc build dir
105+
if: startsWith(matrix.os, 'ubuntu')
106+
uses: actions/cache@v5
107+
with:
108+
path: /tmp/sqlspec-mypyc-build
109+
key: mypyc-${{ matrix.os }}-py${{ matrix.python-version }}-${{ hashFiles('sqlspec/**/*.py', 'pyproject.toml') }}
110+
restore-keys: |
111+
mypyc-${{ matrix.os }}-py${{ matrix.python-version }}-
112+
113+
- name: Pre-warm Windows Defender exclusions
114+
if: matrix.os == 'windows-latest'
115+
shell: pwsh
116+
run: |
117+
# Defender's real-time scan on the freshly-extracted nuget-cpython
118+
# python.exe blocks virtualenv discovery past its 10s timeout.
119+
Add-MpPreference -ExclusionPath "$env:LOCALAPPDATA\pypa\cibuildwheel" -ErrorAction SilentlyContinue
120+
Add-MpPreference -ExclusionPath "$env:TEMP" -ErrorAction SilentlyContinue
121+
Add-MpPreference -ExclusionPath "$env:GITHUB_WORKSPACE" -ErrorAction SilentlyContinue
122+
Add-MpPreference -ExclusionPath "$env:GITHUB_WORKSPACE\wheelhouse" -ErrorAction SilentlyContinue
123+
Add-MpPreference -ExclusionProcess "python.exe" -ErrorAction SilentlyContinue
124+
85125
- name: Install cibuildwheel
86126
run: python -m pip install cibuildwheel
87127

88128
- name: Build wheels with cibuildwheel
89129
run: python -m cibuildwheel --output-dir wheelhouse
90130
env:
91-
# Configure cibuildwheel
92131
CIBW_BUILD: "cp${{ matrix.python-version == '3.10' && '310' || matrix.python-version == '3.11' && '311' || matrix.python-version == '3.12' && '312' || matrix.python-version == '3.13' && '313' || matrix.python-version == '3.14' && '314' }}-*"
93132
CIBW_BUILD_VERBOSITY: 1
94133

95-
# Platform configuration - comprehensive coverage with QEMU emulation
96-
CIBW_ARCHS_LINUX: "x86_64 aarch64"
97-
CIBW_ARCHS_MACOS: "x86_64 arm64"
134+
# Native host arch only — Linux aarch64 builds on ubuntu-24.04-arm,
135+
# macOS arm64 on macos-latest (Apple Silicon). No QEMU emulation.
136+
CIBW_ARCHS_LINUX: ${{ matrix.os == 'ubuntu-24.04-arm' && 'aarch64' || 'x86_64' }}
137+
CIBW_ARCHS_MACOS: "arm64"
98138
CIBW_ARCHS_WINDOWS: "AMD64"
99139

100-
# Skip problematic combinations
101140
CIBW_SKIP: "cp39-win_arm64 *-musllinux*"
102141

103-
# Install build dependencies (base, overridden per-platform below).
104-
# build-constraints.txt is produced from uv.lock so hatch-mypyc / mypy / hatchling
105-
# versions match the locked dev environment and can't drift on each cibw run.
106-
CIBW_BEFORE_BUILD: "pip install -c /project/build-constraints.txt hatch-mypyc hatchling"
142+
# Base build deps; PGO blocks below override on Linux + macOS.
143+
# build-constraints.txt is produced from uv.lock so hatch-mypyc / mypy /
144+
# hatchling versions track the locked dev environment.
145+
CIBW_BEFORE_BUILD: "pip install -c {project}/build-constraints.txt hatch-mypyc hatchling"
107146

108-
# Test the built wheels
109147
CIBW_TEST_REQUIRES: "cloud-sql-python-connector google-cloud-alloydb-connector"
110148
CIBW_TEST_COMMAND: >-
111-
python /project/tools/scripts/mypyc_smoke.py --require-compiled
149+
python {project}/tools/scripts/mypyc_smoke.py --require-compiled
112150
113151
# ── Linux (GCC): PGO three-stage build ──
114152
# Stage 1 (instrumented build) + Stage 2 (training) run in BEFORE_BUILD.
115153
# Stage 3 (optimized build) is the main cibuildwheel build with -fprofile-use.
116154
# HATCH_MYPYC_BUILD_DIR pins the build directory so profile data paths match.
117155
CIBW_BEFORE_BUILD_LINUX: >-
118-
pip install -c /project/build-constraints.txt hatch-mypyc hatchling &&
156+
pip install -c {project}/build-constraints.txt hatch-mypyc hatchling &&
119157
PGO_DIR=/tmp/sqlspec-pgo &&
120158
BUILD_DIR=/tmp/sqlspec-mypyc-build &&
121159
mkdir -p "$PGO_DIR" "$BUILD_DIR" &&
@@ -125,7 +163,7 @@ jobs:
125163
MYPYC_OPT_LEVEL=3 MYPYC_DEBUG_LEVEL=0 MYPYC_MULTI_FILE=1
126164
pip install {package} --no-build-isolation &&
127165
pip install anyio pyarrow aiosqlite duckdb adbc-driver-sqlite adbc-driver-manager &&
128-
python /project/tools/scripts/pgo_training.py &&
166+
python {project}/tools/scripts/pgo_training.py &&
129167
pip uninstall -y sqlspec &&
130168
rm -rf "$BUILD_DIR/build" "$BUILD_DIR/tmp"
131169
@@ -135,57 +173,62 @@ jobs:
135173
MYPYC_OPT_LEVEL=3 MYPYC_DEBUG_LEVEL=0 MYPYC_MULTI_FILE=1
136174
CFLAGS="-fprofile-use=/tmp/sqlspec-pgo -fprofile-correction -Wno-error=missing-profile -Wno-error=coverage-mismatch"
137175
138-
# ── macOS (Clang): PGO three-stage build ──
139-
CIBW_BEFORE_BUILD_MACOS: >-
140-
pip install -c /project/build-constraints.txt hatch-mypyc hatchling &&
141-
PGO_DIR=/tmp/sqlspec-pgo &&
142-
BUILD_DIR=/tmp/sqlspec-mypyc-build &&
143-
mkdir -p "$PGO_DIR" "$BUILD_DIR" &&
144-
CFLAGS="-fprofile-instr-generate=$PGO_DIR/default_%m.profraw"
145-
HATCH_BUILD_HOOKS_ENABLE=1
146-
HATCH_MYPYC_BUILD_DIR="$BUILD_DIR"
147-
MYPYC_OPT_LEVEL=3 MYPYC_DEBUG_LEVEL=0 MYPYC_MULTI_FILE=1
148-
pip install {package} --no-build-isolation &&
149-
pip install anyio pyarrow aiosqlite duckdb adbc-driver-sqlite adbc-driver-manager &&
150-
python /project/tools/scripts/pgo_training.py &&
151-
xcrun llvm-profdata merge -o "$PGO_DIR/merged.profdata" "$PGO_DIR"/*.profraw &&
152-
pip uninstall -y sqlspec &&
153-
rm -rf "$BUILD_DIR/build" "$BUILD_DIR/tmp"
154-
176+
# ── macOS (Clang): plain mypyc, no PGO ──
177+
# PGO was attempted but pgo_training.py needs optional filter deps
178+
# that are absent in the cibuildwheel BEFORE_BUILD env (every workload
179+
# logs "skipped (filters not installed)" → no .profraw files emitted
180+
# → llvm-profdata merge fails). Falls back to the base CIBW_BEFORE_BUILD,
181+
# which just installs hatch-mypyc + hatchling and lets cibuildwheel
182+
# run the normal frontend build with the mypyc env below.
155183
CIBW_ENVIRONMENT_MACOS: >-
156184
HATCH_BUILD_HOOKS_ENABLE=1
157-
HATCH_MYPYC_BUILD_DIR=/tmp/sqlspec-mypyc-build
158185
MYPYC_OPT_LEVEL=3 MYPYC_DEBUG_LEVEL=0 MYPYC_MULTI_FILE=1
159-
CFLAGS="-fprofile-instr-use=/tmp/sqlspec-pgo/merged.profdata"
160186
161-
# ── Windows (MSVC): No PGO, standard mypyc build ──
187+
# ── Windows (MSVC): plain mypyc, no PGO ──
162188
CIBW_ENVIRONMENT_WINDOWS: >-
163189
HATCH_BUILD_HOOKS_ENABLE=1
164190
MYPYC_OPT_LEVEL=3 MYPYC_DEBUG_LEVEL=0 MYPYC_MULTI_FILE=1
165191
166192
- name: Upload wheel artifacts
167193
uses: actions/upload-artifact@v7
168194
with:
169-
name: wheels-mypyc-py${{ matrix.python-version }}
195+
name: wheels-mypyc-${{ matrix.os }}-py${{ matrix.python-version }}
170196
path: wheelhouse/*.whl
171197

172-
173198
test-wheels:
174199
name: Test ${{ matrix.os }} py${{ matrix.python-version }}
175200
needs: [build-wheels-standard, build-wheels-mypyc]
176201
strategy:
177202
fail-fast: false
178203
matrix:
179-
os: [ubuntu-latest, windows-latest, macos-latest]
204+
os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest]
180205
python-version: ["3.10", "3.12", "3.14"]
181206

182207
runs-on: ${{ matrix.os }}
183208
steps:
184209
- name: Check out repository
185210
uses: actions/checkout@v6
186211

212+
# Route untrusted release tag through an env var (not direct ${{ … }}
213+
# interpolation into the shell) to avoid command injection. Fall back
214+
# to pyproject.toml when triggered via workflow_dispatch (no release
215+
# context) so manual reruns still have a version to assert against.
216+
- name: Resolve expected version
217+
shell: bash
218+
env:
219+
RELEASE_TAG: ${{ github.event.release.tag_name }}
220+
run: |
221+
tag="${RELEASE_TAG#v}"
222+
if [ -z "$tag" ]; then
223+
tag=$(grep -m1 '^version = ' pyproject.toml | sed -E 's/version = "(.*)"/\1/')
224+
fi
225+
echo "SQLSPEC_EXPECTED_VERSION=$tag" >> "$GITHUB_ENV"
226+
187227
- name: Install uv
188228
uses: astral-sh/setup-uv@v7
229+
with:
230+
enable-cache: true
231+
cache-dependency-glob: uv.lock
189232

190233
- name: Set up Python ${{ matrix.python-version }}
191234
run: uv python install ${{ matrix.python-version }}
@@ -203,16 +246,26 @@ jobs:
203246
merge-multiple: true
204247
path: dist-mypyc/
205248

249+
# Install sqlspec from local wheels only (--no-index + --no-deps) so any
250+
# missing host wheel fails loudly instead of silently grabbing a stale
251+
# PyPI release. Transitive deps resolve from PyPI in a separate step.
252+
# The version assert catches drift between pyproject.toml and the wheel.
206253
- name: Test standard wheel installation
254+
shell: bash
207255
run: |
208256
uv venv test-standard --python ${{ matrix.python-version }}
209-
uv pip install --python test-standard --find-links dist-standard/ sqlspec
257+
uv pip install --python test-standard --no-index --no-deps --find-links dist-standard/ "sqlspec==${SQLSPEC_EXPECTED_VERSION}"
258+
uv pip install --python test-standard "sqlspec==${SQLSPEC_EXPECTED_VERSION}" --find-links dist-standard/
259+
uv run --no-project --python test-standard python -I -c "import sqlspec, os, sys; v=sqlspec.__version__; e=os.environ['SQLSPEC_EXPECTED_VERSION']; sys.exit(0 if v == e else (sys.stderr.write(f'version mismatch: got {v}, expected {e}\n') or 1))"
210260
uv run --no-project --python test-standard python -I -c "import sqlspec; print('Standard wheel OK')"
211261
212262
- name: Test mypyc wheel installation
263+
shell: bash
213264
run: |
214265
uv venv test-mypyc --python ${{ matrix.python-version }}
215-
uv pip install --python test-mypyc --find-links dist-mypyc/ sqlspec
266+
uv pip install --python test-mypyc --no-index --no-deps --find-links dist-mypyc/ "sqlspec==${SQLSPEC_EXPECTED_VERSION}"
267+
uv pip install --python test-mypyc "sqlspec==${SQLSPEC_EXPECTED_VERSION}" --find-links dist-mypyc/
268+
uv run --no-project --python test-mypyc python -I -c "import sqlspec, os, sys; v=sqlspec.__version__; e=os.environ['SQLSPEC_EXPECTED_VERSION']; sys.exit(0 if v == e else (sys.stderr.write(f'version mismatch: got {v}, expected {e}\n') or 1))"
216269
uv run --no-project --python test-mypyc python -I tools/scripts/mypyc_smoke.py --require-compiled
217270
218271
publish-release:
@@ -228,6 +281,9 @@ jobs:
228281
steps:
229282
- name: Install uv
230283
uses: astral-sh/setup-uv@v7
284+
with:
285+
enable-cache: true
286+
cache-dependency-glob: uv.lock
231287

232288
- name: Download all artifacts
233289
uses: actions/download-artifact@v7

0 commit comments

Comments
 (0)