#!/usr/bin/env bash # Chanora Linux build helper. # # Run this on a Linux host (Ubuntu 22.04+ / Debian bookworm+ / Fedora 38+) that # has already completed the prerequisites in ยง4 of docs/release/linux-build.md: # - Rust stable with x86_64-unknown-linux-gnu target # - Flutter 3.41.9+ stable on PATH # - CMake 3.13+, pkg-config, GTK3 dev headers, clang # - System libopus development package (libopus-dev / opus-devel) # - Optional: CHANORA_ONNXRUNTIME_SHARED_LIB=/abs/path/libonnxruntime.so # to override the ONNX Runtime shared library bundled under bundle/lib/ # - flutter_rust_bridge_codegen 2.12.0 (only if regenerating bindings) # # Usage (from the repo root ~/chanora): # ./tools/build-linux.sh # ./tools/build-linux.sh --no-rust # skip Rust rebuild (cached) # ./tools/build-linux.sh --regenerate-bindings # rerun FRB codegen first # ./tools/build-linux.sh --version v0.2.0-beta.1 # # Produces: # target/x86_64-unknown-linux-gnu/release/libchanora_bridge.so # apps/chanora_flutter/build/linux/x64/release/bundle/chanora_flutter # chanora--linux-x64.tar.gz (release bundle) set -euo pipefail VERSION="v0.2.0-beta.1" SKIP_RUST=0 REGEN=0 TARGET="x86_64-unknown-linux-gnu" ONNXRUNTIME_SHARED_LIB="${CHANORA_ONNXRUNTIME_SHARED_LIB:-}" ONNXRUNTIME_VERSION="${CHANORA_ONNXRUNTIME_VERSION:-1.26.0}" while [[ $# -gt 0 ]]; do case "$1" in --version) VERSION="$2"; shift 2;; --no-rust) SKIP_RUST=1; shift;; --regenerate-bindings) REGEN=1; shift;; --target) TARGET="$2"; shift 2;; *) echo "unknown arg: $1" >&2; exit 2;; esac done cd "$(dirname "$0")/.." REPO_ROOT="$(pwd)" bar() { printf '%s\n' '========================================================================'; } bar echo "Chanora Linux build" echo "Repo root : $REPO_ROOT" echo "Version : $VERSION" echo "Target : $TARGET" echo "Build Rust: $([ "$SKIP_RUST" = 1 ] && echo skip || echo yes)" echo "Bundle ORT: ${ONNXRUNTIME_SHARED_LIB:-auto ($ONNXRUNTIME_VERSION)}" bar resolve_onnxruntime_shared_lib() { if [[ -n "$ONNXRUNTIME_SHARED_LIB" ]]; then if [[ ! -f "$ONNXRUNTIME_SHARED_LIB" ]]; then echo " ERROR: CHANORA_ONNXRUNTIME_SHARED_LIB does not exist:" >&2 echo " $ONNXRUNTIME_SHARED_LIB" >&2 exit 1 fi return fi local ort_arch case "$TARGET" in x86_64-unknown-linux-gnu) ort_arch="x64" ;; aarch64-unknown-linux-gnu) ort_arch="aarch64" ;; *) echo " ERROR: automatic ONNX Runtime bundling does not support target $TARGET." >&2 echo " Set CHANORA_ONNXRUNTIME_SHARED_LIB=/absolute/path/to/libonnxruntime.so." >&2 exit 1 ;; esac local archive_name="onnxruntime-linux-${ort_arch}-${ONNXRUNTIME_VERSION}.tgz" local cache_dir="$REPO_ROOT/.cache/onnxruntime/$ONNXRUNTIME_VERSION/$ort_arch" local archive_path="$cache_dir/$archive_name" local extract_dir="$cache_dir/extracted" local extracted_lib="$extract_dir/onnxruntime-linux-${ort_arch}-${ONNXRUNTIME_VERSION}/lib/libonnxruntime.so" local url="https://github.com/microsoft/onnxruntime/releases/download/v${ONNXRUNTIME_VERSION}/${archive_name}" if [[ ! -f "$extracted_lib" ]]; then mkdir -p "$cache_dir" "$extract_dir" if [[ ! -f "$archive_path" ]]; then if ! command -v curl >/dev/null; then echo " ERROR: curl is required to download ONNX Runtime automatically." >&2 echo " Install curl or set CHANORA_ONNXRUNTIME_SHARED_LIB." >&2 exit 1 fi echo " Downloading ONNX Runtime $ONNXRUNTIME_VERSION for linux-$ort_arch" curl --fail --location --show-error --output "$archive_path" "$url" fi rm -rf "$extract_dir" mkdir -p "$extract_dir" tar -xzf "$archive_path" -C "$extract_dir" fi if [[ ! -f "$extracted_lib" ]]; then echo " ERROR: ONNX Runtime archive did not contain expected library:" >&2 echo " $extracted_lib" >&2 exit 1 fi ONNXRUNTIME_SHARED_LIB="$extracted_lib" } # ---------- 1. Verify toolchain ---------- echo "[1/6] Verify toolchain" for cmd in cargo rustc flutter cmake pkg-config clang gcc tar; do if ! command -v "$cmd" >/dev/null; then echo " ERROR: '$cmd' not on PATH. See docs/release/linux-build.md." >&2 exit 1 fi printf " %-12s : %s\n" "$cmd" "$($cmd --version 2>&1 | head -1)" done # Verify GTK3 dev headers if ! pkg-config --exists gtk+-3.0 2>/dev/null; then echo " ERROR: gtk+-3.0 dev headers not found. Install libgtk-3-dev (Debian/Ubuntu) or gtk3-devel (Fedora)." >&2 exit 1 fi # Verify libopus development files. tsclientlib's audio feature pulls in # audiopus, which links libopus for Linux builds/tests. if ! pkg-config --exists opus 2>/dev/null; then echo " ERROR: opus pkg-config metadata not found." >&2 echo " Install the Opus development package (libopus-dev on" >&2 echo " Debian/Ubuntu or opus-devel on Fedora)." >&2 exit 1 fi printf " %-12s : %s\n" "opus" "$(pkg-config --modversion opus)" # Verify the exact PipeWire pkg-config modules the primary Linux voice backend uses. # `pipewire-0.3` is not the module name exposed by distro packages; the crates # resolve `libpipewire-0.3` and `libspa-0.2`. if ! pkg-config --exists libpipewire-0.3 2>/dev/null; then echo " ERROR: libpipewire-0.3 pkg-config metadata not found." >&2 echo " Install PipeWire development headers (for example pipewire-devel" >&2 echo " on Fedora or libpipewire-0.3-dev on Debian/Ubuntu)." >&2 exit 1 fi if ! pkg-config --exists libspa-0.2 2>/dev/null; then echo " ERROR: libspa-0.2 pkg-config metadata not found." >&2 echo " Install the PipeWire SPA development headers packaged with" >&2 echo " your distro's PipeWire development package." >&2 exit 1 fi printf " %-12s : %s\n" "libpipewire" "$(pkg-config --modversion libpipewire-0.3)" printf " %-12s : %s\n" "libspa" "$(pkg-config --modversion libspa-0.2)" # Verify PulseAudio development files used by the fallback Linux voice backend. if ! pkg-config --exists libpulse 2>/dev/null; then echo " ERROR: libpulse pkg-config metadata not found." >&2 echo " Install PulseAudio development headers (for example libpulse-dev" >&2 echo " on Debian/Ubuntu, pulseaudio-libs-devel on Fedora, or libpulse" >&2 echo " on Arch Linux)." >&2 exit 1 fi if ! pkg-config --exists libpulse-simple 2>/dev/null; then echo " ERROR: libpulse-simple pkg-config metadata not found." >&2 echo " Install PulseAudio simple API development headers." >&2 exit 1 fi printf " %-12s : %s\n" "libpulse" "$(pkg-config --modversion libpulse)" printf " %-12s : %s\n" "pulse-simple" "$(pkg-config --modversion libpulse-simple)" resolve_onnxruntime_shared_lib echo " ORT source : $ONNXRUNTIME_SHARED_LIB" # Verify Rust target if ! rustup target list --installed | grep -q "^$TARGET$"; then echo " Adding rustup target $TARGET..." rustup target add "$TARGET" fi # ---------- 2. Add Linux platform if missing ---------- FLUTTER_APP="$REPO_ROOT/apps/chanora_flutter" if [[ ! -d "$FLUTTER_APP/linux" ]]; then echo "[2/6] Add Linux platform to apps/chanora_flutter" (cd "$FLUTTER_APP" && flutter create --platforms=linux .) else echo "[2/6] Linux platform already present in apps/chanora_flutter" fi # ---------- 3. (Optional) regenerate FRB bindings ---------- if [[ $REGEN -eq 1 ]]; then echo "[3/6] Regenerate flutter_rust_bridge bindings" if ! command -v flutter_rust_bridge_codegen >/dev/null; then echo " flutter_rust_bridge_codegen not on PATH; install it first." >&2 exit 1 fi flutter_rust_bridge_codegen generate else echo "[3/6] Skipping FRB codegen (use --regenerate-bindings if api.rs changed)" fi # ---------- 4. Build the Rust cdylib ---------- BRIDGE_SO="$REPO_ROOT/target/$TARGET/release/libchanora_bridge.so" if [[ $SKIP_RUST -eq 1 ]]; then echo "[4/6] Skipping Rust build (cached)" if [[ ! -f "$BRIDGE_SO" ]]; then echo " ERROR: --no-rust but $BRIDGE_SO not found. Run without --no-rust first." >&2 exit 1 fi else echo "[4/6] cargo build --release -p chanora_bridge --target $TARGET" export CMAKE_POLICY_VERSION_MINIMUM=3.5 export LIBOPUS_STATIC=0 cargo build --release --target "$TARGET" -p chanora_bridge fi echo " Bridge .so: $BRIDGE_SO" if [[ ! -f "$BRIDGE_SO" ]]; then echo " ERROR: $BRIDGE_SO not found after build." >&2 exit 1 fi # ---------- 5. flutter build linux --release ---------- echo "[5/6] flutter build linux --release" cd "$FLUTTER_APP" export CHANORA_BRIDGE_SHARED_LIB="$BRIDGE_SO" export CHANORA_ONNXRUNTIME_SHARED_LIB="$ONNXRUNTIME_SHARED_LIB" flutter pub get flutter build linux --release BUNDLE_DIR="$FLUTTER_APP/build/linux/x64/release/bundle" EXE="$BUNDLE_DIR/chanora_flutter" if [[ ! -f "$EXE" ]]; then echo " ERROR: $EXE not found after flutter build." >&2 exit 1 fi mkdir -p "$BUNDLE_DIR/lib" rm -f "$BUNDLE_DIR/lib/libonnxruntime.so" cp "$ONNXRUNTIME_SHARED_LIB" "$BUNDLE_DIR/lib/libonnxruntime.so" echo " Bundle dir : $BUNDLE_DIR" echo " EXE : $EXE" echo " Bridge .so : $BUNDLE_DIR/lib/libchanora_bridge.so" if [[ ! -f "$BUNDLE_DIR/lib/libchanora_bridge.so" ]]; then echo " ERROR: bridge shared library missing from Linux bundle:" >&2 echo " $BUNDLE_DIR/lib/libchanora_bridge.so" >&2 exit 1 fi if [[ -f "$BUNDLE_DIR/lib/libonnxruntime.so" ]]; then echo " ORT .so : $BUNDLE_DIR/lib/libonnxruntime.so" else echo " ERROR: ONNX Runtime bundle file missing after flutter build:" >&2 echo " $BUNDLE_DIR/lib/libonnxruntime.so" >&2 exit 1 fi # ---------- 6. Package the bundle ---------- echo "[6/6] Package release tarball" cd "$REPO_ROOT" TAR_NAME="chanora-$VERSION-linux-x64.tar.gz" TAR_PATH="$REPO_ROOT/$TAR_NAME" rm -f "$TAR_PATH" # Bundle everything in the bundle directory tar czf "$TAR_PATH" -C "$FLUTTER_APP/build/linux/x64/release/bundle" . echo " Tarball : $TAR_PATH" echo " Size : $(du -h "$TAR_PATH" | cut -f1)" bar echo "Linux build complete." bar echo "Artefacts:" echo " Rust lib:" echo " target/$TARGET/release/libchanora_bridge.so" echo " Bundle dir:" echo " $BUNDLE_DIR/" echo " Tarball:" echo " $TAR_PATH"