Files
2026-09-15 15:44:13 +07:00

239 lines
8.3 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# 出错即停
set -e
# 自动使用 .fvmrc 中指定的 Flutter 版本(避免使用 PATH 里的系统 flutter 导致依赖冲突)
if [ -f "./.fvmrc" ]; then
FVM_VERSION=$(grep -o '"flutter"[[:space:]]*:[[:space:]]*"[^"]*"' .fvmrc | sed -E 's/.*"([^"]+)"$/\1/')
if [ -n "$FVM_VERSION" ] && [ -x "$HOME/fvm/versions/$FVM_VERSION/bin/flutter" ]; then
export PATH="$HOME/fvm/versions/$FVM_VERSION/bin:$PATH"
echo "🔧 使用 FVM Flutter 版本: $FVM_VERSION"
fi
fi
# 参数说明
if [ $# -lt 1 ]; then
printf -- "Build hgdj release package.\n\n"
printf -- "Usage: $0 <target> [mode] [export_method] [export_options_plist] [sign_mode]\n"
printf -- " target : a(android) | i(ios) | all\n"
printf -- " mode : release(默认) | debug | profile\n"
printf -- " export_method (iOS) : app-store(默认) | ad-hoc | development | enterprise\n"
printf -- " export_options_plist : ExportOptions.plist 路径(iOS 可选,留空用默认)\n"
printf -- " sign_mode (iOS) : codesign(默认) | nocodesign\n\n"
printf -- "Examples:\n"
printf -- " $0 a # Android release\n"
printf -- " $0 a debug # Android debug\n"
printf -- " $0 i # iOS release + app-store\n"
printf -- " $0 i release ad-hoc # iOS release + ad-hoc\n"
printf -- " $0 i release app-store ios/ExportOptions.plist\n"
printf -- " $0 i release app-store \"\" nocodesign # iOS 不签名出包\n"
printf -- " $0 all # Android + iOS release\n"
exit 1
fi
TARGET=$1
MODE=${2:-release}
EXPORT_METHOD=${3:-app-store}
EXPORT_OPTIONS_PLIST=${4:-}
SIGN_MODE=${5:-codesign}
# 参数校验
if [ "$TARGET" != "a" ] && [ "$TARGET" != "i" ] && [ "$TARGET" != "all" ]; then
echo "❌ 参数错误:target 只支持 a | i | all,你传的是 '$TARGET'"
exit 1
fi
if [ "$MODE" != "release" ] && [ "$MODE" != "debug" ] && [ "$MODE" != "profile" ]; then
echo "❌ 参数错误:mode 只支持 release | debug | profile,你传的是 '$MODE'"
exit 1
fi
case "$EXPORT_METHOD" in
app-store|ad-hoc|development|enterprise) ;;
*)
echo "❌ 参数错误:export_method 只支持 app-store | ad-hoc | development | enterprise,你传的是 '$EXPORT_METHOD'"
exit 1
;;
esac
if [ "$SIGN_MODE" != "codesign" ] && [ "$SIGN_MODE" != "nocodesign" ]; then
echo "❌ 参数错误:sign_mode 只支持 codesign | nocodesign,你传的是 '$SIGN_MODE'"
exit 1
fi
if [ -n "$EXPORT_OPTIONS_PLIST" ] && [ ! -f "$EXPORT_OPTIONS_PLIST" ]; then
echo "❌ 未找到 ExportOptions.plist$EXPORT_OPTIONS_PLIST"
exit 1
fi
# 项目根路径校验
if [ ! -f "./pubspec.yaml" ]; then
echo "❌ 请在项目根目录下执行此脚本"
exit 1
fi
PROJECT_PATH=$(pwd)
PUBSPEC_FILE="pubspec.yaml"
# 提取项目信息
APP_NAME=$(grep '^name:' "$PUBSPEC_FILE" | awk '{print $2}')
RAW_VERSION=$(grep '^version:' "$PUBSPEC_FILE" | awk '{print $2}')
SAFE_VERSION=${RAW_VERSION//+/_}
TIME_TAG=$(date +"%m%d-%H%M")
# 读取应用内 Config.isDebuglib/config/config.dart),决定产物命名标识:
# isDebug=true → 命名标识固定为 debug(如 hgdj_debug_xxxx),方便区分内测包
# isDebug=false → 正式环境,命名沿用构建模式(release/profile),保持不变
CONFIG_FILE="lib/config/config.dart"
CONFIG_DEBUG=$(grep -E 'static[[:space:]]+const[[:space:]]+isDebug[[:space:]]*=' "$CONFIG_FILE" 2>/dev/null | grep -oE 'true|false' | head -1)
if [ "$CONFIG_DEBUG" = "true" ]; then
BUILD_TAG="debug"
elif [ "$CONFIG_DEBUG" = "false" ]; then
BUILD_TAG="$MODE"
else
echo "⚠️ 未能从 $CONFIG_FILE 解析 Config.isDebug,命名回退为构建模式:$MODE"
BUILD_TAG="$MODE"
fi
DIST_DIR="./dist"
mkdir -p "$DIST_DIR"
# 打印信息并二次确认
echo "=========================================="
echo "📦 准备打包"
echo "📌 项目名: $APP_NAME"
echo "📌 版本号: $RAW_VERSION"
echo "📌 目标平台: $TARGET"
echo "📌 构建模式: $MODE"
echo "📌 Config.isDebug: ${CONFIG_DEBUG:-未知} → 命名标识: $BUILD_TAG"
if [ "$TARGET" = "i" ] || [ "$TARGET" = "all" ]; then
echo "📌 iOS 导出: $EXPORT_METHOD"
echo "📌 iOS 签名: $SIGN_MODE"
if [ -n "$EXPORT_OPTIONS_PLIST" ]; then
echo "📌 ExportOptions.plist: $EXPORT_OPTIONS_PLIST"
fi
fi
echo "📌 工程路径: $PROJECT_PATH"
echo "=========================================="
echo "确认无误?(按回车继续,Ctrl+C 取消)"
read -r CONFIRM
# Android 构建
build_android() {
echo "▶️ 开始构建 Android $MODE 包..."
if [ "$MODE" = "release" ]; then
flutter build apk --release --target-platform android-arm64
APK_SOURCE="build/app/outputs/flutter-apk/app-release.apk"
elif [ "$MODE" = "profile" ]; then
flutter build apk --profile --target-platform android-arm64
APK_SOURCE="build/app/outputs/flutter-apk/app-profile.apk"
else
flutter build apk --debug --target-platform android-arm64
APK_SOURCE="build/app/outputs/flutter-apk/app-debug.apk"
fi
if [ ! -f "$APK_SOURCE" ]; then
echo "❌ 构建失败,未找到 APK:$APK_SOURCE"
exit 1
fi
APK_NAME="${APP_NAME}_${BUILD_TAG}_${SAFE_VERSION}_${TIME_TAG}.apk"
APK_DEST="${DIST_DIR}/${APK_NAME}"
cp "$APK_SOURCE" "$APK_DEST"
APK_SIZE=$(du -h "$APK_DEST" | awk '{print $1}')
echo "✅ Android 构建成功!"
echo "📁 输出路径: $APK_DEST"
echo "📏 文件大小: $APK_SIZE"
LAST_BUILT_FILE="$APK_DEST"
}
# iOS 构建(基于 flutter build ipa,一步出 ipa
build_ios() {
echo "▶️ 开始构建 iOS $MODE 包..."
if [ "$MODE" = "debug" ]; then
echo "⚠️ iOS 不支持 debug 模式打包,请使用 release 或 profiledebug 建议直接 flutter run"
exit 1
fi
IPA_OUTPUT_DIR="build/ios/ipa"
ARCHIVE_OUTPUT_DIR="build/ios/archive"
if [ "$MODE" = "release" ]; then
if [ "$SIGN_MODE" = "nocodesign" ]; then
flutter build ipa --release --no-codesign
elif [ -n "$EXPORT_OPTIONS_PLIST" ]; then
flutter build ipa --release --export-options-plist="$EXPORT_OPTIONS_PLIST"
else
flutter build ipa --release --export-method "$EXPORT_METHOD"
fi
else
if [ "$SIGN_MODE" = "nocodesign" ]; then
flutter build ipa --profile --no-codesign
elif [ -n "$EXPORT_OPTIONS_PLIST" ]; then
flutter build ipa --profile --export-options-plist="$EXPORT_OPTIONS_PLIST"
else
flutter build ipa --profile --export-method "$EXPORT_METHOD"
fi
fi
# 查找产物 ipa
IPA_SOURCE=""
for candidate in "$IPA_OUTPUT_DIR"/*.ipa; do
if [ -f "$candidate" ]; then
IPA_SOURCE="$candidate"
break
fi
done
if [ -z "$IPA_SOURCE" ]; then
if [ "$SIGN_MODE" = "nocodesign" ]; then
if [ -d "$ARCHIVE_OUTPUT_DIR" ]; then
echo "⚠️ 未签名模式下 Flutter 通常不会导出可安装的 IPA"
echo "✅ 未签名构建产物目录:$ARCHIVE_OUTPUT_DIR"
echo "📌 如需可安装/可上传的 IPA,请在 Xcode 配好 Team 和证书后用 codesign 模式重新打包"
LAST_BUILT_FILE=""
return
fi
echo "❌ 未签名构建完成,但未找到 archive 目录:$ARCHIVE_OUTPUT_DIR"
exit 1
fi
echo "❌ 构建结束,但未找到 IPA 文件:$IPA_OUTPUT_DIR/*.ipa"
echo "📌 通常是签名证书、Provisioning Profile 或 ExportOptions 配置有问题"
echo "📌 可以先试不签名出包:$0 i $MODE $EXPORT_METHOD \"$EXPORT_OPTIONS_PLIST\" nocodesign"
exit 1
fi
# 与 apk 命名对齐:去掉冗余的 ios 标识(后缀已区分平台),导出方式去连字符(ad-hoc→adhoc)
IPA_NAME="${APP_NAME}_${BUILD_TAG}_${EXPORT_METHOD//-/}_${SAFE_VERSION}_${TIME_TAG}.ipa"
IPA_DEST="${DIST_DIR}/${IPA_NAME}"
cp "$IPA_SOURCE" "$IPA_DEST"
IPA_SIZE=$(du -h "$IPA_DEST" | awk '{print $1}')
echo "✅ iOS 构建成功!"
echo "📁 输出路径: $IPA_DEST"
echo "📏 文件大小: $IPA_SIZE"
LAST_BUILT_FILE="$IPA_DEST"
}
# 分发
case "$TARGET" in
a) build_android ;;
i) build_ios ;;
all) build_android; build_ios ;;
esac
# 自动在 Finder 中打开并选中产物(仅 macOS)
if [[ "$OSTYPE" == "darwin"* ]]; then
if [ -n "$LAST_BUILT_FILE" ] && [ -f "$LAST_BUILT_FILE" ]; then
open -R "$LAST_BUILT_FILE"
else
open "$DIST_DIR"
fi
fi
echo "🎉 完成"