Files
SpotiFLAC-Mobile/scripts/build_rust_backend.sh
T
zarzet aa99726439 feat(backend): migrate production backend to Rust
Replace the Go backend with the Rust workspace and route Android/iOS through
UniFFI bindings. Include the migrated extension runtime, network, providers,
media metadata, downloads and library operations, with version 5.0.0+147.

Remove Go sources, adapters, native build selection and CI dependencies.
Build Rust unconditionally and lock the iOS Rust pod using a relative path.
Retain legacy source and migration evidence in a local ignored archive.

Validation: Android Kotlin compile and 53 native tests; Swift Rust-branch
equivalence and syntax; CocoaPods install; workflow, shell, Ruby, plist and
diff checks. Reuse the preceding 100 Rust tests, fmt/Clippy and five-ABI
build checkpoint; no new APK or iOS application build for this cleanup.

Known follow-up: URL/URLSearchParams globals are missing from the Rust JS
runtime. A controlled extension replay confirms a URL-resolution regression;
this commit does not fix that runtime gap.
2026-09-14 22:58:27 +07:00

155 lines
6.9 KiB
Bash

#!/usr/bin/env bash
# Stage Rust libraries and UniFFI bindings without changing the app's backend.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
RUST_BACKEND_DIR="$PROJECT_DIR/rust_backend"
PLATFORM="${1:-host}"
case "$PLATFORM" in
host|android|ios) ;;
*) echo "Usage: bash scripts/build_rust_backend.sh [host|android|ios]" >&2; exit 1 ;;
esac
if [[ "$PLATFORM" == "android" ]]; then
ANDROID_ABIS_RAW="${SPOTIFLAC_RUST_ANDROID_ABIS-arm64-v8a,armeabi-v7a}"
case "$ANDROID_ABIS_RAW" in
arm64-v8a|armeabi-v7a|arm64-v8a,armeabi-v7a|armeabi-v7a,arm64-v8a) ;;
*)
echo "Error: SPOTIFLAC_RUST_ANDROID_ABIS must contain arm64-v8a and/or armeabi-v7a, comma-separated without spaces or duplicates." >&2
exit 1
;;
esac
IFS=',' read -r -a ANDROID_ABIS <<< "$ANDROID_ABIS_RAW"
fi
cd "$RUST_BACKEND_DIR"
export CARGO_TARGET_DIR="$RUST_BACKEND_DIR/target"
# AES 0.8 requires this opt-in on ARM64. Runtime detection retains the software
# fallback on CPUs without AES; do not force target-feature=+aes on Android.
export RUSTFLAGS="${RUSTFLAGS:-} --cfg aes_armv8"
case "$(uname -s)" in
Darwin)
HOST_LIBRARY="$CARGO_TARGET_DIR/release/libspotiflac_mobile.dylib"
# Keep cc-rs (QuickJS) and Rust compatible with the Swift host executable.
export MACOSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-11.0}"
;;
Linux) HOST_LIBRARY="$CARGO_TARGET_DIR/release/libspotiflac_mobile.so" ;;
*) echo "Error: use macOS or Linux for this build script." >&2; exit 1 ;;
esac
# Build separately from bindgen so CLI and cargo-metadata features are not
# enabled in the shipped library through Cargo's workspace feature unification.
cargo build --locked --release -p spotiflac-mobile
for language in kotlin swift; do
cargo run --locked -p spotiflac-bindgen -- generate \
--library "$HOST_LIBRARY" \
--config crates/mobile/uniffi.toml \
--language "$language" \
--out-dir "$CARGO_TARGET_DIR/bindings/$language" \
--no-format
done
if [[ "$PLATFORM" == "android" ]]; then
if [[ -z "${ANDROID_NDK_HOME:-}" ]]; then
echo "Error: set ANDROID_NDK_HOME to Android NDK 29.0.14206865." >&2
exit 1
fi
case "$(uname -s)" in
Darwin) NDK_HOST="darwin-x86_64" ;;
Linux) NDK_HOST="linux-x86_64" ;;
esac
NDK_BIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/$NDK_HOST/bin"
NDK_SYSROOT="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/$NDK_HOST/sysroot"
ANDROID_TARGETS=()
ANDROID_LINKERS=()
for ABI in "${ANDROID_ABIS[@]}"; do
case "$ABI" in
arm64-v8a)
ANDROID_TARGETS+=(aarch64-linux-android)
ANDROID_LINKERS+=("$NDK_BIN/aarch64-linux-android24-clang")
# QuickJS is compiled C. Configure cc-rs and libclang for the same
# target/API as Rust; the host SDK cannot supply Android headers or
# ABI layouts.
export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$NDK_BIN/aarch64-linux-android24-clang"
export CC_aarch64_linux_android="$CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER"
export AR_aarch64_linux_android="$NDK_BIN/llvm-ar"
export BINDGEN_EXTRA_CLANG_ARGS_aarch64_linux_android="--sysroot=\"$NDK_SYSROOT\" --target=aarch64-linux-android24"
;;
armeabi-v7a)
ANDROID_TARGETS+=(armv7-linux-androideabi)
ANDROID_LINKERS+=("$NDK_BIN/armv7a-linux-androideabi24-clang")
export CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="$NDK_BIN/armv7a-linux-androideabi24-clang"
export CC_armv7_linux_androideabi="$CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER"
export AR_armv7_linux_androideabi="$NDK_BIN/llvm-ar"
export BINDGEN_EXTRA_CLANG_ARGS_armv7_linux_androideabi="--sysroot=\"$NDK_SYSROOT\" --target=armv7a-linux-androideabi24"
;;
esac
done
for linker in "${ANDROID_LINKERS[@]}"; do
if [[ ! -x "$linker" ]]; then
echo "Error: Android API 24 linker was not found: $linker" >&2
exit 1
fi
done
rustup target add "${ANDROID_TARGETS[@]}"
# Align ELF segments for Android devices using 16 KB pages.
export RUSTFLAGS="${RUSTFLAGS:-} -C link-arg=-Wl,-z,max-page-size=16384"
for target in "${ANDROID_TARGETS[@]}"; do
cargo build --locked --release -p spotiflac-mobile --target "$target"
case "$target" in
aarch64-linux-android) ABI="arm64-v8a" ;;
armv7-linux-androideabi) ABI="armeabi-v7a" ;;
esac
mkdir -p "$CARGO_TARGET_DIR/android/jniLibs/$ABI"
cp "$CARGO_TARGET_DIR/$target/release/libspotiflac_mobile.so" \
"$CARGO_TARGET_DIR/android/jniLibs/$ABI/"
done
elif [[ "$PLATFORM" == "ios" ]]; then
if [[ "$(uname -s)" != "Darwin" ]]; then
echo "Error: iOS builds require macOS and Xcode." >&2
exit 1
fi
rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
export IPHONEOS_DEPLOYMENT_TARGET=16.0
export BINDGEN_EXTRA_CLANG_ARGS_aarch64_apple_ios="-isysroot \"$(xcrun --sdk iphoneos --show-sdk-path)\" --target=arm64-apple-ios16.0"
export BINDGEN_EXTRA_CLANG_ARGS_aarch64_apple_ios_sim="-isysroot \"$(xcrun --sdk iphonesimulator --show-sdk-path)\" --target=arm64-apple-ios16.0-simulator"
export BINDGEN_EXTRA_CLANG_ARGS_x86_64_apple_ios="-isysroot \"$(xcrun --sdk iphonesimulator --show-sdk-path)\" --target=x86_64-apple-ios16.0-simulator"
for target in aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios; do
cargo build --locked --release -p spotiflac-mobile --target "$target"
done
HEADERS_DIR="$CARGO_TARGET_DIR/ios/headers"
FRAMEWORK_DIR="$CARGO_TARGET_DIR/ios/SpotiFLACBackendFFI.xcframework"
SIMULATOR_LIBRARY="$CARGO_TARGET_DIR/ios/simulator/libspotiflac_mobile.a"
mkdir -p "$HEADERS_DIR" "$(dirname "$SIMULATOR_LIBRARY")"
cp "$CARGO_TARGET_DIR/bindings/swift/SpotiFLACBackendFFI.h" "$HEADERS_DIR/"
cp "$CARGO_TARGET_DIR/bindings/swift/SpotiFLACBackendFFI.modulemap" "$HEADERS_DIR/module.modulemap"
# Flutter/CocoaPods may request both simulator architectures, even on an
# ARM64 Mac. Supply one universal simulator slice.
xcrun lipo -create \
"$CARGO_TARGET_DIR/aarch64-apple-ios-sim/release/libspotiflac_mobile.a" \
"$CARGO_TARGET_DIR/x86_64-apple-ios/release/libspotiflac_mobile.a" \
-output "$SIMULATOR_LIBRARY"
# xcodebuild refuses to overwrite an existing generated XCFramework.
if [[ -d "$FRAMEWORK_DIR" ]]; then
rm -rf "$FRAMEWORK_DIR"
fi
xcodebuild -create-xcframework \
-library "$CARGO_TARGET_DIR/aarch64-apple-ios/release/libspotiflac_mobile.a" -headers "$HEADERS_DIR" \
-library "$SIMULATOR_LIBRARY" -headers "$HEADERS_DIR" \
-output "$FRAMEWORK_DIR"
# CocoaPods does not traverse nested symlinks, including a relocated target/.
# Stage one pod root that Podfile can resolve before scanning its files.
cp "$CARGO_TARGET_DIR/bindings/swift/SpotiFLACBackend.swift" "$CARGO_TARGET_DIR/ios/"
cp "$RUST_BACKEND_DIR/SpotiFLACBackend.podspec" "$CARGO_TARGET_DIR/ios/"
cp "$PROJECT_DIR/LICENSE" "$CARGO_TARGET_DIR/ios/"
fi
echo "Rust $PLATFORM artifacts are staged in $CARGO_TARGET_DIR."