Add rust_backend/README.md: crate map and dependencies, vendored patches, toolchain, host and Android/iOS build commands (all pass --locked; none pass --offline), NDK version and ABIs, UniFFI binding outputs and consumers, and the test layout. Derived from rust_backend/Cargo.toml, rust-toolchain.toml, crate Cargo.toml files, uniffi.toml, the scripts/ build and check scripts, android/app/build.gradle.kts, ios/Podfile and .github/workflows/ci.yml.
7.5 KiB
Rust Backend
The Rust workspace is the app's only backend on Android and iOS. Flutter
reaches it through the native channel (see android/README.md); Kotlin and
Swift call it through UniFFI bindings generated from spotiflac-mobile.
The toolchain is pinned in rust_backend/rust-toolchain.toml (Rust 1.99.0
with rustfmt and clippy). The workspace uses edition 2024, resolver 3, and
denies unsafe_code as a workspace lint, with a few audited local
#[allow(unsafe_code)] exceptions in the extensions and mobile crates.
Comments that mention "Go" describe the retired Go backend. Its observable behavior (wire formats, parsing quirks, file layouts) remains a compatibility contract.
Crate Map
Dependencies point downward: core and network depend on no other
workspace crate.
| Crate | Package | Depends on |
|---|---|---|
crates/core |
spotiflac-core |
none |
crates/network |
spotiflac-network |
none |
crates/providers |
spotiflac-providers |
core, network |
crates/extensions |
spotiflac-extensions |
core, network, providers |
crates/mobile |
spotiflac-mobile |
core, network, extensions |
crates/bindgen |
spotiflac-bindgen |
uniffi CLI only |
core. Shared backend logic, independent of mobile bindings and UI
runtimes. Modules cover filenames, tags and metadata, covers and palette
extraction, CUE sheets, lyrics, ISRC, matching, download progress state,
Hi-Res analysis and JSON or XML helpers. It uses image, claxon (FLAC
decoding for the Hi-Res check), roxmltree and pinned Unicode tables.
network. Shared, cancellable HTTP transport for the embedded backend: the
client, cookies, DNS lookup, TLS roots, request policy, query and URL
handling. It is built on hyper-util, hyper-rustls and tokio; Apple
targets verify certificates with rustls-platform-verifier, other targets
with rustls-native-certs.
providers. Built-in provider clients and shared lookup services for the native app owners: Deezer, MusicBrainz, lyrics, the resolver, and a request coalescer.
extensions. Extension JavaScript execution and host services, isolated
from mobile UI code. It embeds QuickJS through rquickjs and provides the
host APIs (network, files, crypto, FFmpeg, auth, sessions, storage), the
manifest and package format, the repository, signed sessions and the
download backend. Its main types are ExtensionRuntime and
ExtensionServices.
mobile. The UniFFI mobile API (uniffi::setup_scaffolding!). This crate
is the only backend and routes the app on both platforms. It is built as
lib, cdylib (Android libspotiflac_mobile.so) and staticlib (iOS).
It adds native data jobs, the native library, cache, collections, playlists
and backup, lyrics, ID3 and network tags, rusqlite, and USB audio
(libusb1-sys, Android only).
bindgen. The UniFFI bindgen command line (uniffi_bindgen_main), used
only to generate the Kotlin and Swift bindings. It enables the cli and
cargo-metadata features of uniffi; the build script builds the library
in a separate cargo call so those features do not reach the shipped library.
Vendored Patches
Cargo.toml patches two crates.io packages with copies in
rust_backend/vendor/: rustix 1.1.5 (Android statx fallback) and
libusb1-sys 0.7.0 (libusb 1.0.30 sources). The reasons and the upgrade
checks are in rust_backend/vendor/README.md. Third-party notices are in
rust_backend/NOTICE.
Building
| Command | Result |
|---|---|
bash scripts/build_rust_backend.sh host |
Release host library and Kotlin and Swift bindings |
bash scripts/build_rust_backend.sh android |
Host step, then libspotiflac_mobile.so for each ABI |
bash scripts/build_rust_backend.sh ios |
Host step, then the XCFramework and CocoaPods staging |
bash scripts/check_rust_backend.sh |
cargo fmt --check, cargo clippy -D warnings, cargo test |
Every cargo build, cargo run, cargo clippy and cargo test call in
both scripts passes --locked, so Cargo.lock must be up to date. The
scripts do not pass --offline; dependencies come from crates.io except the
vendored patches.
The host step always runs first. It builds spotiflac-mobile in release
mode, then runs spotiflac-bindgen generate against the host library for
Kotlin and Swift. Output goes to rust_backend/target/:
target/release/: host library (.dylibon macOS,.soon Linux)target/bindings/kotlin/,target/bindings/swift/: generated bindingstarget/android/jniLibs/<abi>/: Android librariestarget/ios/: the XCFramework, Swift binding and podspec for CocoaPods
Android
Android Gradle runs scripts/build_rust_backend.sh android in its
buildRustBackend task before preBuild, so a normal Flutter build is
enough. For a manual build:
- Set
ANDROID_NDK_HOMEto NDK 30.0.16248370 (the version CI installs in.github/workflows/ci.ymlandandroid/app/build.gradle.ktsuses). SPOTIFLAC_RUST_ANDROID_ABISselectsarm64-v8a,armeabi-v7aor both (comma-separated; the default is both).- Libraries link against API 24 with 16 KB page alignment.
bash scripts/check_android_statx.sh arm64-v8a checks the vendored Rustix
fallback on a connected device (see rust_backend/vendor/README.md).
iOS
On macOS, bash scripts/build_rust_backend.sh ios builds
aarch64-apple-ios, aarch64-apple-ios-sim and x86_64-apple-ios (iOS 16.0
deployment target), merges the two simulator slices, and stages
target/ios/ with rust_backend/SpotiFLACBackend.podspec. Run
(cd ios && pod install) afterwards; ios/Podfile refuses to install
without the staged files.
Where the Bindings Are Used
The binding names come from rust_backend/crates/mobile/uniffi.toml.
| Platform | Generated | Consumed by |
|---|---|---|
| Android | Kotlin package com.spotiflac.backend, loading spotiflac_mobile through JNA |
android/app/build.gradle.kts adds target/bindings/kotlin and target/android/jniLibs as source dirs; used by android/app/src/rust/kotlin/com/zarz/spotiflac/SelectedCoreBackend.kt and its neighbors |
| iOS | Swift module SpotiFLACBackend with FFI module SpotiFLACBackendFFI |
ios/Podfile pod SpotiFLACBackend at ../rust_backend/target/ios; imported by ios/Runner/CoreBackend.swift |
The generated files are build output under target/ and are not tracked.
After changing an exported function, rebuild for the platform you test; the
Kotlin and Swift call sites must match the new signatures.
Tests
bash scripts/check_rust_backend.sh
(cd rust_backend && cargo test --locked -p spotiflac-core)
(cd rust_backend && cargo test --locked -p spotiflac-mobile network_tags --lib)
- Unit tests sit in
#[cfg(test)] mod testsblocks. Larger modules keep them in a siblingtests.rsdeclared withmod tests;, for examplerust_backend/crates/mobile/src/native_library/tests.rsandrust_backend/crates/extensions/src/manager/tests.rs. - Cargo integration tests live in
rust_backend/crates/core/tests/andrust_backend/crates/extensions/tests/. rust_backend/crates/core/src/metadata/musicbrainz/case_data.rsis a frozen generated table (Unicode 15.0.0 ranges and case mappings for MusicBrainz title casing, kept for parity with the Go backend). It is markedDO NOT EDIT; the generator is no longer in the repository, and it must not be regenerated from newer tables.- CI runs
scripts/check_rust_backend.shon Linux, the iOS build on macOS, and the Android build with Kotlin unit tests (gradle -p android :app:assembleDebug :app:testDebugUnitTest).