#!/bin/bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" DEFAULT_OUTPUT_DIR="$PROJECT_ROOT/build/ios_release" OUTPUT_DIR="$DEFAULT_OUTPUT_DIR" EXTRA_ARGS=() # -------- 路径验证函数 -------- validate_output_path() { local path="$1" # 如果是文件路径(以 .ipa 结尾) if [[ "$path" == *.ipa ]]; then local dir="$(dirname "$path")" # 检查目录是否可写 if [[ ! -w "$dir" ]] 2>/dev/null; then # 尝试创建目录 if ! mkdir -p "$dir" 2>/dev/null; then echo "❌ 错误: 无法创建输出目录: $dir" >&2 echo " 提示: 请使用可写的路径,例如: ~/Desktop/output.ipa 或 ./build/myapp.ipa" >&2 return 1 fi fi # 检查父目录是否可写 if [[ ! -w "$dir" ]]; then echo "❌ 错误: 输出目录不可写: $dir" >&2 return 1 fi else # 如果是目录路径 if [[ ! -w "$(dirname "$path")" ]] 2>/dev/null && [[ "$path" != "$PROJECT_ROOT"* ]]; then # 尝试创建目录 if ! mkdir -p "$path" 2>/dev/null; then echo "❌ 错误: 无法创建输出目录: $path" >&2 echo " 提示: 请使用可写的路径,例如: ~/Desktop/ 或 ./build/" >&2 return 1 fi fi fi return 0 } # -------- 参数解析 -------- while [[ $# -gt 0 ]]; do case "$1" in --output=*) OUTPUT_DIR="${1#*=}" shift ;; --output) [[ $# -lt 2 ]] && { echo "❌ --output 需要路径"; exit 1; } OUTPUT_DIR="$2" shift 2 ;; *) EXTRA_ARGS+=("$1") shift ;; esac done # -------- 环境检查 -------- command -v flutter >/dev/null 2>&1 || { echo "❌ Flutter 未安装或未加入 PATH" exit 1 } xcodebuild -version >/dev/null 2>&1 || { echo "❌ Xcode 未正确安装" exit 1 } echo "🧩 Flutter 版本:" flutter --version # 验证输出路径 if ! validate_output_path "$OUTPUT_DIR"; then echo "⚠️ 使用默认输出目录: $DEFAULT_OUTPUT_DIR" OUTPUT_DIR="$DEFAULT_OUTPUT_DIR" fi # 展开 ~ 和相对路径 OUTPUT_DIR="${OUTPUT_DIR/#\~/$HOME}" if [[ "$OUTPUT_DIR" != /* ]]; then OUTPUT_DIR="$PROJECT_ROOT/$OUTPUT_DIR" fi mkdir -p "$OUTPUT_DIR" pushd "$PROJECT_ROOT" >/dev/null echo "📦 清理旧构建..." flutter clean echo "🧹 清理 Flutter pub cache 中的 im_flutter_sdk..." flutter pub cache clean im_flutter_sdk 2>/dev/null || true flutter pub cache clean im_flutter_sdk_ios 2>/dev/null || true echo " ✓ 已清理 im_flutter_sdk 相关缓存" echo "📚 获取依赖..." flutter pub get echo "🧹 清理 iOS 依赖缓存..." cd ios if [[ -f "Podfile.lock" ]]; then rm -f Podfile.lock echo " ✓ 已删除 Podfile.lock" fi if [[ -d "Pods" ]]; then rm -rf Pods echo " ✓ 已删除 Pods 目录" fi if [[ -d ".symlinks" ]]; then rm -rf .symlinks echo " ✓ 已删除 .symlinks 目录" fi # 清理 Xcode DerivedData 中的相关缓存 if [[ -d "$HOME/Library/Developer/Xcode/DerivedData" ]]; then find "$HOME/Library/Developer/Xcode/DerivedData" -name "*Runner*" -type d -exec rm -rf {} + 2>/dev/null || true echo " ✓ 已清理 Xcode DerivedData" fi cd .. echo "📦 重新安装 iOS Pods..." cd ios pod deintegrate 2>/dev/null || true # 清理 CocoaPods 缓存中的 im_flutter_sdk_ios 和 HyphenateChat pod cache clean im_flutter_sdk_ios --all 2>/dev/null || true pod cache clean HyphenateChat --all 2>/dev/null || true # 强制更新 pod 仓库(仅清理时执行一次) if [[ ! -f ".pod_install_done" ]]; then echo " 🔄 更新 CocoaPods 仓库..." pod repo update 2>/dev/null || true touch .pod_install_done fi pod install cd .. echo "🚀 开始构建 iOS IPA..." if [[ ${#EXTRA_ARGS[@]} -gt 0 ]]; then flutter build ipa --release "${EXTRA_ARGS[@]}" else flutter build ipa --release fi IPA_DIR="$PROJECT_ROOT/build/ios/ipa" BUILT_IPA="$(ls -t "$IPA_DIR"/*.ipa 2>/dev/null | head -n 1)" if [[ -z "$BUILT_IPA" ]]; then echo "❌ 未找到 IPA,构建失败" exit 1 fi if [[ "$OUTPUT_DIR" == *.ipa ]]; then TARGET_PATH="$OUTPUT_DIR" # 确保父目录存在且可写 parent_dir="$(dirname "$TARGET_PATH")" if ! mkdir -p "$parent_dir" 2>/dev/null || [[ ! -w "$parent_dir" ]]; then echo "❌ 错误: 无法写入到: $TARGET_PATH" >&2 echo " 使用默认路径: $DEFAULT_OUTPUT_DIR/$(basename "$BUILT_IPA")" >&2 TARGET_PATH="$DEFAULT_OUTPUT_DIR/$(basename "$BUILT_IPA")" mkdir -p "$(dirname "$TARGET_PATH")" fi else TARGET_PATH="$OUTPUT_DIR/$(basename "$BUILT_IPA")" # 确保目录存在且可写 if ! mkdir -p "$OUTPUT_DIR" 2>/dev/null || [[ ! -w "$OUTPUT_DIR" ]]; then echo "❌ 错误: 无法写入到: $OUTPUT_DIR" >&2 echo " 使用默认路径: $DEFAULT_OUTPUT_DIR" >&2 TARGET_PATH="$DEFAULT_OUTPUT_DIR/$(basename "$BUILT_IPA")" mkdir -p "$(dirname "$TARGET_PATH")" fi fi cp "$BUILT_IPA" "$TARGET_PATH" echo "✅ 构建完成:$TARGET_PATH" popd >/dev/null