bela-sys 0.5.0

Raw FFI bindings to the Bela core API (libbela) for Bela Gem
Documentation

bela-sys

Raw FFI bindings to the Bela core API (libbela) for Bela Gem on PocketBeagle 2 (aarch64-unknown-linux-gnu).

Vendored headers

vendor/bela/ contains Bela.h, GPIOcontrol.h and Utilities.h (the include closure of wrapper.h) plus the upstream LICENSE. The provenance is recorded in vendor/bela/SOURCE. These files are LGPL 3.0 (see vendor/bela/LICENSE); the rest of the crate is MIT OR Apache-2.0.

The headers are taken from the board, not from BelaPlatform/Bela: the Bela Gem image ships Bela 1.18.0, which is newer than any published branch (see docs/board-facts.md).

scripts/update-vendor.sh --board          # from root@bela.local
scripts/update-vendor.sh <branch|commit>  # from upstream git

Because the pin follows a board image rather than a released version, a new image moves the headers on the board without changing anything here. After updating one, compare the two:

cargo xtask check-vendor --board          # against root@bela.local

It reports the BELA_*_VERSION macros on both sides and diffs every vendored file against the board's copy, exiting non-zero on drift — which is the signal to re-run the update script and regenerate the bindings below. It needs a board, so CI cannot run it.

Why vendored files instead of a git submodule

  • The include closure is three files (~70 KB); a submodule would drag in the whole upstream repository (IDE, examples, PRU firmware, history) for every clone and CI run.
  • src/bindings.rs is committed, and vendoring keeps "these headers" and "the bindings generated from them" atomic in one commit — a submodule can drift ahead of the generated code, and its bumps show up as opaque hash changes instead of reviewable header diffs.
  • The pin tracks the exact Bela version shipped on the board, which does not correspond to any published upstream commit. File copies can come from anywhere; a submodule can only point at upstream commits.
  • A plain git clone always builds — no --recursive, no submodule initialisation failure modes.

The trade-off is that provenance rests on scripts/update-vendor.sh recording the source in vendor/bela/SOURCE, rather than on git itself.

Regenerating the bindings

src/bindings.rs is generated but committed, so building this crate requires neither libclang nor an aarch64 sysroot. Regenerate it after updating the vendored headers:

cargo xtask bindgen --sysroot <dir>   # or set BELA_SYSROOT

The sysroot is the one synced from the board (see docs/cross-compile.md); bindgen needs it for the libc headers Bela.h includes.

The MIDI shim

shim/midi.h and shim/midi.cpp are a C surface over Bela's Midi class, which is C++ and lives in libbelaextra. They are the one part of this crate that is neither generated nor a declaration of Bela's own C API: Bela ships a partial C surface in libraries/Midi/Midi_c.h, and the shim is that file written again with output, port listing, and error reporting that distinguishes a port that opened from one that did not. docs/midi.md records why, and what the class does on the audio thread.

build.rs compiles it with the cc crate when the sysroot carries Bela's libraries/Midi sources, and skips it — with a warning — when it does not, so that a check without a sysroot still works. The compiler comes from BELA_CXX, or from BELA_CC when that ends in gcc, and the archiver follows the compiler's name; see docs/cross-compile.md. That choice is the one piece of the build script with more than one answer, so it lives in shim_compiler.rs and is tested from src/lib.rscargo test does not build a build script.

The class is LGPL 3.0, like the vendored headers; the shim reaches it by linking libbelaextra.so dynamically.

Linking

build.rs emits the link flags for libbela on device targets: the library search paths from docs/board-facts.md (prefixed with BELA_SYSROOT when cross-compiling) and -lbelaextra -lbela plus the C++ runtime and transitive dependencies (seasocks, evl, stdc++) that Rust does not link on its own.

The order of those two matters: libbelaextra.so needs symbols from libbela.so without naming it in its own DT_NEEDED, and rustc links with --as-needed, which drops a libbela that appears before the library needing it.

On non-device targets it emits nothing, so host builds and cargo check/clippy for the target work without a sysroot.