239 lines
8.3 KiB
Bash
Executable File
239 lines
8.3 KiB
Bash
Executable File
#!/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.isDebug(lib/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 或 profile(debug 建议直接 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 "🎉 完成"
|