#!/bin/bash # iOS App Store 构建和上传脚本 # 支持自动构建、签名、上传到 App Store Connect set -e # 颜色输出 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # 日志函数 log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } # 检查环境 check_environment() { log_info "检查 App Store 构建环境..." # 检查必要的环境变量 if [ -z "$APPLE_ID" ] || [ -z "$APPLE_PASSWORD" ] || [ -z "$TEAM_ID" ]; then log_error "请先运行: source ios_signing_config.sh" exit 1 fi # 检查 Xcode if ! command -v xcodebuild &> /dev/null; then log_error "Xcode 未安装或不在 PATH 中" exit 1 fi # 检查 xcrun altool if ! command -v xcrun &> /dev/null; then log_error "xcrun 不可用" exit 1 fi log_success "环境检查通过" } # 检查证书和配置文件 check_certificates_and_profiles() { log_info "检查证书和配置文件..." # 检查分发证书 if ! security find-identity -v -p codesigning | grep -q "iPhone Distribution\|Apple Distribution"; then log_error "未找到有效的分发证书" log_info "请确保已安装 Apple Distribution 证书" exit 1 fi # 检查配置文件 local profiles_dir="$HOME/Library/MobileDevice/Provisioning Profiles" if [ ! -d "$profiles_dir" ]; then log_error "配置文件目录不存在: $profiles_dir" exit 1 fi log_success "证书和配置文件检查通过" } # 清理构建 clean_build() { log_info "清理之前的构建..." flutter clean rm -rf build/ios rm -rf ios/build log_success "清理完成" } # 获取依赖 get_dependencies() { log_info "获取 Flutter 依赖..." flutter pub get log_success "依赖获取完成" } # 构建 iOS 应用 build_ios_app() { log_info "开始构建 iOS 应用 (App Store)..." # 构建 Flutter 应用 flutter build ios --release --no-codesign # 检查构建结果 local app_path="build/ios/iphoneos/Runner.app" if [ ! -d "$app_path" ]; then log_error "iOS 应用构建失败: $app_path 不存在" exit 1 fi log_success "iOS 应用构建完成: $app_path" } # 使用 Xcode 构建和签名 build_with_xcode() { log_info "使用 Xcode 构建和签名..." # 进入 iOS 目录 cd ios # 使用 xcodebuild 构建 xcodebuild -workspace Runner.xcworkspace \ -scheme Runner \ -configuration Release \ -destination generic/platform=iOS \ -archivePath ../build/ios/Runner.xcarchive \ archive if [ $? -ne 0 ]; then log_error "Xcode 构建失败" exit 1 fi # 返回项目根目录 cd .. log_success "Xcode 构建完成" } # 导出 IPA export_ipa() { log_info "导出 IPA 文件..." # 创建导出选项文件 local export_options_plist="ios/ExportOptions.plist" cat > "$export_options_plist" << EOF method app-store teamID $TEAM_ID uploadBitcode uploadSymbols compileBitcode EOF # 导出 IPA xcodebuild -exportArchive \ -archivePath build/ios/Runner.xcarchive \ -exportPath build/ios/export \ -exportOptionsPlist "$export_options_plist" if [ $? -ne 0 ]; then log_error "IPA 导出失败" exit 1 fi # 移动 IPA 文件 local ipa_path="$IPA_PATH" mkdir -p "$(dirname "$ipa_path")" mv build/ios/export/Runner.ipa "$ipa_path" log_success "IPA 文件导出成功: $ipa_path" } # 验证 IPA validate_ipa() { local ipa_path=$1 log_info "验证 IPA 文件..." # 使用 xcrun altool 验证 xcrun altool --validate-app \ -f "$ipa_path" \ -t ios \ -u "$APPLE_ID" \ -p "$APPLE_PASSWORD" if [ $? -eq 0 ]; then log_success "IPA 验证通过" else log_error "IPA 验证失败" exit 1 fi } # 上传到 App Store upload_to_appstore() { local ipa_path=$1 log_info "上传到 App Store Connect..." # 使用 xcrun altool 上传 xcrun altool --upload-app \ -f "$ipa_path" \ -t ios \ -u "$APPLE_ID" \ -p "$APPLE_PASSWORD" if [ $? -eq 0 ]; then log_success "上传到 App Store Connect 成功" else log_error "上传到 App Store Connect 失败" exit 1 fi } # 创建 DMG create_dmg() { local ipa_path=$1 local dmg_path=$2 log_info "创建 DMG 文件..." # 创建临时目录 local temp_dir="build/ios/temp_dmg" mkdir -p "$temp_dir" # 复制 IPA 到临时目录 cp "$ipa_path" "$temp_dir/" # 创建 DMG hdiutil create -srcfolder "$temp_dir" \ -volname "BearVPN iOS App Store" \ -fs HFS+ \ -format UDZO \ -imagekey zlib-level=9 \ "$dmg_path" # 清理临时目录 rm -rf "$temp_dir" if [ -f "$dmg_path" ]; then log_success "DMG 文件创建成功: $dmg_path" else log_error "DMG 文件创建失败" exit 1 fi } # 显示构建结果 show_result() { local ipa_path=$1 local dmg_path=$2 log_success "==========================================" log_success "iOS App Store 构建完成!" log_success "==========================================" log_info "应用名称: $APP_NAME" log_info "版本: $VERSION" log_info "Bundle ID: $BUNDLE_ID" log_info "IPA 文件: $ipa_path" log_info "DMG 文件: $dmg_path" log_info "开发者: $DISTRIBUTION_IDENTITY" log_success "==========================================" log_info "应用已上传到 App Store Connect" log_info "请在 App Store Connect 中完成最终发布" log_success "==========================================" } # 主函数 main() { local upload=${1:-"true"} log_info "开始 iOS App Store 构建流程..." log_info "上传到 App Store: $upload" log_info "==========================================" check_environment check_certificates_and_profiles clean_build get_dependencies build_ios_app build_with_xcode export_ipa # 设置路径 local ipa_path="$IPA_PATH" local dmg_path="$DMG_PATH" # 创建输出目录 mkdir -p "$(dirname "$dmg_path")" # 验证 IPA validate_ipa "$ipa_path" # 上传到 App Store(如果启用) if [ "$upload" = "true" ]; then upload_to_appstore "$ipa_path" else log_info "跳过上传到 App Store" fi # 创建 DMG create_dmg "$ipa_path" "$dmg_path" # 显示结果 show_result "$ipa_path" "$dmg_path" log_success "所有操作完成!" } # 显示帮助信息 show_help() { echo "iOS App Store 构建和上传脚本" echo "" echo "用法: $0 [选项]" echo "" echo "选项:" echo " upload 构建并上传到 App Store Connect (默认)" echo " build 仅构建,不上传" echo " help 显示此帮助信息" echo "" echo "示例:" echo " $0 # 构建并上传到 App Store" echo " $0 upload # 构建并上传到 App Store" echo " $0 build # 仅构建,不上传" echo "" echo "注意: 请先运行 'source ios_signing_config.sh' 配置签名信息" } # 处理命令行参数 case "${1:-}" in "help"|"-h"|"--help") show_help exit 0 ;; "upload"|"build") main "$1" ;; "") main "upload" ;; *) log_error "未知选项: $1" show_help exit 1 ;; esac