From 1e0ba958dfcec9d9baac6c0a6976de8e269fecfa Mon Sep 17 00:00:00 2001 From: QianheYu Date: Sun, 31 May 2026 13:10:27 +0800 Subject: [PATCH] feat: add cross-platform & cross-architecture build script --- build.sh | 443 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 443 insertions(+) create mode 100755 build.sh diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..4ab6cac --- /dev/null +++ b/build.sh @@ -0,0 +1,443 @@ +#!/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 tauri_args=(tauri build --target universal-apple-darwin) + if [[ "$BUILD_TYPE" == "debug" ]]; then + tauri_args=(tauri build --target universal-apple-darwin --debug) + fi + + info "Running: npm run ${tauri_args[*]}" + npm run "${tauri_args[@]}" +} + +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 tauri_args=(tauri build --target "$rust_target") + if [[ "$BUILD_TYPE" == "debug" ]]; then + tauri_args+=(--debug) + fi + case "$platform" in + macos) tauri_args+=(--bundles app) ;; + windows) tauri_args+=(--bundles nsis) ;; + linux) tauri_args+=(--bundles deb) ;; + esac + info "Running: npm run ${tauri_args[*]}" + npm run "${tauri_args[@]}" + 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 tauri_args=(tauri build --target "$rust_target") + if [[ "$BUILD_TYPE" == "debug" ]]; then + tauri_args+=(--debug) + fi + case "$platform" in + windows) tauri_args+=(--bundles nsis) ;; + linux) tauri_args+=(--bundles deb) ;; + esac + info "Running: npm run ${tauri_args[*]}" + npm run "${tauri_args[@]}" + 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 tauri_args=(tauri build --target "$rust_target" --bundles nsis) + if [[ "$BUILD_TYPE" == "debug" ]]; then + tauri_args+=(--debug) + fi + info "Running: npm run ${tauri_args[*]}" + npm run "${tauri_args[@]}" + 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 tauri_args=(tauri build --target "$rust_target") + if [[ "$BUILD_TYPE" == "debug" ]]; then + tauri_args+=(--debug) + fi + case "$platform" in + linux) tauri_args+=(--bundles deb) ;; + esac + info "Running: npm run ${tauri_args[*]}" + npm run "${tauri_args[@]}" + 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!" \ No newline at end of file