#!/usr/bin/env bash set -euo pipefail RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' info() { echo -e "${GREEN}[INFO]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; } USAGE=" Usage: $(basename "$0") [OPTIONS] Cross-platform & cross-architecture build script for AirDisplay Client (Tauri) Options: -p, --platform TARGET Target platform: macos, windows, linux, all (default: current OS) -a, --arch ARCH Target architecture: x64, arm64, armhf, universal (default: current arch) -t, --tool TOOL Cross-compile toolchain: cross, xwin, cargo (default: auto-detect) -r, --release Build in release mode (default) -d, --debug Build in debug mode -c, --clean Clean before building -l, --list List supported platform/arch combinations -h, --help Show this help message Supported targets: macos x64, arm64, universal windows x64, arm64 linux x64, arm64, armhf Examples: $(basename "$0") Build for current platform & arch $(basename "$0") -p macos -a universal Universal macOS binary (x64+arm64) $(basename "$0") -p linux -a arm64 Linux ARM64 (e.g. Raspberry Pi 5) $(basename "$0") -p linux -a armhf Linux ARM32 hard-float $(basename "$0") -p windows -a arm64 Windows ARM64 $(basename "$0") -p all Build for all platform+arch combos $(basename "$0") -p macos -a arm64 -t cross Force use cross toolchain $(basename "$0") -c -p linux -a arm64 Clean + build Linux ARM64 " BUILD_TYPE="release" CLEAN=false LIST=false TOOLCHAIN="" PLATFORM="" ARCH="" detect_current_platform() { case "$(uname -s)" in Darwin) echo "macos" ;; Linux) echo "linux" ;; MINGW*|MSYS*|CYGWIN*) echo "windows" ;; *) error "Unsupported OS: $(uname -s)" ;; esac } detect_current_arch() { case "$(uname -m)" in x86_64|amd64) echo "x64" ;; arm64|aarch64) echo "arm64" ;; armv7l|armv7) echo "armhf" ;; *) echo "x64" ;; esac } CURRENT_PLATFORM=$(detect_current_platform) CURRENT_ARCH=$(detect_current_arch) PLATFORM="${CURRENT_PLATFORM}" ARCH="${CURRENT_ARCH}" while [[ $# -gt 0 ]]; do case "$1" in -p|--platform) PLATFORM="$2"; shift 2 ;; -a|--arch) ARCH="$2"; shift 2 ;; -t|--tool) TOOLCHAIN="$2"; shift 2 ;; -r|--release) BUILD_TYPE="release"; shift ;; -d|--debug) BUILD_TYPE="debug"; shift ;; -c|--clean) CLEAN=true; shift ;; -l|--list) LIST=true; shift ;; -h|--help) echo "$USAGE"; exit 0 ;; *) error "Unknown option: $1\n$USAGE" ;; esac done VALID_ARCHS_MACOS="x64 arm64 universal" VALID_ARCHS_WINDOWS="x64 arm64" VALID_ARCHS_LINUX="x64 arm64 armhf" validate_arch() { local platform="$1" arch="$2" case "$platform" in macos) echo "$VALID_ARCHS_MACOS" | grep -qw "$arch" || error "macos does not support arch '$arch'. Valid: $VALID_ARCHS_MACOS" ;; windows) echo "$VALID_ARCHS_WINDOWS" | grep -qw "$arch" || error "windows does not support arch '$arch'. Valid: $VALID_ARCHS_WINDOWS" ;; linux) echo "$VALID_ARCHS_LINUX" | grep -qw "$arch" || error "linux does not support arch '$arch'. Valid: $VALID_ARCHS_LINUX" ;; *) error "Unknown platform: $platform" ;; esac } if [[ "$LIST" == true ]]; then echo -e "${CYAN}Supported platform/arch combinations:${NC}" echo "" printf " %-10s %s\n" "macos" "$VALID_ARCHS_MACOS" printf " %-10s %s\n" "windows" "$VALID_ARCHS_WINDOWS" printf " %-10s %s\n" "linux" "$VALID_ARCHS_LINUX" echo "" echo -e "${CYAN}Rust targets mapping:${NC}" echo "" printf " %-20s %-30s %s\n" "PLATFORM/ARCH" "RUST TARGET" "NOTES" printf " %-20s %-30s %s\n" "macos/x64" "x86_64-apple-darwin" "" printf " %-20s %-30s %s\n" "macos/arm64" "aarch64-apple-darwin" "Apple Silicon" printf " %-20s %-30s %s\n" "macos/universal" "x86_64+aarch64-apple-darwin" "Merged fat binary" printf " %-20s %-30s %s\n" "windows/x64" "x86_64-pc-windows-msvc" "" printf " %-20s %-30s %s\n" "windows/arm64" "aarch64-pc-windows-msvc" "ARM64 devices" printf " %-20s %-30s %s\n" "linux/x64" "x86_64-unknown-linux-gnu" "" printf " %-20s %-30s %s\n" "linux/arm64" "aarch64-unknown-linux-gnu" "RPi 5, AWS Graviton, etc." printf " %-20s %-30s %s\n" "linux/armhf" "armv7-unknown-linux-gnueabihf" "RPi 3/4, 32-bit ARM" exit 0 fi validate_arch "$PLATFORM" "$ARCH" rust_target_for() { local platform="$1" arch="$2" case "$platform/$arch" in macos/x64) echo "x86_64-apple-darwin" ;; macos/arm64) echo "aarch64-apple-darwin" ;; macos/universal) echo "universal-apple-darwin" ;; windows/x64) echo "x86_64-pc-windows-msvc" ;; windows/arm64) echo "aarch64-pc-windows-msvc" ;; linux/x64) echo "x86_64-unknown-linux-gnu" ;; linux/arm64) echo "aarch64-unknown-linux-gnu" ;; linux/armhf) echo "armv7-unknown-linux-gnueabihf" ;; *) error "Unsupported combination: $platform/$arch" ;; esac } needs_cross_tool() { local platform="$1" arch="$2" local target target=$(rust_target_for "$platform" "$arch") if [[ "$platform" != "$CURRENT_PLATFORM" ]]; then return 0 fi case "$CURRENT_PLATFORM/$CURRENT_ARCH" in macos/x64) [[ "$arch" == "arm64" ]] && return 0 ;; macos/arm64) [[ "$arch" == "x64" ]] && return 0 ;; *) local current_rust_arch current_rust_arch=$(rust_target_for "$CURRENT_PLATFORM" "$CURRENT_ARCH") [[ "$target" != "$current_rust_arch" ]] && return 0 ;; esac return 1 } detect_toolchain() { local platform="$1" arch="$2" if [[ -n "$TOOLCHAIN" ]]; then echo "$TOOLCHAIN" return fi if ! needs_cross_tool "$platform" "$arch"; then echo "native" return fi case "$platform" in windows) if command -v cargo-xwin &>/dev/null; then echo "xwin" else echo "cross" fi ;; linux) if command -v cross &>/dev/null; then echo "cross" elif command -v cargo-zigbuild &>/dev/null; then echo "zigbuild" else echo "cross" fi ;; macos) if [[ "$arch" == "universal" ]]; then echo "native" else echo "native" fi ;; *) echo "cross" ;; esac } check_command() { if ! command -v "$1" &>/dev/null; then error "$1 is not installed. Please install it first." fi } check_node_deps() { if [[ ! -d "node_modules" ]]; then info "Installing Node.js dependencies..." npm install fi } check_rust_target() { local target="$1" if [[ "$target" == *"universal"* ]]; then check_rust_target "x86_64-apple-darwin" check_rust_target "aarch64-apple-darwin" return fi if ! rustup target list --installed | grep -q "$target"; then info "Installing Rust target: $target" rustup target add "$target" fi } install_cross_tools() { local toolchain="$1" case "$toolchain" in cross) if ! command -v cross &>/dev/null; then warn "cross not found. Installing..." cargo install cross --git https://github.com/cross-rs/cross fi ;; xwin) if ! command -v cargo-xwin &>/dev/null; then warn "cargo-xwin not found. Installing..." cargo install cargo-xwin fi ;; zigbuild) if ! command -v cargo-zigbuild &>/dev/null; then warn "cargo-zigbuild not found. Installing..." pip3 install ziglang cargo install cargo-zigbuild fi ;; esac } do_clean() { info "Cleaning build artifacts..." npm run build -- --clearScreen false 2>/dev/null || true (cd src-tauri && cargo clean) rm -rf dist info "Clean complete." } build_macos_universal() { info "Building macOS universal binary (x64 + arm64 → universal)..." check_rust_target "x86_64-apple-darwin" check_rust_target "aarch64-apple-darwin" local cmd=(npx tauri build --target universal-apple-darwin) if [[ "$BUILD_TYPE" == "debug" ]]; then cmd=(npx tauri build --target universal-apple-darwin --debug) fi info "Running: ${cmd[*]}" "${cmd[@]}" } build_platform() { local platform="$1" arch="$2" info "===========================================" info " Building for $platform ($arch)" info "===========================================" if [[ "$platform/$arch" == "macos/universal" ]]; then build_macos_universal return $? fi local rust_target rust_target=$(rust_target_for "$platform" "$arch") local toolchain toolchain=$(detect_toolchain "$platform" "$arch") info "Using toolchain: $toolchain for $platform/$arch" if [[ "$toolchain" == "native" ]]; then check_rust_target "$rust_target" local cmd=(npx tauri build --target "$rust_target") if [[ "$BUILD_TYPE" == "debug" ]]; then cmd+=(--debug) fi case "$platform" in macos) cmd+=(--bundles app) ;; windows) cmd+=(--bundles nsis) ;; linux) cmd+=(--bundles deb) ;; esac info "Running: ${cmd[*]}" "${cmd[@]}" elif [[ "$toolchain" == "cross" ]]; then install_cross_tools "cross" check_rust_target "$rust_target" info "Cross-compiling with cross for target: $rust_target" local build_flag="" if [[ "$BUILD_TYPE" == "release" ]]; then build_flag="--release" fi (cd src-tauri && cross build --target "$rust_target" $build_flag --lib) local cmd=(npx tauri build --target "$rust_target") if [[ "$BUILD_TYPE" == "debug" ]]; then cmd+=(--debug) fi case "$platform" in windows) cmd+=(--bundles nsis) ;; linux) cmd+=(--bundles deb) ;; esac info "Running: ${cmd[*]}" "${cmd[@]}" elif [[ "$toolchain" == "xwin" ]]; then install_cross_tools "xwin" check_rust_target "$rust_target" info "Cross-compiling with cargo-xwin for target: $rust_target" local build_flag="" if [[ "$BUILD_TYPE" == "release" ]]; then build_flag="--release" fi (cd src-tauri && cargo xwin build --target "$rust_target" $build_flag) local cmd=(npx tauri build --target "$rust_target" --bundles nsis) if [[ "$BUILD_TYPE" == "debug" ]]; then cmd+=(--debug) fi info "Running: ${cmd[*]}" "${cmd[@]}" elif [[ "$toolchain" == "zigbuild" ]]; then install_cross_tools "zigbuild" check_rust_target "$rust_target" info "Cross-compiling with cargo-zigbuild for target: $rust_target" local build_flag="" if [[ "$BUILD_TYPE" == "release" ]]; then build_flag="--release" fi (cd src-tauri && cargo zigbuild --target "$rust_target" $build_flag) local cmd=(npx tauri build --target "$rust_target") if [[ "$BUILD_TYPE" == "debug" ]]; then cmd+=(--debug) fi case "$platform" in linux) cmd+=(--bundles deb) ;; esac info "Running: ${cmd[*]}" "${cmd[@]}" else error "Unknown toolchain: $toolchain" fi } declare -A BUILD_PLATFORMS case "$PLATFORM" in all) BUILD_PLATFORMS=( ["macos-x64"]=1 ["macos-arm64"]=1 ["macos-universal"]=1 ["windows-x64"]=1 ["windows-arm64"]=1 ["linux-x64"]=1 ["linux-arm64"]=1 ["linux-armhf"]=1 ) ;; macos|windows|linux) key="${PLATFORM}-${ARCH}" BUILD_PLATFORMS=(["$key"]=1) ;; *) error "Unknown platform: $PLATFORM. Use: macos, windows, linux, all" ;; esac echo -e "${CYAN}" echo " ╔═══════════════════════════════════════════╗" echo " ║ AirDisplay Client Build System ║" echo " ╚═══════════════════════════════════════════╝" echo -e "${NC}" info "Build type : $BUILD_TYPE" info "Host : $CURRENT_PLATFORM ($CURRENT_ARCH)" info "Targets : ${!BUILD_PLATFORMS[*]}" check_command node check_command npm check_command cargo check_command rustup check_node_deps if [[ "$CLEAN" == true ]]; then do_clean fi FAILED=() SUCCESS=() for key in "${!BUILD_PLATFORMS[@]}"; do IFS=- read -r plat arch <<< "$key" if build_platform "$plat" "$arch"; then SUCCESS+=("$plat ($arch)") else FAILED+=("$plat ($arch)") fi done echo "" info "===========================================" info " Build Summary" info "===========================================" if [[ ${#SUCCESS[@]} -gt 0 ]]; then echo -e "${GREEN}Successful:${NC}" for s in "${SUCCESS[@]}"; do echo " ✓ $s"; done fi if [[ ${#FAILED[@]} -gt 0 ]]; then echo -e "${RED}Failed:${NC}" for f in "${FAILED[@]}"; do echo " ✗ $f"; done exit 1 fi info "All builds completed successfully!"