python bindings #3
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: python-bindings | |
| # Locks in what the Python bindings already guarantee (see | |
| # docs/python-bindings-distribution-plan.md, Milestone 1): | |
| # - lint: generated-FFI drift gate + version-sync across the three | |
| # sources of truth (header / pyproject / __init__). | |
| # - cpp-tests: the full C++ white-box suite against a static build. | |
| # - python-shared: a shared libtranscribe, the pure-C api_smoke as an | |
| # exported-symbol canary, and the Python no-model tests | |
| # (import, ABI layout, version gate, status/enum agreement). | |
| # | |
| # Real-model tests are intentionally not run here: they need multi-GB GGUFs CI | |
| # cannot ship. They live behind env vars and skip cleanly. A tiny redistributable | |
| # canary GGUF (plan M1) will later let one real transcription run in CI. | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - "bindings/python/**" | |
| - "include/**" | |
| - "src/**" | |
| - "tests/**" | |
| - "ggml/**" | |
| - "CMakeLists.txt" | |
| - ".github/workflows/python-bindings.yml" | |
| pull_request: | |
| paths: | |
| - "bindings/python/**" | |
| - "include/**" | |
| - "src/**" | |
| - "tests/**" | |
| - "ggml/**" | |
| - "CMakeLists.txt" | |
| - ".github/workflows/python-bindings.yml" | |
| workflow_dispatch: | |
| # The generated FFI layer is pinned to one libclang so the drift gate's embedded | |
| # `libclang: <version>` line is reproducible across runs. | |
| env: | |
| LIBCLANG_PIN: "libclang==18.1.1" | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| lint: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install uv | |
| run: | | |
| curl -LsSf https://astral.sh/uv/install.sh | sh | |
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| - name: Install clang (libclang resource-dir headers) | |
| run: sudo apt-get update && sudo apt-get install -y clang | |
| - name: Generated-FFI drift gate | |
| run: | | |
| uv run --no-project --with "$LIBCLANG_PIN" \ | |
| bindings/python/_generate/generate.py --check | |
| - name: Version sync (header / pyproject / __init__) | |
| run: uv run --no-project bindings/python/_generate/check_version_sync.py | |
| cpp-tests: | |
| name: cpp-tests (${{ matrix.os }}) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-14] | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install uv (fixtures are generated via uv) | |
| run: | | |
| curl -LsSf https://astral.sh/uv/install.sh | sh | |
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| - name: Install build deps (Linux) | |
| if: runner.os == 'Linux' | |
| run: sudo apt-get update && sudo apt-get install -y cmake ninja-build zlib1g-dev | |
| - name: Install build deps (macOS) | |
| if: runner.os == 'macOS' | |
| run: brew install ninja | |
| - name: Configure (static white-box build) | |
| run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release | |
| - name: Build | |
| run: cmake --build build -j | |
| - name: Test (full white-box suite) | |
| run: ctest --test-dir build --output-on-failure | |
| cpp-tests-sanitized: | |
| # ASan+UBSan over the white-box suite. This is the lane that certifies | |
| # the C lifetime/ABI contracts the FFI bindings rely on — including the | |
| # params copy-out regression (stream_dispatch_unit), which reproduces a | |
| # ctypes-shaped caller freeing its params right after stream_begin. | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install uv (fixtures are generated via uv) | |
| run: | | |
| curl -LsSf https://astral.sh/uv/install.sh | sh | |
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| - name: Install build deps | |
| run: sudo apt-get update && sudo apt-get install -y cmake ninja-build zlib1g-dev | |
| - name: Configure (static, sanitized) | |
| run: | | |
| cmake -B build-asan -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo \ | |
| -DTRANSCRIBE_SANITIZE=ON | |
| - name: Build | |
| run: cmake --build build-asan -j | |
| - name: Test (white-box suite under ASan+UBSan) | |
| run: ctest --test-dir build-asan --output-on-failure | |
| provider-dl-vulkan: | |
| # Builds the default Linux provider shape — CPU + Vulkan as dynamic | |
| # backend modules (GGML_BACKEND_DL) — assembles it into a flat | |
| # wheel-like directory with $ORIGIN rpaths, deletes the build tree so | |
| # nothing can resolve outside the directory, and proves the Vulkan | |
| # degradation contract on the real runner: | |
| # 1. loader REMOVED (no libvulkan) -> import + CPU devices work, | |
| # vulkan answers unavailable; the module-load failure is quiet. | |
| # 2. mesa lavapipe installed -> the SAME artifacts discover a | |
| # software Vulkan device. | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install uv | |
| run: | | |
| curl -LsSf https://astral.sh/uv/install.sh | sh | |
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| - name: Install build deps (Vulkan SDK pieces + patchelf) | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y cmake ninja-build zlib1g-dev \ | |
| libvulkan-dev glslc patchelf | |
| - name: Configure (DL provider, CPU + Vulkan modules, wheel posture) | |
| run: | | |
| cmake -B build-dl -G Ninja -DCMAKE_BUILD_TYPE=Release \ | |
| -DTRANSCRIBE_BUILD_SHARED=ON \ | |
| -DTRANSCRIBE_GGML_BACKEND_DL=ON \ | |
| -DTRANSCRIBE_VULKAN=ON \ | |
| -DTRANSCRIBE_USE_OPENMP=OFF \ | |
| -DTRANSCRIBE_USE_SYSTEM_BLAS=OFF | |
| - name: Build | |
| run: cmake --build build-dl -j | |
| - name: Assemble flat provider directory ($ORIGIN rpaths, build tree deleted) | |
| run: | | |
| mkdir -p provider | |
| cp -L build-dl/src/libtranscribe.so provider/ | |
| cp -L build-dl/ggml/src/libggml.so.* build-dl/ggml/src/libggml-base.so.* provider/ 2>/dev/null || \ | |
| cp -L build-dl/ggml/src/libggml.so build-dl/ggml/src/libggml-base.so provider/ | |
| cp build-dl/bin/libggml-*.so provider/ | |
| for f in provider/*.so*; do patchelf --set-rpath '$ORIGIN' "$f"; done | |
| ls -la provider/ | |
| rm -rf build-dl # isolation: nothing may resolve outside provider/ | |
| - name: "Tier 1: no Vulkan loader — CPU works, vulkan cleanly unavailable" | |
| run: | | |
| sudo apt-get remove -y libvulkan-dev libvulkan1 || \ | |
| sudo rm -f /usr/lib/x86_64-linux-gnu/libvulkan.so.1* | |
| export TRANSCRIBE_LIBRARY="$PWD/provider/libtranscribe.so" | |
| cat > /tmp/check_no_vulkan.py <<'EOF' | |
| import transcribe_cpp as t | |
| devs = t.backends() | |
| print("devices (no loader):", [(d.name, d.kind) for d in devs]) | |
| assert any(d.kind == "cpu" for d in devs), devs | |
| assert t.backend_available("cpu") | |
| assert not t.backend_available("vulkan"), devs | |
| print("ok: import + CPU fine without a Vulkan loader; vulkan answers False") | |
| EOF | |
| uv run --project bindings/python python /tmp/check_no_vulkan.py | |
| - name: "Tier 2: lavapipe installed — same artifacts discover Vulkan" | |
| run: | | |
| sudo apt-get install -y libvulkan1 mesa-vulkan-drivers | |
| export TRANSCRIBE_LIBRARY="$PWD/provider/libtranscribe.so" | |
| cat > /tmp/check_vulkan.py <<'EOF' | |
| import transcribe_cpp as t | |
| devs = t.backends() | |
| print("devices (lavapipe):", [(d.name, d.kind) for d in devs]) | |
| assert t.backend_available("vulkan"), devs | |
| assert any(d.kind == "vulkan" for d in devs), devs | |
| print("ok: the same provider directory discovers a Vulkan device") | |
| EOF | |
| uv run --project bindings/python python /tmp/check_vulkan.py | |
| python-shared: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install uv | |
| run: | | |
| curl -LsSf https://astral.sh/uv/install.sh | sh | |
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| - name: Install build deps | |
| run: sudo apt-get update && sudo apt-get install -y cmake ninja-build zlib1g-dev | |
| # Shared build in the official-wheel posture (no OpenMP / system BLAS), so | |
| # the canary exercises the same configuration the provider wheels ship. | |
| - name: Configure (shared, wheel posture) | |
| run: | | |
| cmake -B build-shared -G Ninja -DCMAKE_BUILD_TYPE=Release \ | |
| -DTRANSCRIBE_BUILD_SHARED=ON \ | |
| -DTRANSCRIBE_USE_OPENMP=OFF \ | |
| -DTRANSCRIBE_USE_SYSTEM_BLAS=OFF | |
| - name: Build | |
| run: cmake --build build-shared -j | |
| - name: api_smoke (exported-symbol canary) + extension umbrella | |
| run: ctest --test-dir build-shared --output-on-failure | |
| - name: Python no-model tests | |
| run: | | |
| # libtranscribe.so is dlopen'd by ctypes; help it resolve its sibling | |
| # ggml shared libs in the build tree. | |
| export LD_LIBRARY_PATH="$(find "$PWD/build-shared" -name '*.so' \ | |
| -printf '%h\n' | sort -u | tr '\n' ':')$LD_LIBRARY_PATH" | |
| # The loader auto-discovers build-shared/src/libtranscribe.so from the | |
| # repo root; model tests skip (no GGUF in CI). | |
| uv run --project bindings/python --extra test \ | |
| pytest bindings/python/tests -q -rs |