| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- #!/usr/bin/env bash
- # CoinVision Flutter 自动打包脚本
- #
- # 用法:
- # ./scripts/build.sh 构建 Android + iOS Release (默认)
- # ./scripts/build.sh android 仅构建 Android Release
- # ./scripts/build.sh android-debug 仅构建 Android Debug
- # ./scripts/build.sh ios 仅构建 iOS Release
- # ./scripts/build.sh ios-adhoc 仅构建 iOS Ad-hoc
- # ./scripts/build.sh --clean 构建前先执行 flutter clean
- # ./scripts/build.sh -h 显示帮助
- #
- # 产物归档位置: releases/v<版本>_<时间戳>/
- # ├── app-release.apk
- # ├── *.ipa
- # ├── symbols-android/ (Dart 混淆符号,用于 flutter symbolize 反解)
- # ├── symbols-ios/ (同上)
- # └── BUILD_INFO.txt
- set -euo pipefail
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
- PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
- cd "$PROJECT_ROOT"
- RELEASES_DIR="releases"
- SYMBOLS_ANDROID_DIR="build/debug-info/android"
- SYMBOLS_IOS_DIR="build/debug-info/ios"
- if [ -t 1 ]; then
- RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
- BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
- else
- RED=''; GREEN=''; YELLOW=''; BLUE=''; CYAN=''; BOLD=''; NC=''
- fi
- log_info() { printf "${BLUE}==>${NC} %s\n" "$*"; }
- log_ok() { printf "${GREEN}✓${NC} %s\n" "$*"; }
- log_warn() { printf "${YELLOW}!${NC} %s\n" "$*"; }
- log_err() { printf "${RED}✗${NC} %s\n" "$*" >&2; }
- log_step() { printf "\n${CYAN}${BOLD}▶ %s${NC}\n" "$*"; }
- usage() {
- cat <<EOF
- CoinVision Flutter 自动打包脚本
- 用法:
- ./scripts/build.sh [模式] [选项]
- 模式:
- all 构建 Android + iOS Release (默认)
- android 仅构建 Android Release
- android-debug 仅构建 Android Debug
- ios 仅构建 iOS Release
- ios-adhoc 仅构建 iOS Ad-hoc
- 选项:
- --clean 构建前执行 flutter clean
- -h, --help 显示此帮助
- 示例:
- ./scripts/build.sh # 同时打 Android + iOS Release
- ./scripts/build.sh android # 只打 Android Release
- ./scripts/build.sh ios-adhoc --clean # 清理后打 iOS Ad-hoc
- 产物归档位置: ${RELEASES_DIR}/v<版本>_<时间戳>/
- EOF
- }
- MODE="all"
- DO_CLEAN=false
- while [ $# -gt 0 ]; do
- case "$1" in
- -h|--help) usage; exit 0 ;;
- --clean) DO_CLEAN=true ;;
- all|android|android-debug|ios|ios-adhoc) MODE="$1" ;;
- *) log_err "未知参数: $1"; echo; usage; exit 1 ;;
- esac
- shift
- done
- if ! command -v flutter >/dev/null 2>&1; then
- log_err "未找到 flutter 命令,请检查 PATH"
- exit 1
- fi
- if [ ! -f pubspec.yaml ]; then
- log_err "未在 pubspec.yaml 所在目录执行,当前: $(pwd)"
- exit 1
- fi
- VERSION="$(grep '^version:' pubspec.yaml | awk '{print $2}' | tr -d '[:space:]')"
- if [ -z "$VERSION" ]; then
- log_err "无法从 pubspec.yaml 读取版本号"
- exit 1
- fi
- TIMESTAMP="$(date +%Y%m%d_%H%M)"
- RELEASE_DIR="${RELEASES_DIR}/v${VERSION}_${TIMESTAMP}"
- log_step "CoinVision 打包"
- log_info "版本 : v${VERSION}"
- log_info "模式 : ${MODE}"
- log_info "归档目录 : ${RELEASE_DIR}"
- log_info "Git 分支 : $(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo 'N/A')"
- log_info "Git 提交 : $(git rev-parse --short HEAD 2>/dev/null || echo 'N/A')"
- DIRTY_COUNT=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
- if [ "$DIRTY_COUNT" -gt 0 ]; then
- log_warn "当前有 $DIRTY_COUNT 个未提交的改动,构建的产物可能包含未追踪代码"
- fi
- if [ "$DO_CLEAN" = true ]; then
- log_step "flutter clean"
- flutter clean
- log_ok "clean 完成"
- fi
- log_step "flutter pub get"
- flutter pub get >/dev/null
- log_ok "pub get 完成"
- mkdir -p "$RELEASE_DIR"
- copy_android_symbols() {
- if [ -d "$SYMBOLS_ANDROID_DIR" ] && [ "$(ls -A "$SYMBOLS_ANDROID_DIR" 2>/dev/null)" ]; then
- mkdir -p "$RELEASE_DIR/symbols-android"
- cp -r "$SYMBOLS_ANDROID_DIR"/* "$RELEASE_DIR/symbols-android/"
- log_ok "Android 符号 → $RELEASE_DIR/symbols-android/"
- fi
- }
- copy_ios_symbols() {
- if [ -d "$SYMBOLS_IOS_DIR" ] && [ "$(ls -A "$SYMBOLS_IOS_DIR" 2>/dev/null)" ]; then
- mkdir -p "$RELEASE_DIR/symbols-ios"
- cp -r "$SYMBOLS_IOS_DIR"/* "$RELEASE_DIR/symbols-ios/"
- log_ok "iOS 符号 → $RELEASE_DIR/symbols-ios/"
- fi
- }
- build_android_debug() {
- log_step "构建 Android Debug APK"
- flutter build apk --debug \
- --target-platform android-arm,android-arm64
- local apk="build/app/outputs/flutter-apk/app-debug.apk"
- if [ ! -f "$apk" ]; then
- log_err "未找到产物: $apk"
- return 1
- fi
- cp "$apk" "$RELEASE_DIR/app-debug.apk"
- local size; size=$(du -h "$apk" | cut -f1)
- log_ok "Debug APK (${size}) → $RELEASE_DIR/app-debug.apk"
- }
- build_android_release() {
- log_step "构建 Android Release APK"
- flutter build apk --release \
- --target-platform android-arm,android-arm64 \
- --obfuscate \
- --split-debug-info="$SYMBOLS_ANDROID_DIR"
- local apk="build/app/outputs/flutter-apk/app-release.apk"
- if [ ! -f "$apk" ]; then
- log_err "未找到产物: $apk"
- return 1
- fi
- cp "$apk" "$RELEASE_DIR/app-release.apk"
- copy_android_symbols
- local size; size=$(du -h "$apk" | cut -f1)
- log_ok "Release APK (${size}) → $RELEASE_DIR/app-release.apk"
- }
- build_ios_release() {
- log_step "构建 iOS Release IPA"
- flutter build ipa --release \
- --obfuscate \
- --split-debug-info="$SYMBOLS_IOS_DIR"
- local ipa
- ipa="$(find build/ios/ipa -maxdepth 1 -name "*.ipa" -type f 2>/dev/null | head -1)"
- if [ -z "$ipa" ]; then
- log_err "未找到 IPA 产物,请检查 Xcode 签名配置"
- return 1
- fi
- local dst="$RELEASE_DIR/$(basename "$ipa" .ipa)-release.ipa"
- cp "$ipa" "$dst"
- copy_ios_symbols
- local size; size=$(du -h "$ipa" | cut -f1)
- log_ok "Release IPA (${size}) → $dst"
- }
- build_ios_adhoc() {
- log_step "构建 iOS Ad-hoc IPA"
- flutter build ipa --release --export-method=ad-hoc \
- --obfuscate \
- --split-debug-info="$SYMBOLS_IOS_DIR"
- local ipa
- ipa="$(find build/ios/ipa -maxdepth 1 -name "*.ipa" -type f 2>/dev/null | head -1)"
- if [ -z "$ipa" ]; then
- log_err "未找到 IPA 产物,请检查 Xcode 签名配置"
- return 1
- fi
- local dst="$RELEASE_DIR/$(basename "$ipa" .ipa)-adhoc.ipa"
- cp "$ipa" "$dst"
- copy_ios_symbols
- local size; size=$(du -h "$ipa" | cut -f1)
- log_ok "Ad-hoc IPA (${size}) → $dst"
- }
- case "$MODE" in
- all) build_android_release; build_ios_release ;;
- android) build_android_release ;;
- android-debug) build_android_debug ;;
- ios) build_ios_release ;;
- ios-adhoc) build_ios_adhoc ;;
- esac
- {
- echo "版本 : v${VERSION}"
- echo "构建时间 : $(date '+%Y-%m-%d %H:%M:%S')"
- echo "构建模式 : ${MODE}"
- echo "Git 分支 : $(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo 'N/A')"
- echo "Git 提交 : $(git rev-parse --short HEAD 2>/dev/null || echo 'N/A')"
- echo "未提交改动 : ${DIRTY_COUNT}"
- echo ""
- echo "Flutter 版本:"
- flutter --version 2>&1 | sed 's/^/ /'
- } > "$RELEASE_DIR/BUILD_INFO.txt"
- log_step "完成"
- log_info "归档目录: $(pwd)/${RELEASE_DIR}"
- echo ""
- ls -lh "$RELEASE_DIR"
- if [ -f .gitignore ] && ! grep -qE "^/?releases/?$" .gitignore; then
- echo ""
- log_warn "建议将 '${RELEASES_DIR}/' 加入 .gitignore,避免构建产物进入 Git"
- fi
|