Skip to content

Parakeet multitalker (#81) #186

Parakeet multitalker (#81)

Parakeet multitalker (#81) #186

Workflow file for this run

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.
#
# Posture cost split: the model tier (canary fetch + the model-gated tests + the
# examples) runs ONLY on the representative *-static leg of each OS. The *-shared
# and *-dynamic-backends legs are LINK-posture proofs, not marshalling proofs
# (the marshalling code is byte-identical across postures), so they run build +
# the -sys FFI smoke + the always-on no-model tier (which still covers the
# dynamic-backends module load) and skip the canary fetch — dropping the
# duplicated model-tier cost (chiefly the moonshine streaming conformance) from
# the legs that can't catch a marshalling regression anyway.
#
# Windows joins here: the first cargo-driven MSVC build of this tree (static
# posture). cl.exe comes from msvc-dev-cmd; the build has no external library
# dependency to resolve (miniz is vendored into libtranscribe), so there is no
# vcpkg / CMAKE_PREFIX_PATH setup.
#
# 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"
- "tests/cmake/ep-prefix-parent/**"
- "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: ""
model_tier: true # representative leg for linux: full model tier
- label: linux-shared
runner: blacksmith-2vcpu-ubuntu-2404
features: "--features shared"
model_tier: false # link-posture proof only (build + -sys smoke + no-model)
# 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"
model_tier: false # no-model tier still covers the DL module load
# 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: ""
model_tier: true # representative leg for macOS: full model tier
- label: macos-arm64-shared
runner: [self-hosted, macOS, ARM64]
features: "--features shared"
model_tier: false # link-posture proof only
# 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: ""
model_tier: true # representative leg for windows: full model tier
- label: windows-shared
runner: blacksmith-2vcpu-windows-2025
features: "--features shared"
model_tier: false # link-posture proof only
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 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
# 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).
# Model tier runs only on the representative *-static leg per OS. The
# shared / dynamic-backends legs skip the canary fetch, so the model-gated
# tests in the conformance step below self-skip and only the no-model tier
# runs there (build + link posture + FFI surface proof).
- if: matrix.model_tier
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)
if: matrix.model_tier # examples need the canary models; only the model-tier legs have them
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
# Consumer-environment MAX_PATH gates: default VS generator (other Windows
# lanes use Ninja and never exercise MSBuild), long paths OFF, and deep build
# roots. The direct CMake build isolates EP_PREFIX; the Cargo build isolates
# windows_short_out_dir. Do NOT use TrackFileAccess=false here: it races the
# vulkan-shaders-gen ExternalProject steps.
rust-windows-deep-path:
runs-on: blacksmith-2vcpu-windows-2025
timeout-minutes: 60
env:
# LunarG prunes old SDK downloads — when bumping, verify the URL exists.
# Keep in lockstep with cuda-windows.yml and wheel-windows (python-wheels.yml).
VULKAN_VERSION: "1.4.350.0"
steps:
# Registry is read at process start, so all later steps see stock limits.
- name: Force stock path limits (LongPathsEnabled=0)
shell: pwsh
run: Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name LongPathsEnabled -Value 0 -Type DWord
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- name: Install Vulkan SDK ${{ env.VULKAN_VERSION }}
shell: pwsh
run: |
curl.exe -o "$env:RUNNER_TEMP\vulkan_sdk.exe" -L "https://sdk.lunarg.com/sdk/download/${env:VULKAN_VERSION}/windows/vulkan_sdk.exe"
& "$env:RUNNER_TEMP\vulkan_sdk.exe" --accept-licenses --default-answer --confirm-command install
Add-Content $env:GITHUB_ENV "VULKAN_SDK=C:\VulkanSDK\${env:VULKAN_VERSION}"
Add-Content $env:GITHUB_PATH "C:\VulkanSDK\${env:VULKAN_VERSION}\bin"
- name: Direct CMake Vulkan build from a deep path (no Cargo junction)
shell: pwsh
run: |
# Long enough to overflow ggml's original nested ExternalProject
# layout, while the flattened <build>\e\src layout remains safe.
$stem = "$env:GITHUB_WORKSPACE\native-cmake-deep-\build"
$pad = "n" * [Math]::Max(1, 105 - $stem.Length)
$nativeBuild = "$env:GITHUB_WORKSPACE\native-cmake-deep-$pad\build"
"native CMake build root: $($nativeBuild.Length) chars"
cmake -S . -B "$nativeBuild" -G "Visual Studio 17 2022" -A x64 `
-DTRANSCRIBE_VULKAN=ON `
-DTRANSCRIBE_BUILD_TESTS=OFF `
-DTRANSCRIBE_BUILD_EXAMPLES=OFF `
-DTRANSCRIBE_BUILD_TOOLS=OFF
# This target includes the nested shader-generator ExternalProject
# and every generated shader, without recompiling the full ASR tree.
cmake --build "$nativeBuild" --target ggml-vulkan --config Release --parallel 2
$shaderBuild = "$nativeBuild\e\src\vulkan-shaders-gen-build"
if (-not (Test-Path $shaderBuild)) {
throw "flattened Vulkan shader build directory not found: $shaderBuild"
}
- name: Embedded project preserves its ExternalProject prefix
shell: pwsh
run: |
cmake `
-S tests/cmake/ep-prefix-parent `
-B "$env:RUNNER_TEMP\ep-prefix-parent-build" `
-DTRANSCRIBE_SOURCE_DIR="$env:GITHUB_WORKSPACE"
- name: Build from a deep consumer path (default VS generator)
shell: pwsh
run: |
# ~120-char target root: fails un-fixed (>75 overflows 260), but
# under rustc's own MSVC link ceiling (~230, LNK1104).
$pad = "deep-consumer-path-padding-" + ("a" * [Math]::Max(1, 120 - $env:GITHUB_WORKSPACE.Length - 36))
$env:CARGO_TARGET_DIR = "$env:GITHUB_WORKSPACE\$pad\target"
Add-Content $env:GITHUB_ENV "CARGO_TARGET_DIR=$env:CARGO_TARGET_DIR"
"target root: $($env:CARGO_TARGET_DIR.Length) chars"
cargo build -p transcribe-cpp-sys --features vulkan,dynamic-backends --verbose
- name: "-sys smoke through the durable link paths"
# Proves link paths reference OUT_DIR, not the junction.
# CARGO_TARGET_DIR carries over from the build step via GITHUB_ENV.
shell: pwsh
run: cargo test -p transcribe-cpp-sys --features vulkan,dynamic-backends
- name: Assert MAX_PATH margin (junction tree stayed under 260)
# 240 leaves headroom for longer usernames than the runner's.
# -FollowSymlink: junctions are reparse points; without it pwsh never
# descends into the build tree and the gate measures nothing.
shell: pwsh
run: |
$max = 0; $worst = ""
Get-ChildItem "$env:LOCALAPPDATA\tcs" -Recurse -FollowSymlink -ErrorAction SilentlyContinue | ForEach-Object {
if ($_.FullName.Length -gt $max) { $max = $_.FullName.Length; $worst = $_.FullName }
}
"longest build path: $max chars"
$worst
if ($max -eq 0) { throw "no junction tree under $env:LOCALAPPDATA\tcs - the gate measured nothing" }
if ($max -gt 240) { throw "build path margin eroded: $max > 240" }