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).
|
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:
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.rsis 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 clonealways 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:
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.rs — cargo 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.