feat: add cross-platform & cross-architecture build script

This commit is contained in:
2026-05-31 13:10:27 +08:00
parent eda8f7d927
commit 1e0ba958df

443
build.sh Executable file
View File

@@ -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!"