From 4c1228eec81982c600f4115265a1c29d466d11f2 Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Wed, 27 May 2026 11:56:24 -0500 Subject: [PATCH 01/28] Pin componentize-py git dependency to resolved commit SHA Pins to the commit already resolved in Cargo.lock (81d582a) so that future builds cannot silently pull in upstream HEAD changes. --- crates/fastly-compute-py/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/fastly-compute-py/Cargo.toml b/crates/fastly-compute-py/Cargo.toml index fdafb41..707badf 100644 --- a/crates/fastly-compute-py/Cargo.toml +++ b/crates/fastly-compute-py/Cargo.toml @@ -25,7 +25,7 @@ wit-parser = "0.244" wit-component = "0.244" wasm-metadata = { version = "0.245", default-features = false } tempfile = "3" -componentize-py = { git = "https://github.com/bytecodealliance/componentize-py" } +componentize-py = { git = "https://github.com/bytecodealliance/componentize-py", rev = "81d582a2333198cff13c0b56de35d72c25a652e7" } futures = { version = "0.3", default-features = false, features = ["executor"] } pyo3 = { version = "0.27.2", optional = true } serde = { version = "1.0", features = ["derive"] } From d19f618bc3eef12240e6219f5b90599d249de028 Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Wed, 27 May 2026 11:56:37 -0500 Subject: [PATCH 02/28] Add release workflow for multi-platform binary wheels Builds manylinux_2_28 wheels for Linux x86_64 and aarch64 (via maturin-action cross-compilation containers), native wheels for macOS x86_64 (macos-13) and aarch64 (macos-14), and a Windows x86_64 wheel. On tag push (v*) a pre-release GitHub Release is created with all wheels attached as assets so they can be installed and validated before PyPI integration. On workflow_dispatch the wheels are left as Actions artifacts only. Trusted publishing will be wired up as a follow-on step. --- .github/workflows/release.yml | 272 ++++++++++++++++++++++++++++ Cargo.lock | 51 ++---- crates/fastly-compute-py/Cargo.toml | 2 +- crates/fastly-compute-py/build.rs | 6 + 4 files changed, 291 insertions(+), 40 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ab18fdb --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,272 @@ +name: Release + +# Triggers: +# - Push a version tag (e.g. git tag v0.1.0 && git push --tags) for a real release. +# - workflow_dispatch for ad-hoc test builds without creating a tag. +# Wheels are uploaded as GitHub Actions artifacts and no GitHub Release is created. +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + version: + description: "Version label for artifacts (e.g. v0.1.0-test)" + required: true + default: "v0.0.0-dev" + +# Minimal permissions by default; create-github-release job adds write where needed. +permissions: + contents: read + +jobs: + # --------------------------------------------------------------------------- + # Linux wheels — built inside manylinux_2_28 containers via maturin-action. + # Cross-compilation for aarch64 is handled by the action's built-in container; + # no QEMU or explicit cross-toolchain setup is needed. + # --------------------------------------------------------------------------- + build-linux: + name: "Linux ${{ matrix.target }}" + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-unknown-linux-gnu + manylinux: "2_28" + - target: aarch64-unknown-linux-gnu + manylinux: "2_28" + + steps: + - uses: actions/checkout@v4 + + # maturin-action runs inside a manylinux container for Linux builds. + # We need both stable (for the extension) and nightly (for wasiless / + # componentize-py which requires wasm32-wasip1 + rust-src). + # The before-script-linux hook runs inside the container before maturin. + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + maturin-version: "v1.11.5" + target: ${{ matrix.target }} + manylinux: ${{ matrix.manylinux }} + rust-toolchain: "1.92.0" + rustup-components: "rustfmt,clippy" + args: --release --locked -i python3.12 --compatibility pypi + # Install nightly + wasm targets inside the container before the build. + before-script-linux: | + rustup toolchain install nightly --component rust-src + rustup target add wasm32-wasip1 --toolchain nightly + rustup target add wasm32-unknown-unknown + + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-linux-${{ matrix.target }} + path: target/wheels/*.whl + if-no-files-found: error + + # --------------------------------------------------------------------------- + # macOS wheels — native builds on GitHub-hosted runners. + # macos-14 is Apple Silicon (aarch64); macos-13 is Intel (x86_64). + # Using native runners avoids cross-compilation complexity entirely. + # --------------------------------------------------------------------------- + build-macos: + name: "macOS ${{ matrix.target }}" + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-apple-darwin + runner: macos-13 + - target: aarch64-apple-darwin + runner: macos-14 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: "1.92.0" + target: wasm32-unknown-unknown + components: rustfmt,clippy + cache: false + + - name: Set up nightly Rust toolchain with rust-src + run: | + rustup toolchain install nightly --component rust-src + rustup target add wasm32-wasip1 --toolchain nightly + + # Cache Rust registry/git sources — keyed on Cargo.lock only so they + # are shared across runs with the same lockfile. + - name: Cache Rust sources + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + ~/.cargo/git/checkouts/ + key: cargo-sources-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-v1 + restore-keys: | + cargo-sources-${{ runner.os }}- + + # Build artifacts keyed on Cargo.lock + toolchain to avoid incompatible + # artifact reuse across toolchain upgrades. + - name: Cache Rust build artifacts + uses: actions/cache@v4 + with: + path: target/ + key: cargo-target-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}-1.92.0-v1 + restore-keys: | + cargo-target-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}- + cargo-target-${{ runner.os }}-${{ matrix.target }}- + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + maturin-version: "v1.11.5" + target: ${{ matrix.target }} + # container: off is implied for non-Linux but set explicitly for clarity. + container: "off" + args: --release --locked -i python3.12 + + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-macos-${{ matrix.target }} + path: target/wheels/*.whl + if-no-files-found: error + + # --------------------------------------------------------------------------- + # Windows wheel — native build on windows-latest (x86_64). + # Windows support is currently untested; this is a hard failure intentionally + # so that any Windows-specific build issues surface immediately. + # --------------------------------------------------------------------------- + build-windows: + name: "Windows x86_64" + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: "1.92.0" + target: wasm32-unknown-unknown + components: rustfmt,clippy + cache: false + + - name: Set up nightly Rust toolchain with rust-src + run: | + rustup toolchain install nightly --component rust-src + rustup target add wasm32-wasip1 --toolchain nightly + + - name: Cache Rust sources + uses: actions/cache@v4 + with: + path: | + ~\.cargo\registry\index\ + ~\.cargo\registry\cache\ + ~\.cargo\git\db\ + ~\.cargo\git\checkouts\ + key: cargo-sources-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-v1 + restore-keys: | + cargo-sources-${{ runner.os }}- + + - name: Cache Rust build artifacts + uses: actions/cache@v4 + with: + path: target\ + key: cargo-target-${{ runner.os }}-x86_64-unknown-windows-msvc-${{ hashFiles('**/Cargo.lock') }}-1.92.0-v1 + restore-keys: | + cargo-target-${{ runner.os }}-x86_64-unknown-windows-msvc-${{ hashFiles('**/Cargo.lock') }}- + cargo-target-${{ runner.os }}-x86_64-unknown-windows-msvc- + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + maturin-version: "v1.11.5" + target: x86_64-pc-windows-msvc + container: "off" + args: --release --locked -i python3.12 + + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-windows-x86_64 + path: target/wheels/*.whl + if-no-files-found: error + + # --------------------------------------------------------------------------- + # Collect all wheels into a single artifact for convenient download. + # --------------------------------------------------------------------------- + collect-wheels: + name: "Collect all wheels" + needs: [build-linux, build-macos, build-windows] + runs-on: ubuntu-24.04 + + steps: + - name: Download all wheel artifacts + uses: actions/download-artifact@v4 + with: + pattern: wheels-* + path: dist/ + merge-multiple: true + + - name: List collected wheels + run: ls -lh dist/ + + - name: Upload combined artifact + uses: actions/upload-artifact@v4 + with: + name: all-wheels + path: dist/*.whl + if-no-files-found: error + + # --------------------------------------------------------------------------- + # Create a GitHub Release and attach all wheels. + # Only runs on tag pushes — workflow_dispatch builds stop at collect-wheels, + # leaving wheels as Actions artifacts for download and local testing. + # --------------------------------------------------------------------------- + create-github-release: + name: "Create GitHub Release" + needs: [collect-wheels] + runs-on: ubuntu-24.04 + # Only create a release for tag-triggered runs. + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + permissions: + contents: write # Required to create releases and upload assets. + + steps: + - uses: actions/checkout@v4 + + - name: Download all wheels + uses: actions/download-artifact@v4 + with: + name: all-wheels + path: dist/ + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ github.token }} + run: | + TAG="${{ github.ref_name }}" + gh release create "$TAG" dist/*.whl \ + --title "$TAG" \ + --prerelease \ + --generate-notes \ + --notes-start-tag "$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo '')" diff --git a/Cargo.lock b/Cargo.lock index 63ed84c..ad317ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -485,7 +485,7 @@ dependencies = [ [[package]] name = "componentize-py" version = "0.22.1" -source = "git+https://github.com/bytecodealliance/componentize-py#81d582a2333198cff13c0b56de35d72c25a652e7" +source = "git+https://github.com/bytecodealliance/componentize-py?rev=81d582a2333198cff13c0b56de35d72c25a652e7#81d582a2333198cff13c0b56de35d72c25a652e7" dependencies = [ "anyhow", "async-trait", @@ -1476,15 +1476,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "indoc" -version = "2.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" -dependencies = [ - "rustversion", -] - [[package]] name = "io-extras" version = "0.18.4" @@ -1750,15 +1741,6 @@ dependencies = [ "rustix 1.1.4", ] -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - [[package]] name = "miette" version = "7.6.0" @@ -1988,35 +1970,32 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.27.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab53c047fcd1a1d2a8820fe84f05d6be69e9526be40cb03b73f86b6b03e6d87d" +checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" dependencies = [ - "indoc", "libc", - "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", - "unindent", ] [[package]] name = "pyo3-build-config" -version = "0.27.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b455933107de8642b4487ed26d912c2d899dec6114884214a0b3bb3be9261ea6" +checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" dependencies = [ "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.27.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c85c9cbfaddf651b1221594209aed57e9e5cff63c4d11d1feead529b872a089" +checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" dependencies = [ "libc", "pyo3-build-config", @@ -2024,9 +2003,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.27.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a5b10c9bf9888125d917fb4d2ca2d25c8df94c7ab5a52e13313a07e050a3b02" +checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -2036,9 +2015,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.27.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b51720d314836e53327f5871d4c0cfb4fb37cc2c4a11cc71907a86342c40f9" +checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" dependencies = [ "heck", "proc-macro2", @@ -2720,7 +2699,7 @@ dependencies = [ [[package]] name = "test-generator" version = "0.1.0" -source = "git+https://github.com/bytecodealliance/componentize-py#81d582a2333198cff13c0b56de35d72c25a652e7" +source = "git+https://github.com/bytecodealliance/componentize-py?rev=81d582a2333198cff13c0b56de35d72c25a652e7#81d582a2333198cff13c0b56de35d72c25a652e7" dependencies = [ "anyhow", "getrandom 0.2.17", @@ -3051,12 +3030,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "unindent" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" - [[package]] name = "unsafe-libyaml" version = "0.2.11" diff --git a/crates/fastly-compute-py/Cargo.toml b/crates/fastly-compute-py/Cargo.toml index 707badf..a7c824c 100644 --- a/crates/fastly-compute-py/Cargo.toml +++ b/crates/fastly-compute-py/Cargo.toml @@ -27,7 +27,7 @@ wasm-metadata = { version = "0.245", default-features = false } tempfile = "3" componentize-py = { git = "https://github.com/bytecodealliance/componentize-py", rev = "81d582a2333198cff13c0b56de35d72c25a652e7" } futures = { version = "0.3", default-features = false, features = ["executor"] } -pyo3 = { version = "0.27.2", optional = true } +pyo3 = { version = "0.28.3", features = ["abi3-py312"], optional = true } serde = { version = "1.0", features = ["derive"] } toml = "0.8" log = "0.4" diff --git a/crates/fastly-compute-py/build.rs b/crates/fastly-compute-py/build.rs index 557f6db..a080685 100644 --- a/crates/fastly-compute-py/build.rs +++ b/crates/fastly-compute-py/build.rs @@ -79,6 +79,12 @@ fn build_wasiless_wasm(root_dir: impl AsRef, out_dir: impl AsRef) -> .arg("--manifest-path") .arg(&manifest_path) .env("CARGO_TARGET_DIR", &target_dir) + // Clear host-specific rustflags injected by maturin or other build + // wrappers (e.g. -Csplit-debuginfo=packed). These are valid for the + // host target but are not supported by wasm32-unknown-unknown and + // will cause the cross-compilation to fail. + .env_remove("CARGO_ENCODED_RUSTFLAGS") + .env_remove("RUSTFLAGS") .status() .context("Failed to run cargo build for wasiless")?; From 5d3ca26f661c1c858499ef36eed4b217e22a439f Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Wed, 27 May 2026 12:03:57 -0500 Subject: [PATCH 03/28] Switch to abi3 wheels (CPython 3.12+) Enable pyo3's stable ABI support so a single wheel per platform works on CPython 3.12 and all future minor versions, avoiding an exploding release matrix. - Add abi3-py312 feature to pyo3 in Cargo.toml - Set features = ["pyo3/abi3-py312"] in [tool.maturin] so maturin emits the cp312-abi3-* wheel tag - Drop -i python3.12 from release workflow build args (not needed with abi3; maturin infers the minimum version from the Cargo feature) --- .github/workflows/release.yml | 3 ++- pyproject.toml | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ab18fdb..0e57283 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,7 +52,8 @@ jobs: manylinux: ${{ matrix.manylinux }} rust-toolchain: "1.92.0" rustup-components: "rustfmt,clippy" - args: --release --locked -i python3.12 --compatibility pypi + # abi3-py312 is set in [tool.maturin] features; no -i needed. + args: --release --locked --compatibility pypi # Install nightly + wasm targets inside the container before the build. before-script-linux: | rustup toolchain install nightly --component rust-src diff --git a/pyproject.toml b/pyproject.toml index 8c57099..fb9ca74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,6 +92,9 @@ dev = [ module-name = "fastly_compute._fastly_compute_py" manifest-path = "crates/fastly-compute-py/Cargo.toml" exclude = ["fastly_compute/tests/**/*"] +# Emit a stable-ABI (abi3) wheel tagged cp312-abi3-* so a single wheel +# works on CPython 3.12 and all future versions without rebuilding. +features = ["pyo3/abi3-py312"] [project.scripts] fastly-compute-py = "fastly_compute.fastly_compute_py:main" From 52b7fc630cfe239aa582256884ef310ac8dc315e Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Wed, 27 May 2026 15:40:56 -0500 Subject: [PATCH 04/28] Fix .gitignore gaps for __pycache__ and macOS debug symbols - Make __pycache__ recursive (**/__pycache__/) so subdirectories under examples/, scripts/, fastly_compute/_bindings/, and tests/ are covered - Add *.pyd for Windows compiled extensions alongside the existing *.so - Add *.dSYM/ to ignore macOS debug symbol bundles emitted by maturin develop --- .gitignore | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 768d3a3..e33a836 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Python/uv artifacts .venv/ *.egg-info -__pycache__ +**/__pycache__/ # Build artifacts /build/ @@ -9,4 +9,10 @@ bin/ # Rust target/ + +# Compiled Python extension (maturin develop output) *.so +*.pyd + +# macOS debug symbol bundles generated by maturin develop +*.dSYM/ From 2cceed2bff027a45e6846e1f7293a2399ff4283b Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Wed, 27 May 2026 15:46:34 -0500 Subject: [PATCH 05/28] Extend check_version_sync.py to validate against a release tag Add an optional --tag argument (also read from $VERSION) that strips the leading 'v' and checks it against both in-tree versions. Exits non-zero if any mismatch is found, so it can be used as a hard gate in CI. Existing no-argument behaviour (file consistency check) is unchanged. --- scripts/check_version_sync.py | 60 +++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/scripts/check_version_sync.py b/scripts/check_version_sync.py index bc018d3..bfae6c0 100755 --- a/scripts/check_version_sync.py +++ b/scripts/check_version_sync.py @@ -5,14 +5,26 @@ in crates/fastly-compute-py/Cargo.toml. Both files must be kept in sync to avoid confusion when releasing new versions. +When --tag is passed (or the VERSION environment variable is set), the +in-tree versions are also validated against the expected release version. +The tag is expected to be in the form "vX.Y.Z"; the leading "v" is stripped +before comparison. + Exit codes: - 0: Versions are synchronized + 0: Versions are synchronized (and match the tag, if provided) 1: Version mismatch detected Usage: + # Check file consistency only (used by `make lint`): python scripts/check_version_sync.py + + # Also validate against a release tag (used by the release workflow): + python scripts/check_version_sync.py --tag v1.2.3 + VERSION=v1.2.3 python scripts/check_version_sync.py """ +import argparse +import os import sys import tomllib from pathlib import Path @@ -36,22 +48,50 @@ def get_cargo_version() -> str: return data["package"]["version"] +def parse_tag_version(tag: str) -> str: + """Strip a leading 'v' from a tag to get a bare version string.""" + return tag.lstrip("v") + + def main() -> int: """Check version consistency and exit with appropriate code.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--tag", + metavar="TAG", + default=os.environ.get("VERSION"), + help="Expected release tag (e.g. v1.2.3). Also read from $VERSION.", + ) + args = parser.parse_args() + pyproject_version = get_pyproject_version() cargo_version = get_cargo_version() + ok = True + if pyproject_version == cargo_version: - print(f"✓ Version numbers are synchronized: {pyproject_version}") - return 0 + print(f"✓ pyproject.toml and Cargo.toml are synchronized: {pyproject_version}") else: - print("✗ Version mismatch detected:", file=sys.stderr) - print(f" pyproject.toml: {pyproject_version}", file=sys.stderr) - print( - f" crates/fastly-compute-py/Cargo.toml: {cargo_version}", file=sys.stderr - ) - print("\nPlease update both files to use the same version.", file=sys.stderr) - return 1 + print("✗ Version mismatch between pyproject.toml and Cargo.toml:", file=sys.stderr) + print(f" pyproject.toml: {pyproject_version}", file=sys.stderr) + print(f" crates/fastly-compute-py/Cargo.toml: {cargo_version}", file=sys.stderr) + print("\nUpdate both files to use the same version.", file=sys.stderr) + ok = False + + if args.tag: + expected = parse_tag_version(args.tag) + if pyproject_version == expected and cargo_version == expected: + print(f"✓ In-tree versions match tag '{args.tag}': {expected}") + else: + print(f"✗ In-tree versions do not match tag '{args.tag}':", file=sys.stderr) + if pyproject_version != expected: + print(f" pyproject.toml: {pyproject_version} (expected {expected})", file=sys.stderr) + if cargo_version != expected: + print(f" crates/fastly-compute-py/Cargo.toml: {cargo_version} (expected {expected})", file=sys.stderr) + print("\nBump both files to match the tag before releasing.", file=sys.stderr) + ok = False + + return 0 if ok else 1 if __name__ == "__main__": From 6bab6bb6785913c396d8e71e16df6ea4acb0bdbd Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Wed, 27 May 2026 15:46:39 -0500 Subject: [PATCH 06/28] Add version consistency gate to release workflow Add a check-version job that runs check_version_sync.py --tag against the triggering tag (or workflow_dispatch version input) before any builds start. All three build jobs depend on it, so a tag/file mismatch fails fast without burning build minutes. --- .github/workflows/release.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0e57283..69028e9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,6 +20,27 @@ permissions: contents: read jobs: + # --------------------------------------------------------------------------- + # Verify that the versions in pyproject.toml and Cargo.toml match the tag + # (or the manually-supplied version label) before spending time on builds. + # For workflow_dispatch runs the version input is used; for tag pushes the + # tag itself is used. + # --------------------------------------------------------------------------- + check-version: + name: "Check version consistency" + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Verify versions match + run: | + python scripts/check_version_sync.py --tag "${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }}" + # --------------------------------------------------------------------------- # Linux wheels — built inside manylinux_2_28 containers via maturin-action. # Cross-compilation for aarch64 is handled by the action's built-in container; @@ -28,6 +49,7 @@ jobs: build-linux: name: "Linux ${{ matrix.target }}" runs-on: ubuntu-24.04 + needs: [check-version] strategy: fail-fast: false matrix: @@ -75,6 +97,7 @@ jobs: build-macos: name: "macOS ${{ matrix.target }}" runs-on: ${{ matrix.runner }} + needs: [check-version] strategy: fail-fast: false matrix: @@ -154,6 +177,7 @@ jobs: build-windows: name: "Windows x86_64" runs-on: windows-latest + needs: [check-version] steps: - uses: actions/checkout@v4 From 3b9222593eb6ab4f15df3bee22d218925d6a57e7 Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Wed, 27 May 2026 15:48:00 -0500 Subject: [PATCH 07/28] Add release process documentation to CONTRIBUTING.md Documents the step-by-step release flow: version bump, sync check, tagging, workflow monitoring, wheel validation, and PyPI promotion. Also covers ad-hoc test builds via workflow_dispatch and how to recover from a failed version check. --- CONTRIBUTING.md | 69 +++++++++++++++++++++++++++++++++++++++++++++---- Makefile | 9 ++++++- 2 files changed, 72 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b98f651..705e91a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -135,9 +135,68 @@ Understanding the build process helps when debugging issues: ## Continuous Integration -Our CI builds wheels for multiple platforms: -- Linux: x86_64, aarch64 -- macOS: x86_64 (Intel), aarch64 (Apple Silicon) -- Windows: x86_64 +The CI workflow (`.github/workflows/python-ci.yml`) validates formatting, +linting, and tests on every push and pull request. The release workflow +(`.github/workflows/release.yml`) builds binary wheels for all supported +platforms (Linux x86_64/aarch64, macOS x86_64/aarch64, Windows x86_64) +and attaches them to a GitHub pre-release. + +## Performing a Release + +Releases are driven by a git tag. The release workflow builds binary wheels +and attaches them to a GitHub pre-release for validation before PyPI publishing. + +The version must be kept in sync across two files: +- `pyproject.toml` — `[project] version` +- `crates/fastly-compute-py/Cargo.toml` — `[package] version` + +`make lint` checks these are in sync. To also validate against a specific tag: + +```bash +make check-version-tag TAG=v0.2.0 +``` + +### Steps + +1. Bump `version` in both files above to the new version (e.g. `0.2.0`). + +2. Verify locally: + ```bash + make check-version-tag TAG=v0.2.0 + ``` + +3. Commit and tag: + ```bash + git add pyproject.toml crates/fastly-compute-py/Cargo.toml + git commit -m "Bump version to 0.2.0" + git tag v0.2.0 + git push origin v0.2.0 + ``` + +4. The release workflow runs automatically. Jobs: `check-version` (fails fast + on any mismatch) → parallel wheel builds → `collect-wheels` → + `create-github-release`. + +5. Install a wheel from the GitHub Release to validate before promoting to PyPI: + ```bash + pip install https://github.com/fastly/compute-sdk-python/releases/download/v0.2.0/.whl + ``` + +6. Promote to PyPI once validated (via trusted publishing, configured separately). + +### Ad-hoc test builds + +To build wheels without tagging, trigger the workflow manually from +**Actions → Release → Run workflow** with a version label (e.g. `v0.2.0-rc1`). +Wheels are uploaded as Actions artifacts; no release is created. The in-tree +versions must still match the label you provide. + +### If the version check fails + +Fix the mismatch, then retag: +```bash +git tag -f v0.2.0 +git push --force origin v0.2.0 +``` + -The CI workflow (`.github/workflows/build-wheels.yml`) ensures all required tools are installed automatically. diff --git a/Makefile b/Makefile index 99e1d11..177fb6b 100644 --- a/Makefile +++ b/Makefile @@ -92,6 +92,13 @@ clean: cd crates/fastly-compute-py && cargo clean # Development tools +check-version: + uv run python scripts/check_version_sync.py + +check-version-tag: + @test -n "$(TAG)" || (echo "Usage: make check-version-tag TAG=v0.1.0" && exit 1) + uv run python scripts/check_version_sync.py --tag $(TAG) + lint: fastly_compute/runtime_patching/patches.py | $(STUBS_DIR) @echo "Checking version synchronization..." uv run python scripts/check_version_sync.py @@ -156,4 +163,4 @@ help: @echo "" @echo "Available examples: $(EXAMPLES)" -.PHONY: all serve test test-update-snapshots list-examples build-all clean lint lint-fix format format-check help +.PHONY: all serve test test-update-snapshots list-examples build-all clean lint lint-fix format format-check check-version check-version-tag help From 950080c0b5b7b7d3d486c05d001330ec60788fe1 Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Wed, 27 May 2026 16:07:04 -0500 Subject: [PATCH 08/28] Factor toolchain versions into workflow-level env vars Centralise RUST_STABLE, MATURIN_VERSION, and PYTHON_VERSION into top-level env: blocks so upgrading any of them requires a single-line change: - release.yml: RUST_STABLE, MATURIN_VERSION, PYTHON_VERSION - python-ci.yml: RUST_STABLE (cross-ref to release.yml for context) Also fixes two issues introduced in recent edits: - Maturin version was inconsistent (v1.13.3 on Linux, v1.11.5 on macOS/Windows) - -i python3.12 was still present on macOS/Windows args despite abi3 not needing it - Hardcoded 1.92.0 in two cache key strings replaced with ${{ env.RUST_STABLE }} --- .github/workflows/python-ci.yml | 5 +-- .github/workflows/release.yml | 59 +++++++++++++++------------------ 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 832a3f7..67813ef 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -5,10 +5,11 @@ on: branches: - main pull_request: - workflow_dispatch: # Allows manual triggering from GitHub Actions UI + workflow_dispatch: # allow manual triggering env: VICEROY_TAG: v0.16.4 + RUST_STABLE: "1.92.0" # pinned; see comment in release.yml build-linux jobs: build: @@ -18,7 +19,7 @@ jobs: - name: Set up Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: 1.92.0 + toolchain: ${{ env.RUST_STABLE }} target: wasm32-unknown-unknown components: rustfmt, clippy # Disable the built-in cargo cache - we use our own below diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 69028e9..2198d62 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,13 +19,17 @@ on: permissions: contents: read +env: + # Bump these in one place when upgrading toolchains or maturin. + PYTHON_VERSION: "3.12" + RUST_STABLE: "1.92.0" # pinned; see comment in build-linux + MATURIN_VERSION: "v1.13.3" + jobs: - # --------------------------------------------------------------------------- # Verify that the versions in pyproject.toml and Cargo.toml match the tag # (or the manually-supplied version label) before spending time on builds. # For workflow_dispatch runs the version input is used; for tag pushes the # tag itself is used. - # --------------------------------------------------------------------------- check-version: name: "Check version consistency" runs-on: ubuntu-24.04 @@ -35,17 +39,15 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: ${{ env.PYTHON_VERSION }} - name: Verify versions match run: | python scripts/check_version_sync.py --tag "${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }}" - # --------------------------------------------------------------------------- - # Linux wheels — built inside manylinux_2_28 containers via maturin-action. - # Cross-compilation for aarch64 is handled by the action's built-in container; - # no QEMU or explicit cross-toolchain setup is needed. - # --------------------------------------------------------------------------- + # Linux wheels are built inside manylinux_2_28 containers via maturin-action. + # We explicitly target manylinux 2_28; auto likely would too but pinning + # avoids unexpected wheel renames. See https://github.com/pypa/manylinux. build-linux: name: "Linux ${{ matrix.target }}" runs-on: ubuntu-24.04 @@ -69,10 +71,11 @@ jobs: - name: Build wheels uses: PyO3/maturin-action@v1 with: - maturin-version: "v1.11.5" + maturin-version: ${{ env.MATURIN_VERSION }} target: ${{ matrix.target }} manylinux: ${{ matrix.manylinux }} - rust-toolchain: "1.92.0" + # Pinned to avoid wasm32-wasip2 semver issues with newer stable releases. + rust-toolchain: ${{ env.RUST_STABLE }} rustup-components: "rustfmt,clippy" # abi3-py312 is set in [tool.maturin] features; no -i needed. args: --release --locked --compatibility pypi @@ -89,11 +92,8 @@ jobs: path: target/wheels/*.whl if-no-files-found: error - # --------------------------------------------------------------------------- # macOS wheels — native builds on GitHub-hosted runners. # macos-14 is Apple Silicon (aarch64); macos-13 is Intel (x86_64). - # Using native runners avoids cross-compilation complexity entirely. - # --------------------------------------------------------------------------- build-macos: name: "macOS ${{ matrix.target }}" runs-on: ${{ matrix.runner }} @@ -113,7 +113,7 @@ jobs: - name: Set up Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: "1.92.0" + toolchain: ${{ env.RUST_STABLE }} target: wasm32-unknown-unknown components: rustfmt,clippy cache: false @@ -143,7 +143,7 @@ jobs: uses: actions/cache@v4 with: path: target/ - key: cargo-target-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}-1.92.0-v1 + key: cargo-target-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE }}-v1 restore-keys: | cargo-target-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}- cargo-target-${{ runner.os }}-${{ matrix.target }}- @@ -151,16 +151,17 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: ${{ env.PYTHON_VERSION }} - name: Build wheels uses: PyO3/maturin-action@v1 with: - maturin-version: "v1.11.5" + maturin-version: ${{ env.MATURIN_VERSION }} target: ${{ matrix.target }} # container: off is implied for non-Linux but set explicitly for clarity. container: "off" - args: --release --locked -i python3.12 + # abi3-py312 is set in [tool.maturin] features; no -i needed. + args: --release --locked - name: Upload wheels uses: actions/upload-artifact@v4 @@ -169,11 +170,9 @@ jobs: path: target/wheels/*.whl if-no-files-found: error - # --------------------------------------------------------------------------- # Windows wheel — native build on windows-latest (x86_64). # Windows support is currently untested; this is a hard failure intentionally # so that any Windows-specific build issues surface immediately. - # --------------------------------------------------------------------------- build-windows: name: "Windows x86_64" runs-on: windows-latest @@ -185,7 +184,7 @@ jobs: - name: Set up Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: "1.92.0" + toolchain: ${{ env.RUST_STABLE }} target: wasm32-unknown-unknown components: rustfmt,clippy cache: false @@ -211,23 +210,24 @@ jobs: uses: actions/cache@v4 with: path: target\ - key: cargo-target-${{ runner.os }}-x86_64-unknown-windows-msvc-${{ hashFiles('**/Cargo.lock') }}-1.92.0-v1 + key: cargo-target-${{ runner.os }}-x86_64-pc-windows-msvc-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE }}-v1 restore-keys: | - cargo-target-${{ runner.os }}-x86_64-unknown-windows-msvc-${{ hashFiles('**/Cargo.lock') }}- - cargo-target-${{ runner.os }}-x86_64-unknown-windows-msvc- + cargo-target-${{ runner.os }}-x86_64-pc-windows-msvc-${{ hashFiles('**/Cargo.lock') }}- + cargo-target-${{ runner.os }}-x86_64-pc-windows-msvc- - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: ${{ env.PYTHON_VERSION }} - name: Build wheels uses: PyO3/maturin-action@v1 with: - maturin-version: "v1.11.5" + maturin-version: ${{ env.MATURIN_VERSION }} target: x86_64-pc-windows-msvc container: "off" - args: --release --locked -i python3.12 + # abi3-py312 is set in [tool.maturin] features; no -i needed. + args: --release --locked - name: Upload wheels uses: actions/upload-artifact@v4 @@ -236,9 +236,7 @@ jobs: path: target/wheels/*.whl if-no-files-found: error - # --------------------------------------------------------------------------- # Collect all wheels into a single artifact for convenient download. - # --------------------------------------------------------------------------- collect-wheels: name: "Collect all wheels" needs: [build-linux, build-macos, build-windows] @@ -262,16 +260,13 @@ jobs: path: dist/*.whl if-no-files-found: error - # --------------------------------------------------------------------------- # Create a GitHub Release and attach all wheels. # Only runs on tag pushes — workflow_dispatch builds stop at collect-wheels, # leaving wheels as Actions artifacts for download and local testing. - # --------------------------------------------------------------------------- create-github-release: name: "Create GitHub Release" needs: [collect-wheels] runs-on: ubuntu-24.04 - # Only create a release for tag-triggered runs. if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') permissions: contents: write # Required to create releases and upload assets. From 49e0aaf1e0a6842394f1590ed019adb159aa633f Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Wed, 27 May 2026 16:13:27 -0500 Subject: [PATCH 09/28] Extract shared Rust toolchain setup into a composite action Add .github/actions/setup-rust which installs the pinned stable and nightly toolchains in one step. Both workflows now call this action instead of duplicating the setup-rust-toolchain + rustup nightly install sequence. - python-ci.yml: drops RUST_STABLE / RUST_NIGHTLY from env entirely; calls the action with no overrides (uses defaults) - release.yml: macOS and Windows jobs call the action; Linux keeps before-script-linux (composite actions can't run inside maturin-action containers) but still reads from env vars that are kept in sync with the action defaults via a comment - rust-toolchain.toml: added to pin the stable channel for local dev and document the nightly requirement --- .github/actions/setup-rust/action.yml | 32 +++++++++++++++++ .github/workflows/python-ci.yml | 15 ++------ .github/workflows/release.yml | 50 +++++++++------------------ rust-toolchain.toml | 20 +++++++++++ 4 files changed, 71 insertions(+), 46 deletions(-) create mode 100644 .github/actions/setup-rust/action.yml create mode 100644 rust-toolchain.toml diff --git a/.github/actions/setup-rust/action.yml b/.github/actions/setup-rust/action.yml new file mode 100644 index 0000000..86fa52b --- /dev/null +++ b/.github/actions/setup-rust/action.yml @@ -0,0 +1,32 @@ +name: "Setup Rust toolchains" +description: > + Installs the pinned stable and nightly Rust toolchains required by this + project. Nightly is needed solely because componentize-py's build.rs uses + `-Z build-std` to compile its wasm32-wasip1 runtime with PIC, which is a + nightly-only Cargo flag. + +inputs: + rust-stable: + description: "Stable toolchain version" + default: "1.92.0" + rust-nightly: + description: "Nightly toolchain date (e.g. nightly-2026-04-27)" + default: "nightly-2026-04-27" + +runs: + using: "composite" + steps: + - name: Set up stable Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: ${{ inputs.rust-stable }} + target: wasm32-unknown-unknown + components: rustfmt,clippy + # Disable the built-in cargo cache — callers manage their own. + cache: false + + - name: Set up nightly Rust toolchain with rust-src + shell: bash + run: | + rustup toolchain install ${{ inputs.rust-nightly }} --component rust-src + rustup target add wasm32-wasip1 --toolchain ${{ inputs.rust-nightly }} diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 67813ef..320b7e3 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -9,25 +9,14 @@ on: env: VICEROY_TAG: v0.16.4 - RUST_STABLE: "1.92.0" # pinned; see comment in release.yml build-linux jobs: build: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - - name: Set up Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - toolchain: ${{ env.RUST_STABLE }} - target: wasm32-unknown-unknown - components: rustfmt, clippy - # Disable the built-in cargo cache - we use our own below - cache: false - - name: Set up nightly Rust toolchain with rust-src - run: | - rustup toolchain install nightly --component rust-src - rustup target add wasm32-wasip1 --toolchain nightly + - name: Set up Rust toolchains + uses: ./.github/actions/setup-rust - name: Set up Python uses: actions/setup-python@v5 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2198d62..80ebc81 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,15 +21,16 @@ permissions: env: # Bump these in one place when upgrading toolchains or maturin. + # RUST_STABLE and RUST_NIGHTLY must also be kept in sync with the defaults + # in .github/actions/setup-rust/action.yml. PYTHON_VERSION: "3.12" - RUST_STABLE: "1.92.0" # pinned; see comment in build-linux + RUST_STABLE: "1.92.0" + RUST_NIGHTLY: "nightly-2026-04-27" MATURIN_VERSION: "v1.13.3" jobs: # Verify that the versions in pyproject.toml and Cargo.toml match the tag # (or the manually-supplied version label) before spending time on builds. - # For workflow_dispatch runs the version input is used; for tag pushes the - # tag itself is used. check-version: name: "Check version consistency" runs-on: ubuntu-24.04 @@ -48,6 +49,9 @@ jobs: # Linux wheels are built inside manylinux_2_28 containers via maturin-action. # We explicitly target manylinux 2_28; auto likely would too but pinning # avoids unexpected wheel renames. See https://github.com/pypa/manylinux. + # + # The composite setup-rust action cannot run inside the maturin-action + # container, so toolchain setup is handled via before-script-linux instead. build-linux: name: "Linux ${{ matrix.target }}" runs-on: ubuntu-24.04 @@ -64,25 +68,19 @@ jobs: steps: - uses: actions/checkout@v4 - # maturin-action runs inside a manylinux container for Linux builds. - # We need both stable (for the extension) and nightly (for wasiless / - # componentize-py which requires wasm32-wasip1 + rust-src). - # The before-script-linux hook runs inside the container before maturin. - name: Build wheels uses: PyO3/maturin-action@v1 with: maturin-version: ${{ env.MATURIN_VERSION }} target: ${{ matrix.target }} manylinux: ${{ matrix.manylinux }} - # Pinned to avoid wasm32-wasip2 semver issues with newer stable releases. rust-toolchain: ${{ env.RUST_STABLE }} rustup-components: "rustfmt,clippy" # abi3-py312 is set in [tool.maturin] features; no -i needed. args: --release --locked --compatibility pypi - # Install nightly + wasm targets inside the container before the build. before-script-linux: | - rustup toolchain install nightly --component rust-src - rustup target add wasm32-wasip1 --toolchain nightly + rustup toolchain install ${{ env.RUST_NIGHTLY }} --component rust-src + rustup target add wasm32-wasip1 --toolchain ${{ env.RUST_NIGHTLY }} rustup target add wasm32-unknown-unknown - name: Upload wheels @@ -110,18 +108,11 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 + - name: Set up Rust toolchains + uses: ./.github/actions/setup-rust with: - toolchain: ${{ env.RUST_STABLE }} - target: wasm32-unknown-unknown - components: rustfmt,clippy - cache: false - - - name: Set up nightly Rust toolchain with rust-src - run: | - rustup toolchain install nightly --component rust-src - rustup target add wasm32-wasip1 --toolchain nightly + rust-stable: ${{ env.RUST_STABLE }} + rust-nightly: ${{ env.RUST_NIGHTLY }} # Cache Rust registry/git sources — keyed on Cargo.lock only so they # are shared across runs with the same lockfile. @@ -181,18 +172,11 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 + - name: Set up Rust toolchains + uses: ./.github/actions/setup-rust with: - toolchain: ${{ env.RUST_STABLE }} - target: wasm32-unknown-unknown - components: rustfmt,clippy - cache: false - - - name: Set up nightly Rust toolchain with rust-src - run: | - rustup toolchain install nightly --component rust-src - rustup target add wasm32-wasip1 --toolchain nightly + rust-stable: ${{ env.RUST_STABLE }} + rust-nightly: ${{ env.RUST_NIGHTLY }} - name: Cache Rust sources uses: actions/cache@v4 diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..8d37d2b --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,20 @@ +# Rust toolchain configuration for this project. +# +# Pins the stable channel used for the main build. wasm32-unknown-unknown is +# required by build.rs (wasiless cross-compilation). +# +# A nightly toolchain is also required at build time: componentize-py's +# build.rs uses `-Z build-std` to compile its wasm32-wasip1 runtime with +# position-independent code, which is a nightly-only Cargo flag. This cannot +# be avoided with stable Rust, it is a hard upstream requirement. +# +# The nightly version is pinned in the CI workflows (RUST_NIGHTLY env var). +# For local development, install it with: +# +# rustup toolchain install nightly-YYYY-MM-DD --component rust-src --target wasm32-wasip1 +# +# See .github/workflows/python-ci.yml for the current pinned date. +[toolchain] +channel = "1.92.0" +targets = ["wasm32-unknown-unknown"] +components = ["rustfmt", "clippy"] From 54a5759b06a22a8c40fe1597dffc72adfa169ea1 Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Wed, 27 May 2026 16:22:38 -0500 Subject: [PATCH 10/28] Incorporate some bits from componentize-py CI - Add concurrency/cancel-in-progress so redundant runs on the same branch are cancelled when a new push arrives (per componentize-py CI) - Add a generate-checksums job that runs after collect-wheels and before create-github-release. It produces checksums.txt (sha256sum of all wheels) which is uploaded as an artifact and attached to the GitHub Release alongside the wheels. --- .github/workflows/python-ci.yml | 8 ++++++- .github/workflows/release.yml | 37 +++++++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 320b7e3..a5ba002 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -7,6 +7,12 @@ on: pull_request: workflow_dispatch: # allow manual triggering +# Cancel in-progress runs for the same branch/PR when a new push arrives. +# Omitted from release.yml — you never want to cancel an in-progress release. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: VICEROY_TAG: v0.16.4 @@ -24,7 +30,7 @@ jobs: - name: Install uv run: pip install uv - # Cache cargo binaries (viceroy, wasm-tools, etc.) + # Cache cargo binaries (viceroy, wasm-tools, wac-cli). - name: Cache cargo binaries id: cache-cargo-bins uses: actions/cache@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 80ebc81..d08f9ec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -244,12 +244,39 @@ jobs: path: dist/*.whl if-no-files-found: error + # Generate SHA256 checksums for all wheels and attach them to the release. + # pip and uv can verify wheels against published checksums. + generate-checksums: + name: "Generate checksums" + needs: [collect-wheels] + runs-on: ubuntu-24.04 + + steps: + - name: Download all wheels + uses: actions/download-artifact@v4 + with: + name: all-wheels + path: dist/ + + - name: Generate SHA256 checksums + run: | + cd dist + sha256sum *.whl > checksums.txt + cat checksums.txt + + - name: Upload checksums artifact + uses: actions/upload-artifact@v4 + with: + name: checksums + path: dist/checksums.txt + if-no-files-found: error + # Create a GitHub Release and attach all wheels. # Only runs on tag pushes — workflow_dispatch builds stop at collect-wheels, # leaving wheels as Actions artifacts for download and local testing. create-github-release: name: "Create GitHub Release" - needs: [collect-wheels] + needs: [generate-checksums] runs-on: ubuntu-24.04 if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') permissions: @@ -264,12 +291,18 @@ jobs: name: all-wheels path: dist/ + - name: Download checksums + uses: actions/download-artifact@v4 + with: + name: checksums + path: dist/ + - name: Create GitHub Release env: GH_TOKEN: ${{ github.token }} run: | TAG="${{ github.ref_name }}" - gh release create "$TAG" dist/*.whl \ + gh release create "$TAG" dist/*.whl dist/checksums.txt \ --title "$TAG" \ --prerelease \ --generate-notes \ From 9279655e7014f02a754fcc4231ab092bb209e1ba Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Wed, 27 May 2026 16:28:18 -0500 Subject: [PATCH 11/28] Upgrade actions/checkout from v4 to v6 v6 adds Node.js 24 runtime support and a minor credentials storage improvement. No breaking changes for our usage. --- .github/workflows/python-ci.yml | 2 +- .github/workflows/release.yml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index a5ba002..549c722 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -20,7 +20,7 @@ jobs: build: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Rust toolchains uses: ./.github/actions/setup-rust - name: Set up Python diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d08f9ec..0848d89 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,7 +35,7 @@ jobs: name: "Check version consistency" runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v5 @@ -66,7 +66,7 @@ jobs: manylinux: "2_28" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Build wheels uses: PyO3/maturin-action@v1 @@ -106,7 +106,7 @@ jobs: runner: macos-14 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Rust toolchains uses: ./.github/actions/setup-rust @@ -170,7 +170,7 @@ jobs: needs: [check-version] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Rust toolchains uses: ./.github/actions/setup-rust @@ -283,7 +283,7 @@ jobs: contents: write # Required to create releases and upload assets. steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Download all wheels uses: actions/download-artifact@v4 From dd1d7159f94b480a6c61ef4e3d51bcec82a5c503 Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Wed, 27 May 2026 17:31:32 -0500 Subject: [PATCH 12/28] Fix failures from first pass of release CI - Remove windows support for now; it should be able to be supported with a componentize-py update it appears (and some other work). For now, drop to avoid increasing scope further. - Link the "nightly" rustup name to our pinned nightly. This has to do with how componentize-py's build interacts with rustup. --- .github/actions/setup-rust/action.yml | 7 +++ .github/workflows/release.yml | 70 ++++----------------------- CONTRIBUTING.md | 9 +++- 3 files changed, 24 insertions(+), 62 deletions(-) diff --git a/.github/actions/setup-rust/action.yml b/.github/actions/setup-rust/action.yml index 86fa52b..37c4ad8 100644 --- a/.github/actions/setup-rust/action.yml +++ b/.github/actions/setup-rust/action.yml @@ -28,5 +28,12 @@ runs: - name: Set up nightly Rust toolchain with rust-src shell: bash run: | + # Install the pinned dated nightly. componentize-py's build.rs also + # invokes `rustup run nightly cargo build` using the bare 'nightly' + # name; rustup rejects that name for `toolchain link`, so we install + # the dated toolchain and also install the bare nightly channel. + # Cargo.lock pins all crate versions regardless of compiler date. rustup toolchain install ${{ inputs.rust-nightly }} --component rust-src rustup target add wasm32-wasip1 --toolchain ${{ inputs.rust-nightly }} + rustup toolchain install nightly --component rust-src + rustup target add wasm32-wasip1 --toolchain nightly diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0848d89..1095a4f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -75,13 +75,17 @@ jobs: target: ${{ matrix.target }} manylinux: ${{ matrix.manylinux }} rust-toolchain: ${{ env.RUST_STABLE }} - rustup-components: "rustfmt,clippy" # abi3-py312 is set in [tool.maturin] features; no -i needed. args: --release --locked --compatibility pypi before-script-linux: | rustup toolchain install ${{ env.RUST_NIGHTLY }} --component rust-src rustup target add wasm32-wasip1 --toolchain ${{ env.RUST_NIGHTLY }} rustup target add wasm32-unknown-unknown + # componentize-py's build.rs invokes `rustup run nightly cargo build`. + # rustup rejects 'nightly' as a custom link name, so install the + # bare nightly channel separately. Cargo.lock pins crate versions. + rustup toolchain install nightly --component rust-src + rustup target add wasm32-wasip1 --toolchain nightly - name: Upload wheels uses: actions/upload-artifact@v4 @@ -161,69 +165,15 @@ jobs: path: target/wheels/*.whl if-no-files-found: error - # Windows wheel — native build on windows-latest (x86_64). - # Windows support is currently untested; this is a hard failure intentionally - # so that any Windows-specific build issues surface immediately. - build-windows: - name: "Windows x86_64" - runs-on: windows-latest - needs: [check-version] - - steps: - - uses: actions/checkout@v6 - - - name: Set up Rust toolchains - uses: ./.github/actions/setup-rust - with: - rust-stable: ${{ env.RUST_STABLE }} - rust-nightly: ${{ env.RUST_NIGHTLY }} - - - name: Cache Rust sources - uses: actions/cache@v4 - with: - path: | - ~\.cargo\registry\index\ - ~\.cargo\registry\cache\ - ~\.cargo\git\db\ - ~\.cargo\git\checkouts\ - key: cargo-sources-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-v1 - restore-keys: | - cargo-sources-${{ runner.os }}- - - - name: Cache Rust build artifacts - uses: actions/cache@v4 - with: - path: target\ - key: cargo-target-${{ runner.os }}-x86_64-pc-windows-msvc-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE }}-v1 - restore-keys: | - cargo-target-${{ runner.os }}-x86_64-pc-windows-msvc-${{ hashFiles('**/Cargo.lock') }}- - cargo-target-${{ runner.os }}-x86_64-pc-windows-msvc- - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Build wheels - uses: PyO3/maturin-action@v1 - with: - maturin-version: ${{ env.MATURIN_VERSION }} - target: x86_64-pc-windows-msvc - container: "off" - # abi3-py312 is set in [tool.maturin] features; no -i needed. - args: --release --locked - - - name: Upload wheels - uses: actions/upload-artifact@v4 - with: - name: wheels-windows-x86_64 - path: target/wheels/*.whl - if-no-files-found: error + # Windows is not supported at the currently pinned componentize-py commit: + # its build.rs calls a POSIX configure script unconditionally when building + # CPython for WASI. Newer releases of componentize-py have Windows support; + # re-enable this job when upgrading componentize-py. # Collect all wheels into a single artifact for convenient download. collect-wheels: name: "Collect all wheels" - needs: [build-linux, build-macos, build-windows] + needs: [build-linux, build-macos] runs-on: ubuntu-24.04 steps: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 705e91a..2a94024 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -138,8 +138,13 @@ Understanding the build process helps when debugging issues: The CI workflow (`.github/workflows/python-ci.yml`) validates formatting, linting, and tests on every push and pull request. The release workflow (`.github/workflows/release.yml`) builds binary wheels for all supported -platforms (Linux x86_64/aarch64, macOS x86_64/aarch64, Windows x86_64) -and attaches them to a GitHub pre-release. +platforms (Linux x86_64/aarch64, macOS x86_64/aarch64) and attaches them +to a GitHub pre-release. + +Windows is not currently supported: the pinned version of `componentize-py` +calls a POSIX `configure` script unconditionally when building CPython for +WASI. Newer releases of `componentize-py` have resolved this; Windows support +can be re-enabled when upgrading the dependency. ## Performing a Release From a4c046b9a5234b2c57ef4d48416e7ebc1da687c6 Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Wed, 27 May 2026 17:52:34 -0500 Subject: [PATCH 13/28] Add wasm-tools/wac-cli to containers for release and pin --- .github/workflows/python-ci.yml | 2 +- .github/workflows/release.yml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 549c722..6c399a4 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -42,7 +42,7 @@ jobs: ~/.cargo/bin/wac* - name: Install wasm-tools and wac if: steps.cache-cargo-bins.outputs.cache-hit != 'true' - run: cargo install wasm-tools wac-cli + run: cargo install wasm-tools --version 1.250.0 --locked && cargo install wac-cli - name: Install viceroy if: steps.cache-cargo-bins.outputs.cache-hit != 'true' run: cargo install --git https://github.com/fastly/Viceroy.git --tag "$VICEROY_TAG" viceroy diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1095a4f..920f8ef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,6 +27,7 @@ env: RUST_STABLE: "1.92.0" RUST_NIGHTLY: "nightly-2026-04-27" MATURIN_VERSION: "v1.13.3" + WASM_TOOLS_VERSION: "1.250.0" jobs: # Verify that the versions in pyproject.toml and Cargo.toml match the tag @@ -86,6 +87,8 @@ jobs: # bare nightly channel separately. Cargo.lock pins crate versions. rustup toolchain install nightly --component rust-src rustup target add wasm32-wasip1 --toolchain nightly + # build.rs calls wasm-tools directly; install it inside the container. + cargo install wasm-tools --version ${{ env.WASM_TOOLS_VERSION }} --locked - name: Upload wheels uses: actions/upload-artifact@v4 From b195a8786ffaa2ee5096bb413e54729875fdd389 Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Wed, 27 May 2026 18:59:27 -0500 Subject: [PATCH 14/28] Fix wasm-tools install in release workflow Linux: the manylinux cross container sets CARGO_BUILD_TARGET to the wheel target (e.g. aarch64), so cargo install would produce an aarch64 binary that can't execute on the x86_64 host. Download the x86_64 pre-built binary from GitHub releases explicitly. macOS: wasm-tools is not pre-installed on GitHub runners; add an install step using the pre-built macOS binary, selecting the correct arch via uname -m (x86_64 on macos-13, arm64 on macos-14). --- .github/workflows/release.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 920f8ef..e60b1d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -87,8 +87,12 @@ jobs: # bare nightly channel separately. Cargo.lock pins crate versions. rustup toolchain install nightly --component rust-src rustup target add wasm32-wasip1 --toolchain nightly - # build.rs calls wasm-tools directly; install it inside the container. - cargo install wasm-tools --version ${{ env.WASM_TOOLS_VERSION }} --locked + # build.rs calls wasm-tools as a host-side subprocess. The container + # always runs on an x86_64 host (CARGO_BUILD_TARGET is set to the + # cross target, so cargo install would produce an aarch64 binary that + # can't execute). Download the x86_64 pre-built binary explicitly. + curl -sSfL "https://github.com/bytecodealliance/wasm-tools/releases/download/v${{ env.WASM_TOOLS_VERSION }}/wasm-tools-${{ env.WASM_TOOLS_VERSION }}-x86_64-linux.tar.gz" \ + | tar -xz --strip-components=1 -C /usr/local/bin "wasm-tools-${{ env.WASM_TOOLS_VERSION }}-x86_64-linux/wasm-tools" - name: Upload wheels uses: actions/upload-artifact@v4 @@ -146,6 +150,12 @@ jobs: cargo-target-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}- cargo-target-${{ runner.os }}-${{ matrix.target }}- + - name: Install wasm-tools + run: | + ARCH=$(uname -m) + curl -sSfL "https://github.com/bytecodealliance/wasm-tools/releases/download/v${{ env.WASM_TOOLS_VERSION }}/wasm-tools-${{ env.WASM_TOOLS_VERSION }}-${ARCH}-macos.tar.gz" \ + | tar -xz --strip-components=1 -C /usr/local/bin "wasm-tools-${{ env.WASM_TOOLS_VERSION }}-${ARCH}-macos/wasm-tools" + - name: Set up Python uses: actions/setup-python@v5 with: From f54dec8083f74dda735eb567ef4ec4517bcc83ee Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Wed, 27 May 2026 20:14:58 -0500 Subject: [PATCH 15/28] Fix arm64 macOS wasm-tools download; disable macos-13 runner uname -m returns 'arm64' on Apple Silicon but release assets use 'aarch64'; add a mapping before constructing the download URL. Also comment out the x86_64-apple-darwin (macos-13) matrix entry as those runners are queueing indefinitely. Re-enable when availability improves. --- .github/workflows/release.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e60b1d6..44c9dda 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -111,8 +111,10 @@ jobs: fail-fast: false matrix: include: - - target: x86_64-apple-darwin - runner: macos-13 + # macos-13 (x86_64) runners have been queueing indefinitely; disabled + # for now. Re-enable once runner availability improves. + # - target: x86_64-apple-darwin + # runner: macos-13 - target: aarch64-apple-darwin runner: macos-14 @@ -153,6 +155,8 @@ jobs: - name: Install wasm-tools run: | ARCH=$(uname -m) + # uname -m returns 'arm64' on Apple Silicon; release assets use 'aarch64' + [[ "$ARCH" == "arm64" ]] && ARCH="aarch64" curl -sSfL "https://github.com/bytecodealliance/wasm-tools/releases/download/v${{ env.WASM_TOOLS_VERSION }}/wasm-tools-${{ env.WASM_TOOLS_VERSION }}-${ARCH}-macos.tar.gz" \ | tar -xz --strip-components=1 -C /usr/local/bin "wasm-tools-${{ env.WASM_TOOLS_VERSION }}-${ARCH}-macos/wasm-tools" From 58a81c25eae569b4748cf6964bed02d1d72032e7 Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Thu, 28 May 2026 11:01:15 -0500 Subject: [PATCH 16/28] Update macOS runners to macos-26/macos-26-intel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit macos-13 is no longer reliably available; macos-26 (Tahoe) is current and available for both Intel and Apple Silicon. Runner OS version has no effect on wheel compatibility — deployment target is set by the Rust target triple, not the runner. --- .github/workflows/release.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 44c9dda..a823314 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -111,12 +111,10 @@ jobs: fail-fast: false matrix: include: - # macos-13 (x86_64) runners have been queueing indefinitely; disabled - # for now. Re-enable once runner availability improves. - # - target: x86_64-apple-darwin - # runner: macos-13 + - target: x86_64-apple-darwin + runner: macos-26-intel - target: aarch64-apple-darwin - runner: macos-14 + runner: macos-26 steps: - uses: actions/checkout@v6 From 09535027125ca74794c873f626acface90a03709 Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Thu, 28 May 2026 11:08:36 -0500 Subject: [PATCH 17/28] Tighten up the release docs in CONTRIBUTING.md --- CONTRIBUTING.md | 41 ++++++++--------------------------------- 1 file changed, 8 insertions(+), 33 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2a94024..c4aaedf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -138,14 +138,9 @@ Understanding the build process helps when debugging issues: The CI workflow (`.github/workflows/python-ci.yml`) validates formatting, linting, and tests on every push and pull request. The release workflow (`.github/workflows/release.yml`) builds binary wheels for all supported -platforms (Linux x86_64/aarch64, macOS x86_64/aarch64) and attaches them +platforms (`Linux x86_64/aarch64`, `macOS x86_64/aarch64`) and attaches them to a GitHub pre-release. -Windows is not currently supported: the pinned version of `componentize-py` -calls a POSIX `configure` script unconditionally when building CPython for -WASI. Newer releases of `componentize-py` have resolved this; Windows support -can be re-enabled when upgrading the dependency. - ## Performing a Release Releases are driven by a git tag. The release workflow builds binary wheels @@ -170,38 +165,18 @@ make check-version-tag TAG=v0.2.0 make check-version-tag TAG=v0.2.0 ``` -3. Commit and tag: - ```bash - git add pyproject.toml crates/fastly-compute-py/Cargo.toml - git commit -m "Bump version to 0.2.0" +3. PR the changes and land into main. + +4. Push tag (make sure you are on the right sha first) + ``` git tag v0.2.0 git push origin v0.2.0 ``` - + 4. The release workflow runs automatically. Jobs: `check-version` (fails fast on any mismatch) → parallel wheel builds → `collect-wheels` → `create-github-release`. -5. Install a wheel from the GitHub Release to validate before promoting to PyPI: - ```bash - pip install https://github.com/fastly/compute-sdk-python/releases/download/v0.2.0/.whl - ``` - -6. Promote to PyPI once validated (via trusted publishing, configured separately). - -### Ad-hoc test builds - -To build wheels without tagging, trigger the workflow manually from -**Actions → Release → Run workflow** with a version label (e.g. `v0.2.0-rc1`). -Wheels are uploaded as Actions artifacts; no release is created. The in-tree -versions must still match the label you provide. - -### If the version check fails - -Fix the mismatch, then retag: -```bash -git tag -f v0.2.0 -git push --force origin v0.2.0 -``` - +5. (Pending) If the release is built successfully, it will make its way to PyPI + via trusted publishing. From 185fcdf04d3f6e01e871dc04d46d2eda1235b5dd Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Thu, 28 May 2026 11:35:23 -0500 Subject: [PATCH 18/28] `make format` --- scripts/check_version_sync.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/scripts/check_version_sync.py b/scripts/check_version_sync.py index bfae6c0..ca79cdb 100755 --- a/scripts/check_version_sync.py +++ b/scripts/check_version_sync.py @@ -72,9 +72,15 @@ def main() -> int: if pyproject_version == cargo_version: print(f"✓ pyproject.toml and Cargo.toml are synchronized: {pyproject_version}") else: - print("✗ Version mismatch between pyproject.toml and Cargo.toml:", file=sys.stderr) - print(f" pyproject.toml: {pyproject_version}", file=sys.stderr) - print(f" crates/fastly-compute-py/Cargo.toml: {cargo_version}", file=sys.stderr) + print( + "✗ Version mismatch between pyproject.toml and Cargo.toml:", file=sys.stderr + ) + print( + f" pyproject.toml: {pyproject_version}", file=sys.stderr + ) + print( + f" crates/fastly-compute-py/Cargo.toml: {cargo_version}", file=sys.stderr + ) print("\nUpdate both files to use the same version.", file=sys.stderr) ok = False @@ -85,10 +91,18 @@ def main() -> int: else: print(f"✗ In-tree versions do not match tag '{args.tag}':", file=sys.stderr) if pyproject_version != expected: - print(f" pyproject.toml: {pyproject_version} (expected {expected})", file=sys.stderr) + print( + f" pyproject.toml: {pyproject_version} (expected {expected})", + file=sys.stderr, + ) if cargo_version != expected: - print(f" crates/fastly-compute-py/Cargo.toml: {cargo_version} (expected {expected})", file=sys.stderr) - print("\nBump both files to match the tag before releasing.", file=sys.stderr) + print( + f" crates/fastly-compute-py/Cargo.toml: {cargo_version} (expected {expected})", + file=sys.stderr, + ) + print( + "\nBump both files to match the tag before releasing.", file=sys.stderr + ) ok = False return 0 if ok else 1 From c45cb6c2fbefd6443a0b45e82c8e82be60d9b92b Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Thu, 28 May 2026 13:28:51 -0500 Subject: [PATCH 19/28] Add sdist alongside wheels --- .github/workflows/release.yml | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a823314..c350b7f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -185,6 +185,28 @@ jobs: # CPython for WASI. Newer releases of componentize-py have Windows support; # re-enable this job when upgrading componentize-py. + # Build a source distribution for PyPI alongside the binary wheels. + build-sdist: + name: "Build sdist" + runs-on: ubuntu-24.04 + needs: [check-version] + steps: + - uses: actions/checkout@v6 + + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + maturin-version: ${{ env.MATURIN_VERSION }} + command: sdist + args: --out dist + + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: sdist + path: dist/*.tar.gz + if-no-files-found: error + # Collect all wheels into a single artifact for convenient download. collect-wheels: name: "Collect all wheels" @@ -241,7 +263,7 @@ jobs: # leaving wheels as Actions artifacts for download and local testing. create-github-release: name: "Create GitHub Release" - needs: [generate-checksums] + needs: [generate-checksums, build-sdist] runs-on: ubuntu-24.04 if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') permissions: @@ -262,12 +284,18 @@ jobs: name: checksums path: dist/ + - name: Download sdist + uses: actions/download-artifact@v4 + with: + name: sdist + path: dist/ + - name: Create GitHub Release env: GH_TOKEN: ${{ github.token }} run: | TAG="${{ github.ref_name }}" - gh release create "$TAG" dist/*.whl dist/checksums.txt \ + gh release create "$TAG" dist/*.whl dist/*.tar.gz dist/checksums.txt \ --title "$TAG" \ --prerelease \ --generate-notes \ From 6b324be43c500b596f688689bd3d5a13b5de3321 Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Thu, 28 May 2026 16:57:51 -0500 Subject: [PATCH 20/28] Drop make targets for checks, just do with lint The python script still supports feeding in a tag as an additional check, but the makefile wrapper and instructions around that were realistically overly complicated. --- CONTRIBUTING.md | 10 +++------- Makefile | 9 +-------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c4aaedf..2c9777a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -150,11 +150,7 @@ The version must be kept in sync across two files: - `pyproject.toml` — `[project] version` - `crates/fastly-compute-py/Cargo.toml` — `[package] version` -`make lint` checks these are in sync. To also validate against a specific tag: - -```bash -make check-version-tag TAG=v0.2.0 -``` +`make lint` checks these are in sync. ### Steps @@ -162,7 +158,7 @@ make check-version-tag TAG=v0.2.0 2. Verify locally: ```bash - make check-version-tag TAG=v0.2.0 + make lint ``` 3. PR the changes and land into main. @@ -174,7 +170,7 @@ make check-version-tag TAG=v0.2.0 ``` 4. The release workflow runs automatically. Jobs: `check-version` (fails fast - on any mismatch) → parallel wheel builds → `collect-wheels` → + on any mismatch) → parallel wheel + sdist builds → `collect-artifacts` → `create-github-release`. 5. (Pending) If the release is built successfully, it will make its way to PyPI diff --git a/Makefile b/Makefile index 177fb6b..99e1d11 100644 --- a/Makefile +++ b/Makefile @@ -92,13 +92,6 @@ clean: cd crates/fastly-compute-py && cargo clean # Development tools -check-version: - uv run python scripts/check_version_sync.py - -check-version-tag: - @test -n "$(TAG)" || (echo "Usage: make check-version-tag TAG=v0.1.0" && exit 1) - uv run python scripts/check_version_sync.py --tag $(TAG) - lint: fastly_compute/runtime_patching/patches.py | $(STUBS_DIR) @echo "Checking version synchronization..." uv run python scripts/check_version_sync.py @@ -163,4 +156,4 @@ help: @echo "" @echo "Available examples: $(EXAMPLES)" -.PHONY: all serve test test-update-snapshots list-examples build-all clean lint lint-fix format format-check check-version check-version-tag help +.PHONY: all serve test test-update-snapshots list-examples build-all clean lint lint-fix format format-check help From ad25b7f1875b6de7c8f2dabc1dfa02f9e7beb474 Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Thu, 28 May 2026 17:12:08 -0500 Subject: [PATCH 21/28] release: drop source/binary caches Both of these were keyed off Cargo.lock which changes with version bumps. Given the limited TTL on these caches, they would be unlikely to provide any value in practice for releases which are expected to be relatively infrequent. --- .github/workflows/release.yml | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c350b7f..2b884ff 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -125,31 +125,6 @@ jobs: rust-stable: ${{ env.RUST_STABLE }} rust-nightly: ${{ env.RUST_NIGHTLY }} - # Cache Rust registry/git sources — keyed on Cargo.lock only so they - # are shared across runs with the same lockfile. - - name: Cache Rust sources - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - ~/.cargo/git/checkouts/ - key: cargo-sources-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-v1 - restore-keys: | - cargo-sources-${{ runner.os }}- - - # Build artifacts keyed on Cargo.lock + toolchain to avoid incompatible - # artifact reuse across toolchain upgrades. - - name: Cache Rust build artifacts - uses: actions/cache@v4 - with: - path: target/ - key: cargo-target-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE }}-v1 - restore-keys: | - cargo-target-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}- - cargo-target-${{ runner.os }}-${{ matrix.target }}- - - name: Install wasm-tools run: | ARCH=$(uname -m) From 42ce28dd80f29ad5b932d4af6f25987c78b13772 Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Thu, 28 May 2026 17:13:13 -0500 Subject: [PATCH 22/28] release: remove confusing/unecessary language related to nightly toolchain --- rust-toolchain.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 8d37d2b..d617235 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -5,8 +5,7 @@ # # A nightly toolchain is also required at build time: componentize-py's # build.rs uses `-Z build-std` to compile its wasm32-wasip1 runtime with -# position-independent code, which is a nightly-only Cargo flag. This cannot -# be avoided with stable Rust, it is a hard upstream requirement. +# position-independent code, which is a nightly-only/unstable Cargo flag. # # The nightly version is pinned in the CI workflows (RUST_NIGHTLY env var). # For local development, install it with: From 798847e84515c0af337e8dbef4efb26faeb0a414 Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Thu, 28 May 2026 17:15:55 -0500 Subject: [PATCH 23/28] release: include sdist in checksums, unify artifact collection THis change unifies sdist, wheel, and checksum computation into a unified job that can be depended on a bit more easily. This also makes it so that checksums are computed on sdist as well as wheels. --- .github/workflows/release.yml | 66 +++++++++++------------------------ 1 file changed, 20 insertions(+), 46 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2b884ff..7a4a4d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -182,10 +182,11 @@ jobs: path: dist/*.tar.gz if-no-files-found: error - # Collect all wheels into a single artifact for convenient download. - collect-wheels: - name: "Collect all wheels" - needs: [build-linux, build-macos] + # Collect all release artifacts (wheels + sdist) into a single artifact, + # generate SHA256 checksums, and upload everything together. + collect-artifacts: + name: "Collect release artifacts" + needs: [build-linux, build-macos, build-sdist] runs-on: ubuntu-24.04 steps: @@ -196,49 +197,34 @@ jobs: path: dist/ merge-multiple: true - - name: List collected wheels - run: ls -lh dist/ - - - name: Upload combined artifact - uses: actions/upload-artifact@v4 - with: - name: all-wheels - path: dist/*.whl - if-no-files-found: error - - # Generate SHA256 checksums for all wheels and attach them to the release. - # pip and uv can verify wheels against published checksums. - generate-checksums: - name: "Generate checksums" - needs: [collect-wheels] - runs-on: ubuntu-24.04 - - steps: - - name: Download all wheels + - name: Download sdist uses: actions/download-artifact@v4 with: - name: all-wheels + name: sdist path: dist/ - name: Generate SHA256 checksums run: | cd dist - sha256sum *.whl > checksums.txt + sha256sum *.whl *.tar.gz > checksums.txt cat checksums.txt - - name: Upload checksums artifact + - name: List artifacts + run: ls -lh dist/ + + - name: Upload combined artifact uses: actions/upload-artifact@v4 with: - name: checksums - path: dist/checksums.txt + name: release-artifacts + path: dist/ if-no-files-found: error - # Create a GitHub Release and attach all wheels. - # Only runs on tag pushes — workflow_dispatch builds stop at collect-wheels, - # leaving wheels as Actions artifacts for download and local testing. + # Create a GitHub Release and attach all artifacts. + # Only runs on tag pushes, workflow_dispatch builds stop at collect-artifacts, + # presumed to be used for testing CI or related. create-github-release: name: "Create GitHub Release" - needs: [generate-checksums, build-sdist] + needs: [collect-artifacts] runs-on: ubuntu-24.04 if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') permissions: @@ -247,22 +233,10 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Download all wheels - uses: actions/download-artifact@v4 - with: - name: all-wheels - path: dist/ - - - name: Download checksums + - name: Download release artifacts uses: actions/download-artifact@v4 with: - name: checksums - path: dist/ - - - name: Download sdist - uses: actions/download-artifact@v4 - with: - name: sdist + name: release-artifacts path: dist/ - name: Create GitHub Release From 0a64f918885cda2952d652d2f3fd7c7b99781925 Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Thu, 28 May 2026 17:53:39 -0500 Subject: [PATCH 24/28] Remove wasm-tools shell calls from build.rs Instead, use APIs on wit-parser and wit-component in order to achieve the same thing. This gives us better control over dependency versions and removes an external build-time dependency on the host machine. --- crates/fastly-compute-py/build.rs | 64 ++++++++++++++++++------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/crates/fastly-compute-py/build.rs b/crates/fastly-compute-py/build.rs index a080685..3b3b5ad 100644 --- a/crates/fastly-compute-py/build.rs +++ b/crates/fastly-compute-py/build.rs @@ -3,6 +3,8 @@ use std::env; use std::fs; use std::path::Path; use std::path::PathBuf; +use wit_component::WitPrinter; +use wit_parser::Resolve; fn main() -> Result<()> { println!("cargo:rerun-if-changed=../../wit"); @@ -44,21 +46,31 @@ fn main() -> Result<()> { } fn generate_merged_wit(source_wit_dir: &PathBuf, out_dir: impl AsRef) -> Result<()> { - // Generate merged WIT file using wasm-tools - let merged_wit_path = out_dir.as_ref().join("merged.wit"); - let output = std::process::Command::new("wasm-tools") - .arg("component") - .arg("wit") - .arg(source_wit_dir) - .output() - .context("Failed to run wasm-tools")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - anyhow::bail!("wasm-tools component wit failed: {}", stderr); - } + let mut resolve = Resolve { + all_features: true, + ..Default::default() + }; + let (main_pkg, _) = resolve.push_path(source_wit_dir).with_context(|| { + format!( + "failed to parse WIT directory: {}", + source_wit_dir.display() + ) + })?; + + // Collect all package IDs and remove the main pkg + let nested_pkgs: Vec<_> = resolve + .packages + .iter() + .map(|(id, _)| id) + .filter(|&id| id != main_pkg) + .collect(); + + let merged = WitPrinter::default() + .print(&resolve, main_pkg, &nested_pkgs) + .context("failed to print merged WIT")?; - fs::write(&merged_wit_path, &output.stdout)?; + let merged_wit_path = out_dir.as_ref().join("merged.wit"); + fs::write(&merged_wit_path, merged)?; Ok(()) } @@ -92,22 +104,22 @@ fn build_wasiless_wasm(root_dir: impl AsRef, out_dir: impl AsRef) -> anyhow::bail!("Failed to build wasiless"); } - // Transform wasiless into a component using wasm-tools component new + // Wrap the core wasm module into a wasm component using wit-component's + // ComponentEncoder, replacing the previous `wasm-tools component new` call. let input_wasm = target_dir.join("wasm32-unknown-unknown/release/wasiless.wasm"); let output_wasm = out_dir.as_ref().join("wasiless.wasm"); - let status = std::process::Command::new("wasm-tools") - .arg("component") - .arg("new") - .arg(&input_wasm) - .arg("-o") - .arg(&output_wasm) - .status() - .context("Failed to run wasm-tools component new")?; + let module_bytes = fs::read(&input_wasm) + .with_context(|| format!("failed to read {}", input_wasm.display()))?; - if !status.success() { - anyhow::bail!("Failed to componentize wasiless"); - } + let component_bytes = wit_component::ComponentEncoder::default() + .module(&module_bytes) + .context("failed to set module on ComponentEncoder")? + .encode() + .context("failed to encode wasm component")?; + + fs::write(&output_wasm, component_bytes) + .with_context(|| format!("failed to write {}", output_wasm.display()))?; Ok(()) } From cc0d9c6554c0d3a80ea3b3f8c977d79cf66fa792 Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Thu, 28 May 2026 17:58:35 -0500 Subject: [PATCH 25/28] Script installing pinned nightly with caching, remove wasm-tools/wac-cli With our latest build.rs changes, we now no longer have a build-time or runtime dependency on shelling out to wasm-tools or wac-cli, so we can remove that comlexity entirely. A github specific script is extracted to aid in installing our pinned nightly with caching around that. --- .github/actions/setup-rust/action.yml | 26 ++++++++++------------- .github/scripts/setup-nightly.sh | 30 +++++++++++++++++++++++++++ .github/workflows/python-ci.yml | 7 +------ .github/workflows/release.yml | 23 +------------------- 4 files changed, 43 insertions(+), 43 deletions(-) create mode 100755 .github/scripts/setup-nightly.sh diff --git a/.github/actions/setup-rust/action.yml b/.github/actions/setup-rust/action.yml index 37c4ad8..a08d530 100644 --- a/.github/actions/setup-rust/action.yml +++ b/.github/actions/setup-rust/action.yml @@ -1,9 +1,8 @@ name: "Setup Rust toolchains" description: > Installs the pinned stable and nightly Rust toolchains required by this - project. Nightly is needed solely because componentize-py's build.rs uses - `-Z build-std` to compile its wasm32-wasip1 runtime with PIC, which is a - nightly-only Cargo flag. + project. See .github/scripts/setup-nightly.sh for details on why nightly + is required. inputs: rust-stable: @@ -22,18 +21,15 @@ runs: toolchain: ${{ inputs.rust-stable }} target: wasm32-unknown-unknown components: rustfmt,clippy - # Disable the built-in cargo cache — callers manage their own. + # Disable the built-in cargo cache -- callers manage their own. cache: false - - name: Set up nightly Rust toolchain with rust-src + - name: Cache nightly toolchain + uses: actions/cache@v4 + with: + path: ~/.rustup/toolchains/nightly-* + key: rustup-nightly-${{ runner.os }}-${{ inputs.rust-nightly }}-v1 + + - name: Set up nightly Rust toolchain shell: bash - run: | - # Install the pinned dated nightly. componentize-py's build.rs also - # invokes `rustup run nightly cargo build` using the bare 'nightly' - # name; rustup rejects that name for `toolchain link`, so we install - # the dated toolchain and also install the bare nightly channel. - # Cargo.lock pins all crate versions regardless of compiler date. - rustup toolchain install ${{ inputs.rust-nightly }} --component rust-src - rustup target add wasm32-wasip1 --toolchain ${{ inputs.rust-nightly }} - rustup toolchain install nightly --component rust-src - rustup target add wasm32-wasip1 --toolchain nightly + run: .github/scripts/setup-nightly.sh ${{ inputs.rust-nightly }} diff --git a/.github/scripts/setup-nightly.sh b/.github/scripts/setup-nightly.sh new file mode 100755 index 0000000..1b84c8c --- /dev/null +++ b/.github/scripts/setup-nightly.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Install the nightly Rust toolchain required to build this project. +# +# componentize-py's build.rs uses -Z build-std (nightly-only) to compile its +# wasm32-wasip1 runtime. It also invokes `rustup run nightly cargo build` by +# name, so both the pinned dated nightly and the bare 'nightly' channel must +# be installed; rustup rejects 'nightly' as a toolchain link target. +# +# Both installs are skipped if already present (e.g. restored from cache). +# +# Usage: setup-nightly.sh +# e.g. setup-nightly.sh nightly-2026-04-27 + +set -euo pipefail + +RUST_NIGHTLY="${1:?Usage: $0 }" + +if ! rustup toolchain list | grep -q "^${RUST_NIGHTLY}"; then + rustup toolchain install "$RUST_NIGHTLY" --component rust-src + rustup target add wasm32-wasip1 --toolchain "$RUST_NIGHTLY" +else + echo "Nightly toolchain $RUST_NIGHTLY already installed (cache hit)" +fi + +if ! rustup toolchain list | grep -q "^nightly-[0-9]"; then + rustup toolchain install nightly --component rust-src + rustup target add wasm32-wasip1 --toolchain nightly +else + echo "Bare nightly toolchain already installed (cache hit)" +fi diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 6c399a4..7cb6d82 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -30,7 +30,7 @@ jobs: - name: Install uv run: pip install uv - # Cache cargo binaries (viceroy, wasm-tools, wac-cli). + # Cache cargo binaries (viceroy). - name: Cache cargo binaries id: cache-cargo-bins uses: actions/cache@v4 @@ -38,11 +38,6 @@ jobs: key: cargo-bins-${{ runner.os }}-${{ env.VICEROY_TAG }} path: | ~/.cargo/bin/viceroy* - ~/.cargo/bin/wasm-tools* - ~/.cargo/bin/wac* - - name: Install wasm-tools and wac - if: steps.cache-cargo-bins.outputs.cache-hit != 'true' - run: cargo install wasm-tools --version 1.250.0 --locked && cargo install wac-cli - name: Install viceroy if: steps.cache-cargo-bins.outputs.cache-hit != 'true' run: cargo install --git https://github.com/fastly/Viceroy.git --tag "$VICEROY_TAG" viceroy diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7a4a4d6..424fb47 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,6 @@ env: RUST_STABLE: "1.92.0" RUST_NIGHTLY: "nightly-2026-04-27" MATURIN_VERSION: "v1.13.3" - WASM_TOOLS_VERSION: "1.250.0" jobs: # Verify that the versions in pyproject.toml and Cargo.toml match the tag @@ -79,20 +78,8 @@ jobs: # abi3-py312 is set in [tool.maturin] features; no -i needed. args: --release --locked --compatibility pypi before-script-linux: | - rustup toolchain install ${{ env.RUST_NIGHTLY }} --component rust-src - rustup target add wasm32-wasip1 --toolchain ${{ env.RUST_NIGHTLY }} + .github/scripts/setup-nightly.sh ${{ env.RUST_NIGHTLY }} rustup target add wasm32-unknown-unknown - # componentize-py's build.rs invokes `rustup run nightly cargo build`. - # rustup rejects 'nightly' as a custom link name, so install the - # bare nightly channel separately. Cargo.lock pins crate versions. - rustup toolchain install nightly --component rust-src - rustup target add wasm32-wasip1 --toolchain nightly - # build.rs calls wasm-tools as a host-side subprocess. The container - # always runs on an x86_64 host (CARGO_BUILD_TARGET is set to the - # cross target, so cargo install would produce an aarch64 binary that - # can't execute). Download the x86_64 pre-built binary explicitly. - curl -sSfL "https://github.com/bytecodealliance/wasm-tools/releases/download/v${{ env.WASM_TOOLS_VERSION }}/wasm-tools-${{ env.WASM_TOOLS_VERSION }}-x86_64-linux.tar.gz" \ - | tar -xz --strip-components=1 -C /usr/local/bin "wasm-tools-${{ env.WASM_TOOLS_VERSION }}-x86_64-linux/wasm-tools" - name: Upload wheels uses: actions/upload-artifact@v4 @@ -125,14 +112,6 @@ jobs: rust-stable: ${{ env.RUST_STABLE }} rust-nightly: ${{ env.RUST_NIGHTLY }} - - name: Install wasm-tools - run: | - ARCH=$(uname -m) - # uname -m returns 'arm64' on Apple Silicon; release assets use 'aarch64' - [[ "$ARCH" == "arm64" ]] && ARCH="aarch64" - curl -sSfL "https://github.com/bytecodealliance/wasm-tools/releases/download/v${{ env.WASM_TOOLS_VERSION }}/wasm-tools-${{ env.WASM_TOOLS_VERSION }}-${ARCH}-macos.tar.gz" \ - | tar -xz --strip-components=1 -C /usr/local/bin "wasm-tools-${{ env.WASM_TOOLS_VERSION }}-${ARCH}-macos/wasm-tools" - - name: Set up Python uses: actions/setup-python@v5 with: From d05b79c95409da5f535b086a29e0199da79371aa Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Thu, 28 May 2026 18:17:51 -0500 Subject: [PATCH 26/28] Add back wasm-tools to main ci workflow I was a bit overzealous in removing wasm-tools (for now) and it is still used in codegen steps from python; we can, however, avoid the dependency on the release side of things which simplifies things. --- .github/workflows/python-ci.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 7cb6d82..2a40d08 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -15,6 +15,7 @@ concurrency: env: VICEROY_TAG: v0.16.4 + WASM_TOOLS_VERSION: "1.250.0" jobs: build: @@ -30,14 +31,20 @@ jobs: - name: Install uv run: pip install uv - # Cache cargo binaries (viceroy). + # Cache cargo binaries (viceroy, wasm-tools). + # wasm-tools is required by scripts/generate_patches, which runs as part + # of make lint to verify patches.py is up to date with the WIT sources. - name: Cache cargo binaries id: cache-cargo-bins uses: actions/cache@v4 with: - key: cargo-bins-${{ runner.os }}-${{ env.VICEROY_TAG }} + key: cargo-bins-${{ runner.os }}-${{ env.VICEROY_TAG }}-wasm-tools-${{ env.WASM_TOOLS_VERSION }} path: | ~/.cargo/bin/viceroy* + ~/.cargo/bin/wasm-tools* + - name: Install wasm-tools + if: steps.cache-cargo-bins.outputs.cache-hit != 'true' + run: cargo install wasm-tools --version ${{ env.WASM_TOOLS_VERSION }} --locked - name: Install viceroy if: steps.cache-cargo-bins.outputs.cache-hit != 'true' run: cargo install --git https://github.com/fastly/Viceroy.git --tag "$VICEROY_TAG" viceroy From c6654b4a855e98911ed22c88d2297309021bad7c Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Fri, 29 May 2026 10:27:00 -0500 Subject: [PATCH 27/28] Test nightly install differently The regex we had before wasn't correct on CI; use a `rustup run nightly rustc` invocation instead which should hopefully be more robust. --- .github/scripts/setup-nightly.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/setup-nightly.sh b/.github/scripts/setup-nightly.sh index 1b84c8c..4007592 100755 --- a/.github/scripts/setup-nightly.sh +++ b/.github/scripts/setup-nightly.sh @@ -22,7 +22,7 @@ else echo "Nightly toolchain $RUST_NIGHTLY already installed (cache hit)" fi -if ! rustup toolchain list | grep -q "^nightly-[0-9]"; then +if ! rustup run nightly rustc --version &>/dev/null; then rustup toolchain install nightly --component rust-src rustup target add wasm32-wasip1 --toolchain nightly else From 00be0198f1c5f4170dbad4f3e6ce4edffa2b7f2c Mon Sep 17 00:00:00 2001 From: Paul Osborne Date: Fri, 29 May 2026 10:59:04 -0500 Subject: [PATCH 28/28] release: target MACOSX_DEPLOYMENT_TARGET 10.15 for x86_64 Maturin currently sets a default for x86_64 that isn't compatible with what xcode 26 supports. Tell it to target 10.15 instead which will hopefully solve the problem. If not, we'll need to change the runner for x86_64 mac. --- .github/workflows/release.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 424fb47..3bcd404 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -100,8 +100,14 @@ jobs: include: - target: x86_64-apple-darwin runner: macos-26-intel + # Maturin defaults to 10.12 for x86_64, but componentize-py's + # build.rs compiles a native CPython host binary that requires + # >=10.15 (sqlite3_create_window_function) with Xcode 26's SDK. + # Python 3.12 itself requires 10.13+, so 10.15 is a safe minimum. + macosx_deployment_target: "10.15" - target: aarch64-apple-darwin runner: macos-26 + macosx_deployment_target: "11.0" steps: - uses: actions/checkout@v6 @@ -126,6 +132,8 @@ jobs: container: "off" # abi3-py312 is set in [tool.maturin] features; no -i needed. args: --release --locked + env: + MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }} - name: Upload wheels uses: actions/upload-artifact@v4