web_shell_flutter/tool/Jenkinsfile

398 lines
17 KiB
Groovy
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.

// 统一Jenkins构建配置 (Melos Monorepo版本)
// 同时支持开发和生产环境
pipeline {
agent any
options {
// 超时时间
timeout(time: 45, unit: 'MINUTES')
// 保留构建历史
buildDiscarder(logRotator(numToKeepStr: '20'))
// 时间戳显示
timestamps()
// 彩色输出
ansiColor('xterm')
}
environment {
// 基础配置
PROJECT_NAME = 'Learning Officer OA'
APP_NAME = 'LearningOfficerOA'
FVM_VERSION = '3.38.9'
// 构建模式 (始终使用 release 获得最佳性能)
BUILD_MODE = 'release'
// 动态环境配置 (根据参数设置)
ENVIRONMENT = "${params.BUILD_ENVIRONMENT}"
// 归档配置
RELEASE_DIR = "${env.HOME}/release/oa/${params.BUILD_ENVIRONMENT}"
// Melos Monorepo 配置
APP_DIR = 'apps/oa_app'
// Fastlane和Flutter配置
LC_ALL = 'en_US.UTF-8'
LANG = 'en_US.UTF-8'
CI = 'true'
FLUTTER_SUPPRESS_ANALYTICS = 'true'
FLUTTER_NO_ANALYTICS = 'true'
// PATH配置使用环境变量避免硬编码用户路径
PATH = "${env.HOME ?: '/Users/yuanxuan'}/.pub-cache/bin:/opt/homebrew/bin:/usr/local/bin:${env.PATH}"
// 签名配置
KEYCHAIN_PATH = "${HOME}/Library/Keychains/login.keychain-db"
KEYCHAIN_PASSWORD = "${KEYCHAIN_PASSWORD}"
// 版本信息
APP_VERSION = ''
BUILD_NUMBER = ''
MAIN_APK_FOUND = 'false'
}
parameters {
// 🎯 环境选择 (核心参数)
choice(
name: 'BUILD_ENVIRONMENT',
choices: ['development', 'preview', 'production'],
description: '🏗️ 构建环境: development(开发测试) | preview(预发布) | production(正式发布)'
)
// 📱 平台选择
choice(
name: 'PLATFORM',
choices: ['android', 'ios', 'all'],
description: '📱 构建平台: android | ios | all'
)
// 🧹 清理构建
booleanParam(
name: 'CLEAN_BUILD',
defaultValue: true,
description: '🧹 是否执行清理构建 (生产环境推荐开启)'
)
// 🍎 iOS配置 (开发/预发布环境)
booleanParam(
name: 'UPLOAD_TESTFLIGHT',
defaultValue: true,
description: '🚀 是否自动上传iOS到TestFlight (仅开发/预发布环境有效)'
)
// 🍎 iOS分发类型 (生产环境)
choice(
name: 'IOS_DISTRIBUTION_TYPE',
choices: ['testflight', 'adhoc', 'appstore'],
description: '🍎 iOS分发类型: adhoc(企业分发) | testflight(外部测试) | appstore(应用商店)'
)
}
stages {
stage('🔧 环境初始化') {
steps {
script {
echo "🚀 开始构建: ${PROJECT_NAME} [${params.BUILD_ENVIRONMENT}]"
}
sh '''
#!/bin/zsh
set +x
# 静默加载环境
[ -f "$HOME/.zshrc" ] && source "$HOME/.zshrc" > /dev/null 2>&1
# 仅输出核心版本信息
if command -v fvm &> /dev/null; then
echo " - Flutter: $(fvm flutter --version | head -1)"
echo " - Melos: $(melos --version 2>/dev/null || echo '本地依赖')"
else
echo "❌ 错误: 未找到FVM环境"
exit 1
fi
'''
}
}
stage('📥 依赖管理') {
steps {
script {
echo "📦 正在同步 Monorepo 依赖 (Bootstrap)..."
sh '''
#!/bin/zsh
set +x
[ -f "$HOME/.zshrc" ] && source "$HOME/.zshrc" > /dev/null 2>&1
# 使用 melos bootstrap 初始化所有包
melos bootstrap
echo " ✅ 依赖同步完成"
'''
}
}
}
stage('📝 版本与生成') {
steps {
script {
sh '''
#!/bin/zsh
set +x
[ -f "$HOME/.zshrc" ] && source "$HOME/.zshrc" > /dev/null 2>&1
# 运行版本管理 (在 app 目录)
# 运行版本管理 (从根目录运行 Dart 脚本,避免子目录缺少依赖)
if [ -f "scripts/version_manager.dart" ]; then
# 确保根目录依赖已准备好 (通常在 Bootstrap 阶段完成,但在某些 CI 环境可能需要显式 pub get)
# dart pub get # 如果需要,取消注释
eval $(dart scripts/version_manager.dart ci-clean --path ${APP_DIR}/pubspec.yaml)
echo " - 目标版本: ${APP_FULL_VERSION}"
fi
# 代码生成 (使用 melos run generate 覆盖所有包)
echo " - 正在为所有包生成代码..."
melos run generate
echo " ✅ 代码生成完成"
'''
}
}
}
stage('🏗️ 编译构建') {
steps {
script {
if (params.PLATFORM == 'android' || params.PLATFORM == 'all') {
echo "🤖 正在构建 Android APK..."
sh '''
#!/bin/zsh
set +x
[ -f "$HOME/.zshrc" ] && source "$HOME/.zshrc" > /dev/null 2>&1
cd ${APP_DIR}
if [ "${CLEAN_BUILD}" = "true" ]; then
echo " - 执行 flutter clean..."
fvm flutter clean
fi
fvm flutter build apk --release --dart-define=APP_ENV=${ENVIRONMENT}
'''
echo " ✅ Android 构建成功"
}
if (params.PLATFORM == 'ios' || params.PLATFORM == 'all') {
echo "🍎 正在构建 iOS IPA (via Melos)..."
sh '''
#!/bin/zsh
set -e
[ -f "$HOME/.zshrc" ] && source "$HOME/.zshrc" > /dev/null 2>&1
cd ${APP_DIR}
if [ "${CLEAN_BUILD}" = "true" ]; then
echo " - 执行 flutter clean..."
fvm flutter clean
fi
# clean 后必须重新拉依赖,确保插件注册文件 (.dart_tool/flutter_build) 完整
echo " - 恢复 Flutter 依赖..."
fvm flutter pub get
# 统一更新 CocoaPods 仓库并按 Podfile 安装依赖
echo " - CocoaPods: repo update + install..."
retry=0
until [ $retry -ge 3 ]
do
if (cd ios && pod install --repo-update); then
echo " ✅ CocoaPods 依赖已就绪"
break
fi
retry=$((retry+1))
echo " ⚠️ pod install 失败,重试 ${retry}/3..."
sleep 3
done
if [ $retry -ge 3 ]; then
echo "❌ pod install 连续失败,停止构建"
exit 1
fi
cd -
# Melos 脚本会自动进入 apps/oa_app 目录并执行,通过 -- 传递额外参数
melos run build:ipa -- --dart-define=APP_ENV=${ENVIRONMENT}
'''
echo " ✅ iOS 构建成功"
}
}
}
}
stage('🚀 上传 TestFlight') {
when {
allOf {
anyOf {
expression { params.BUILD_ENVIRONMENT == 'development' }
expression { params.BUILD_ENVIRONMENT == 'preview' }
}
expression { params.UPLOAD_TESTFLIGHT == true }
anyOf {
expression { params.PLATFORM == 'ios' }
expression { params.PLATFORM == 'all' }
}
}
}
steps {
script {
echo "📤 准备上传到 TestFlight..."
sh '''
#!/bin/zsh
set +x
[ -f "$HOME/.zshrc" ] && source "$HOME/.zshrc" > /dev/null 2>&1
# 清理环境变量引号
export FASTLANE_APP_STORE_CONNECT_API_KEY_PATH=$(echo "${FASTLANE_APP_STORE_CONNECT_API_KEY_PATH}" | tr -d '"' | tr -d "'")
export APP_STORE_CONNECT_API_KEY_KEY_ID=$(echo "${APP_STORE_CONNECT_API_KEY_KEY_ID}" | tr -d '"' | tr -d "'")
export APP_STORE_CONNECT_API_KEY_ISSUER_ID=$(echo "${APP_STORE_CONNECT_API_KEY_ISSUER_ID}" | tr -d '"' | tr -d "'")
# 获取绝对路径的 IPA 文件
ABS_IPA_PATH=$(ls -t $(pwd)/${APP_DIR}/build/ios/ipa/*.ipa 2>/dev/null | head -1)
if [ -z "$ABS_IPA_PATH" ]; then echo "❌ 未找到IPA"; exit 1; fi
echo " - 产物: $(basename $ABS_IPA_PATH)"
if [ -f "fastlane/Gemfile" ]; then
cd fastlane
bundle install --quiet
echo " - 正在上传 (应用 Fastfile 内部补丁)..."
# 1. 清理环境,依赖 Fastfile 顶部的 require 解决兼容性
unset RUBYOPT
# 2. 设置 Fastlane 官方环境变量
export SPACESHIP_CONNECT_API_KEY_ID="$APP_STORE_CONNECT_API_KEY_KEY_ID"
export SPACESHIP_CONNECT_API_ISSUER_ID="$APP_STORE_CONNECT_API_KEY_ISSUER_ID"
export SPACESHIP_CONNECT_API_KEY_PATH="$FASTLANE_APP_STORE_CONNECT_API_KEY_PATH"
# 3. 执行上传 (使用绝对路径)
bundle exec fastlane upload_to_testflight_with_key ipa:"$ABS_IPA_PATH"
fi
echo " ✅ TestFlight 上传完成"
'''
}
}
}
}
post {
always {
script {
echo "🧹 处理构建产物和清理"
// 根据环境归档不同的构建产物
echo "📦 归档${params.BUILD_ENVIRONMENT}环境构建产物..."
// 归档 APK (仅限 Android 平台)
if ((params.PLATFORM == 'android' || params.PLATFORM == 'all') && fileExists('apps/oa_app/build/app/outputs/flutter-apk/app-release.apk')) {
echo " - 归档Android Release APK..."
archiveArtifacts artifacts: 'apps/oa_app/build/app/outputs/flutter-apk/app-release.apk', fingerprint: true
echo " ✅ Android Release APK已归档"
}
// 归档 AAB (仅限 Android 平台)
if ((params.PLATFORM == 'android' || params.PLATFORM == 'all') && fileExists('apps/oa_app/build/app/outputs/bundle/release/app-release.aab')) {
echo " - 归档Android AAB..."
archiveArtifacts artifacts: 'apps/oa_app/build/app/outputs/bundle/release/app-release.aab', fingerprint: true
echo " ✅ Android AAB已归档"
}
// 归档iOS IPA (仅限 iOS 平台)
if (params.PLATFORM == 'ios' || params.PLATFORM == 'all') {
def ipaFileCount = sh(
script: "find apps/oa_app/build/ios/ipa -name '*.ipa' 2>/dev/null | wc -l | tr -d ' '",
returnStdout: true
).trim()
if (ipaFileCount && ipaFileCount != '0') {
echo " - 归档iOS IPA..."
archiveArtifacts artifacts: 'apps/oa_app/build/ios/ipa/*.ipa', fingerprint: true
echo " ✅ iOS IPA已归档 (找到 ${ipaFileCount} 个文件)"
} else {
echo " ⚠️ 未找到iOS IPA文件"
}
}
// 归档FVM配置
if (fileExists('.fvmrc')) {
archiveArtifacts artifacts: '.fvmrc', fingerprint: true, allowEmptyArchive: true
echo " ✅ FVM配置已归档"
}
}
}
success {
script {
echo "🎉 ${params.BUILD_ENVIRONMENT}环境构建成功!"
echo ""
echo "✅ 构建总结:"
echo " - 项目: ${PROJECT_NAME}"
echo " - 应用: ${APP_NAME}"
echo " - 环境: ${params.BUILD_ENVIRONMENT}"
echo " - 平台: ${params.PLATFORM}"
echo " - 项目类型: Melos Monorepo"
echo " - Flutter版本: ${FVM_VERSION}"
echo " - 归档位置: ${RELEASE_DIR}"
echo ""
if (params.BUILD_ENVIRONMENT == 'production') {
echo "🏭 生产环境特性:"
echo " - Release APK (生产签名,可发布)"
echo " - iOS ${params.IOS_DISTRIBUTION_TYPE} IPA"
echo " - AAB (Google Play Store)"
echo ""
echo "⚠️ 重要提醒:"
echo " - 生产包仅用于正式发布"
echo " - 发布前请进行充分测试"
echo " - 确认版本号和更新说明"
} else if (params.BUILD_ENVIRONMENT == 'preview') {
echo "🔍 预发布环境特性:"
echo " - Release APK (优化版本,连接预发布库)"
echo " - iOS TestFlight版本"
if (params.UPLOAD_TESTFLIGHT) {
echo " - 已自动上传到TestFlight"
}
echo ""
echo "🚀 下一步:"
echo " - 供测试团队/产品验收"
} else {
echo "🛠️ 开发环境特性:"
echo " - Release APK (优化版本,用于测试)"
echo " - iOS TestFlight版本"
if (params.UPLOAD_TESTFLIGHT) {
echo " - 已自动上传到TestFlight"
}
echo ""
echo "🚀 下一步:"
echo " - 下载APK进行内部测试"
echo " - TestFlight查看iOS测试版本"
}
}
}
failure {
script {
echo "❌ ${params.BUILD_ENVIRONMENT}环境构建失败"
echo ""
echo "🔍 故障排除建议:"
echo " 1. 检查Flutter环境配置"
echo " 2. 验证Melos Monorepo依赖配置"
if (params.BUILD_ENVIRONMENT == 'production') {
echo " 3. 验证iOS Distribution/Ad-hoc证书"
echo " 4. 确认Android Release签名配置"
echo " 5. 检查生产环境权限和密钥"
} else {
echo " 3. 验证App Store Connect API密钥配置"
echo " 4. 确认TestFlight上传权限"
echo " 5. 检查开发/预发布证书配置"
}
echo " 6. 查看详细构建日志"
}
}
}
}