Files
SpotiFLAC-Mobile/rust_backend/README.md
T
zarzet 00d476c146 docs(rust): describe the backend workspace, builds and bindings
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.
2026-10-19 05:27:36 +07:00

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 (.dylib on macOS, .so on Linux)
  • target/bindings/kotlin/, target/bindings/swift/: generated bindings
  • target/android/jniLibs/<abi>/: Android libraries
  • target/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_HOME to NDK 30.0.16248370 (the version CI installs in .github/workflows/ci.yml and android/app/build.gradle.kts uses).
  • SPOTIFLAC_RUST_ANDROID_ABIS selects arm64-v8a, armeabi-v7a or 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 tests blocks. Larger modules keep them in a sibling tests.rs declared with mod tests;, for example rust_backend/crates/mobile/src/native_library/tests.rs and rust_backend/crates/extensions/src/manager/tests.rs.
  • Cargo integration tests live in rust_backend/crates/core/tests/ and rust_backend/crates/extensions/tests/.
  • rust_backend/crates/core/src/metadata/musicbrainz/case_data.rs is 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 marked DO 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.sh on Linux, the iOS build on macOS, and the Android build with Kotlin unit tests (gradle -p android :app:assembleDebug :app:testDebugUnitTest).