Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
cd34a14
first push towards rust bindings
cjpais Jun 13, 2026
2f1d7c4
rust: M4–M6 — conformance CI tiers, Windows/MSVC, dylib posture, exam…
cjpais Jun 13, 2026
094a611
rust: M7 — packaging + publish (rehearsal, shipped-artifact smoke, re…
cjpais Jun 13, 2026
e44f113
rust/cmake: fix Windows static zlib staging (scope-visibility bug)
cjpais Jun 13, 2026
f35e899
rust: fix default-build link failures surfaced by Windows + linux-sta…
cjpais Jun 13, 2026
6cb4fba
diag: capture the x86 cancel-path divide-by-zero backtrace (temporary)
cjpais Jun 13, 2026
97b55a9
diag: trigger diag-cancel on push to rust-bindings (dispatch needs de…
cjpais Jun 13, 2026
77fde1c
fix(whisper.h): MSVC-safe ±INF threshold sentinels (was C2124 on MSVC)
cjpais Jun 13, 2026
efcadcc
diag: shrink the cancel sweep so the debugger run fits the timeout
cjpais Jun 13, 2026
2c21a2a
rust: short-form cancel test; file the native long-form-cancel ÷0 (B)…
cjpais Jun 13, 2026
48fff42
rust: skip in-flight-cancel test on Windows (native whisper abort ÷0)…
cjpais Jun 13, 2026
0786b69
rust: skip both cancel tests + example cancellation on Windows (token…
cjpais Jun 13, 2026
82413af
fix(transcribe.h): TRANSCRIBE_STATIC escape hatch for Windows static …
cjpais Jun 13, 2026
0142da7
diag(B): self-contained int-÷0 backtrace + Release-codegen diag workflow
cjpais Jun 13, 2026
5145b1b
diag(B): pwsh configure step (Git Bash mangled /Z7 and /DEBUG into pa…
cjpais Jun 13, 2026
6d9508f
fix(B): init ggml's MSVC timer before any family load-timing (Windows…
cjpais Jun 13, 2026
4f5f133
diag(B): watchdog thread-dump for the post-÷0-fix Windows hang
cjpais Jun 13, 2026
8c0eea2
diag(B): test OpenMP-ON hypothesis for the MSVC barrier deadlock
cjpais Jun 13, 2026
f6b54d7
fix(B): GGML_OPENMP=ON under MSVC — the non-OpenMP barrier deadlocks …
cjpais Jun 13, 2026
45d5dac
rust: un-skip the Windows cancel tests + example (B fixed)
cjpais Jun 13, 2026
298864d
diag(B): remove diagnostic scaffolding now that B is fixed
cjpais Jun 13, 2026
91d640c
fix(B): scope the ggml-OpenMP MSVC workaround to the Rust binding
cjpais Jun 13, 2026
530bf90
rust: enforce the C "one in-flight compute per model" contract (Error…
cjpais Jun 13, 2026
70a3a0c
rust: set default-features=false on the -sys dependency (feature cont…
cjpais Jun 13, 2026
1fce3aa
rust: surface NUL in initial_prompt + faithful init_backends path bytes
cjpais Jun 13, 2026
70dc9f8
docs: make the Windows/MSVC OpenMP split one documented central policy
cjpais Jun 13, 2026
e1684b4
rust: --no-default-features is now a true CPU build on Apple (Metal OFF)
cjpais Jun 14, 2026
72d5811
rust: free the model compute lease at stream finalize/reset, not just…
cjpais Jun 14, 2026
7d057f1
docs: fix stale path in check_version_sync comment (safe -> transcrib…
cjpais Jun 14, 2026
8b6c7a7
diag: confirm the Python wheel posture (shared, GGML_OPENMP=OFF) dead…
cjpais Jun 14, 2026
46882da
diag: pin down static-vs-shared for the MSVC barrier deadlock
cjpais Jun 14, 2026
b8b0a78
diag: fix pwsh ${i} interpolation (parse error skipped the whole run)
cjpais Jun 14, 2026
4123d6d
diag: remove the Python CPU repro; record the Windows/MSVC barrier de…
cjpais Jun 14, 2026
a5132db
dynamic for rust
cjpais Jun 15, 2026
1fc8345
better dynamic
cjpais Jun 15, 2026
3ffa0d9
remove file
cjpais Jun 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Workspace-wide cargo configuration for the Rust bindings.
[alias]
# `cargo xtask bindgen [--check]` — regenerate / drift-check the committed FFI.
xtask = "run --package xtask --"
4 changes: 2 additions & 2 deletions .github/workflows/native-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ name: native-ci
# hand-assembled provider directory, plus the
# link-smoke in the shared/DL posture (the toy
# consumer drives transcribe_init_backends on the
# installed module dir — the Rust dylib-mode shape).
# installed module dir — the Rust dynamic-backends shape).
# - posture-lint: the wheel-preset <-> pyproject-lane mirror gate
# (scripts/ci/check_lane_mirror.py): anchored lane
# regexes + settled-posture equality. The 2026-06-12
Expand Down Expand Up @@ -186,7 +186,7 @@ jobs:
# Before the build tree is deleted, the shared/DL leg of the link smoke
# runs against `cmake --install` output: the toy consumer links
# libtranscribe alone and drives transcribe_init_backends() on the
# installed module directory — the exact shape a Rust dylib-feature
# installed module directory — the exact shape a Rust dynamic-backends
# consumer has.
runs-on: blacksmith-2vcpu-ubuntu-2404
env:
Expand Down
56 changes: 56 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,59 @@ jobs:
run: |
gh workflow run wheel-index.yml --repo "$GITHUB_REPOSITORY" || \
echo "::warning::wheel-index dispatch failed — is the workflow on the default branch and Pages enabled?"

# ---- Rust crates (crates.io) -------------------------------------------------
# The Rust release path mirrors the Python one: a dispatch REHEARSAL that
# validates without uploading, and a tag-gated RELEASE cut from CI. crates.io
# has no TestPyPI analog, so the rehearsal is `cargo publish --dry-run` plus
# the packed-crate smoke (build libtranscribe from the shipped tarball and
# transcribe) — the Rust "test the shipped artifact" gate (requirements §4).

rust-rehearsal:
# Dispatch only. Independent of the wheel jobs (no `needs`): the Rust and
# Python release artifacts don't share build steps.
if: github.event_name == 'workflow_dispatch'
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 40
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: astral-sh/setup-uv@v8.2.0 # fetch-canary + the packed-smoke script
- name: Install build deps
run: sudo apt-get update && sudo apt-get install -y cmake ninja-build zlib1g-dev
- name: Dry-run transcribe-cpp-sys (full verify build, as cargo would publish it)
run: cargo publish --dry-run --allow-dirty -p transcribe-cpp-sys
# The safe crate cannot be dry-run-verified pre-publish: it depends on
# transcribe-cpp-sys = "0.0.1", and the registry only has the 0.0.0
# name-reservation placeholder until sys actually publishes (sys-first
# ordering). Its packaging is verified at release time below; here the
# packed-crate smoke exercises it against the packed sys instead.
- uses: ./.github/actions/fetch-canary
with:
hf-token: ${{ secrets.HF_TOKEN }}
- name: Packed-crate smoke (build the native lib from the shipped tarball, transcribe)
run: uv run --no-project python scripts/ci/rust_packed_smoke.py

rust-release:
# Tags only. Releases are cut from CI, never a laptop (requirements §5).
# sys publishes FIRST (the safe crate resolves it by version), then the safe
# crate. The `crates-io` environment carries the approval gate (CJ approves)
# and holds CARGO_REGISTRY_TOKEN. cargo waits for the index to carry sys
# before returning, so the safe publish that follows resolves it.
if: startsWith(github.ref, 'refs/tags/v')
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 40
environment: crates-io
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- name: Install build deps
run: sudo apt-get update && sudo apt-get install -y cmake ninja-build zlib1g-dev
- name: Publish transcribe-cpp-sys (the native-carrying crate; verifies the build)
run: cargo publish -p transcribe-cpp-sys
- name: Publish transcribe-cpp (the safe wrapper; resolves the just-published sys)
run: cargo publish -p transcribe-cpp
241 changes: 241 additions & 0 deletions .github/workflows/rust-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
name: rust-ci

# Every-PR gates for the Rust bindings (transcribe-cpp-sys + transcribe-cpp).
# Thin per-binding workflow on the shared rails laid by bindings-shared-infra:
# the binding-agnostic C contracts are certified in native-ci.yml; this file
# adds only what the Rust layer introduces.
#
# - rust-gates: bindgen drift check (pinned to include/transcribe.abihash),
# version-sync, the cargo-package content/size audit, and
# rustfmt. No native build — fast, runs everywhere.
# - rust-build: the real source build (build.rs drives the vendored CMake
# tree, links from lib/transcribe-link.json) across the posture
# matrix on linux + macos + windows, then the -sys smoke tests
# (transcribe_version / abi-struct-size through the FFI), the
# safe-crate conformance suite, and clippy. Postures: STATIC
# (default), SHARED (shared libtranscribe), and DYNAMIC-BACKENDS
# (linux: shared + loadable per-ISA backend modules, exercised
# through transcribe_init_backends from Rust).
#
# Two test tiers (requirements §4): the no-model tests (version/ABI/error
# mapping/device discovery, in tests/no_model.rs) always run, including on
# forks. The model-gated tests (real transcription, streaming, cancel, family
# extensions) un-skip only when the canary GGUFs are fetched — the same
# fetch-canary composite action and HF_TOKEN gate the Python lane uses — and
# `return` early (a clean skip) otherwise. One `cargo test` invocation runs
# both tiers; the model tier self-skips when the canary env is absent.
#
# Windows joins here: the first cargo-driven MSVC build of this tree (static
# posture). cl.exe comes from msvc-dev-cmd, zlib from vcpkg as a static lib
# against the dynamic CRT (`x64-windows-static-md`, matching Rust's default /MD
# on x86_64-pc-windows-msvc), resolved by find_package(ZLIB) via
# CMAKE_PREFIX_PATH. The static Windows link manifest's zlib translation lives
# in cmake/transcribe-install.cmake.
#
# Path filters follow native-ci.yml's shape: the binding's own tree plus the
# native paths it compiles from (binding behavior depends on the C side, so
# do not narrow). Branch protection is OFF (project decision, 2026-06-13) — no
# required-check maintenance comes with this workflow.

on:
push:
branches: [main]
paths: &paths
- "bindings/rust/**"
- "Cargo.toml"
- "Cargo.lock"
- ".cargo/**"
- "src/**"
- "include/**"
- "ggml/**"
- "cmake/**"
- "CMakeLists.txt"
- "CMakePresets.json"
- "scripts/ci/rust_package_audit.py"
- "bindings/python/_generate/check_version_sync.py"
- ".github/workflows/rust-ci.yml"
pull_request:
paths: *paths
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
rust-gates:
# Cheap, native-build-free gates. The drift check needs libclang (bindgen);
# users never do, because the generated FFI is committed.
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v8.2.0 # for the python gate scripts
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: Install libclang (bindgen drift check)
run: sudo apt-get update && sudo apt-get install -y libclang-dev clang
- name: FFI drift gate (bindgen vs committed, pinned to transcribe.abihash)
run: cargo xtask bindgen --check
- name: rustfmt
run: cargo fmt --all --check
- name: Version sync (header <-> every active manifest)
run: uv run --no-project python bindings/python/_generate/check_version_sync.py
- name: Crate package audit (contents + 10 MB size cap)
run: uv run --no-project python scripts/ci/rust_package_audit.py

rust-build:
name: rust-build (${{ matrix.label }})
strategy:
fail-fast: false
# Posture matrix: {static, shared} × {linux, macos, windows}, plus a
# linux dynamic-backends leg. `features` is forwarded verbatim to the
# build/test cargo commands ("" = the static default; "--features shared"
# = a shared libtranscribe; "--features dynamic-backends" = shared +
# loadable backend modules). The shared leg is the end-to-end proof of the
# shared-link posture: its tests load libtranscribe at runtime via the
# rpath the safe crate's build.rs emits on Unix, and on Windows (which has
# no rpath) via the -sys build.rs staging transcribe.dll + the ggml DLLs
# next to each cargo artifact. The dynamic-backends leg additionally proves
# the loadable-module path: the build configures GGML_BACKEND_DL +
# (x86) GGML_CPU_ALL_VARIANTS, and the tests/examples call
# init_backends_default() to load the per-ISA CPU module and transcribe.
matrix:
include:
- label: linux-static
runner: blacksmith-2vcpu-ubuntu-2404
features: ""
- label: linux-shared
runner: blacksmith-2vcpu-ubuntu-2404
features: "--features shared"
# Loadable per-ISA backend modules (the multi-ISA CPU provider posture)
# exercised through Rust: build.rs turns on BACKEND_DL + CPU_ALL_VARIANTS
# on x86, the modules install into the -sys prefix, and the safe crate's
# init_backends_default() loads them. CPU-only (no Vulkan SDK needed);
# the C-level Vulkan degradation three-tier stays in native-ci.
- label: linux-dynamic-backends
runner: blacksmith-2vcpu-ubuntu-2404
features: "--features dynamic-backends"
# macOS arm64 on the bare-metal M4 mini (all macOS arm64 CI on owned
# hardware; the source build turns Metal on, and real hardware is the
# trustworthy place to exercise it). Same posture as native-ci.
- label: macos-arm64-static
runner: [self-hosted, macOS, ARM64]
features: ""
- label: macos-arm64-shared
runner: [self-hosted, macOS, ARM64]
features: "--features shared"
# Windows / MSVC (Blacksmith windows-2025, same image family as the
# wheel lane). First cargo-driven MSVC build of this tree; no ccache
# here (MSVC + ccache is fiddly), so every run is a cold ggml build —
# hence the wider timeout. sccache is a later optimization. Both
# postures: static is self-contained; shared links transcribe.dll (the
# -sys build.rs stages the DLLs next to the test/example binaries).
- label: windows-static
runner: blacksmith-2vcpu-windows-2025
features: ""
- label: windows-shared
runner: blacksmith-2vcpu-windows-2025
features: "--features shared"
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
env:
# Gates the model-test tier: present on this repo's branches, absent on
# forks (where fetch-canary skips and the model tests skip cleanly).
HF_TOKEN: ${{ secrets.HF_TOKEN }}
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: astral-sh/setup-uv@v8.2.0 # fetch-canary fetches via uvx
- name: Enable ccache compiler launcher (non-Windows)
# CMake reads these at first configure, so build.rs's cmake-crate build
# picks up ccache with no -D plumbing. NOT set on Windows: ccache isn't
# installed there and an unresolvable launcher fails configure.
if: runner.os != 'Windows'
shell: bash
run: |
echo "CMAKE_C_COMPILER_LAUNCHER=ccache" >> "$GITHUB_ENV"
echo "CMAKE_CXX_COMPILER_LAUNCHER=ccache" >> "$GITHUB_ENV"
- name: Install build deps (Linux)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y cmake ninja-build zlib1g-dev ccache
- name: Install build deps (macOS)
if: runner.os == 'macOS'
run: |
brew install ninja
command -v ccache >/dev/null || brew install ccache
- name: Set up MSVC (cl.exe on PATH for the Ninja generator)
# The cmake-crate build uses the Ninja generator (CMAKE_GENERATOR below);
# Ninja needs cl.exe ambient. Without vcvars, CMake silently falls back
# to the image's MinGW gcc (gcc-flavored M_PI errors) — same trap the
# wheel lane documents.
if: runner.os == 'Windows'
uses: ilammy/msvc-dev-cmd@v1
with:
arch: x64
- name: Install build deps (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
choco install ninja --no-progress -y
# Static zlib against the DYNAMIC CRT (-static-md): matches Rust's
# default /MD on x86_64-pc-windows-msvc. A /MT (x64-windows-static)
# zlib would clash with the CRT rustc links. find_package(ZLIB)
# resolves it from CMAKE_PREFIX_PATH — no vcpkg toolchain file needed.
vcpkg install zlib:x64-windows-static-md
# Forward slashes: backslashes get eaten as escapes downstream.
$zlibPrefix = "$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows-static-md" -replace '\\','/'
Add-Content $env:GITHUB_ENV "CMAKE_PREFIX_PATH=$zlibPrefix"
# cmake-crate honors CMAKE_GENERATOR from the environment.
Add-Content $env:GITHUB_ENV "CMAKE_GENERATOR=Ninja"
- name: CPU ISA signature (segregates ccache across the heterogeneous fleet)
# ggml builds with -march=native in this source posture; ccache hashes
# the literal flag, not the resolved ISA, so key the cache by the CPU's
# feature flags (matches native-ci.yml).
if: runner.os == 'Linux'
run: echo "CPU_SIG=$(grep -m1 '^flags' /proc/cpuinfo | sha256sum | cut -c1-8)" >> "$GITHUB_ENV"
- name: ccache (compile cache for the native source build)
if: runner.os == 'Linux' # the mini is persistent; its local cache suffices
uses: actions/cache@v5
with:
path: ~/.cache/ccache
key: ccache-rust-build-${{ matrix.label }}-${{ env.CPU_SIG }}-${{ github.sha }}
restore-keys: ccache-rust-build-${{ matrix.label }}-${{ env.CPU_SIG }}-
# Package-scoped (not --workspace): a single posture's native build, and
# it sidesteps the backend features choking on the xtask member, which has
# none. xtask is compiled/run in rust-gates (the bindgen check).
- name: Build (${{ matrix.label }} posture; build.rs drives CMake)
run: cargo build -p transcribe-cpp ${{ matrix.features }} --verbose
- name: "-sys smoke (transcribe_version / abi size through the FFI)"
run: cargo test -p transcribe-cpp-sys ${{ matrix.features }}
# The canary GGUFs upgrade the safe-crate suite from no-model gates to real
# transcription/streaming/cancel/extension coverage. Guarded on HF_TOKEN
# (forks have none): without it the model tests skip cleanly, leaving the
# always-on no-model tier. jfk.wav ships in-repo, so only the two model
# paths need exporting (fetch-canary handles that).
- uses: ./.github/actions/fetch-canary
with:
hf-token: ${{ secrets.HF_TOKEN }}
- name: Safe-crate conformance (no-model tier always; model tier when canary present)
run: cargo test -p transcribe-cpp ${{ matrix.features }}
# The 5 canonical examples (requirements §6), run headless under the same
# canary skip rules as the model tests: each resolves its model/audio from
# the TRANSCRIBE_SMOKE_* env fetch-canary exports (or skips cleanly + exits
# 0). shell:bash so the loop is identical on the windows runner (Git Bash).
- name: Examples (CI-executed; §6 Rosetta set)
shell: bash
run: |
for ex in transcribe-file streaming batch backend-select error-handling; do
echo "=== example: $ex ==="
cargo run -p transcribe-cpp --example "$ex" ${{ matrix.features }}
done
- name: Clippy (deny warnings)
# Once is enough; the linux-static leg covers it (clippy on another
# leg would force a second full native build for no extra lint signal).
if: matrix.label == 'linux-static'
run: cargo clippy --workspace --all-targets -- -D warnings
- name: ccache stats
if: runner.os == 'Linux'
run: ccache -s | head -8
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
/build-*/
/cmake-build-*/

# Rust workspace build output (Cargo.lock IS committed — workspace has a binary)
/target/

# Python distribution output (provider wheels/sdists at the repo root;
# bindings/python/dist for the pure API package; wheelhouse* from local
# cibuildwheel / wheel-repair runs)
Expand Down
Loading
Loading