// 统一Jenkins构建配置 (单仓多App定制版) // 专注 Android APK 构建,支持多应用与版本动态传参 pipeline { agent any options { // 超时时间 timeout(time: 45, unit: 'MINUTES') // 保留构建历史 buildDiscarder(logRotator(numToKeepStr: '20')) // 时间戳显示 timestamps() // 彩色输出 ansiColor('xterm') } environment { // 基础配置 PROJECT_NAME = 'Web Android Shell' // 此项目不使用 Melos,但通过 dart run tool/generate_app.dart 生成 App 工程 FVM_VERSION = '3.41.2' // 动态环境配置 (根据参数设置) ENVIRONMENT = "${params.BUILD_ENVIRONMENT}" // 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}" } parameters { // 🎯 目标应用 (可由 tool/update_jenkins_apps.dart 动态更新内容) choice( name: 'APP_NAME', choices: ['aixue', 'test', 'yunxiao', 'all'], description: '📱 选择要打包的应用 (从 flavors 目录生成)' ) // 🎯 环境选择 choice( name: 'BUILD_ENVIRONMENT', choices: ['development', 'preview', 'production'], description: '🏗️ 构建环境: development(开发测试) | preview(预发布) | production(正式发布)' ) // 🎯 版本相关参数 string( name: 'VERSION_NAME', defaultValue: '', description: '🏷️ 覆盖版本名称 (例如 1.0.0)。留空则使用默认配置中的版本名。' ) string( name: 'BUILD_NUMBER', defaultValue: '', description: '🔢 覆盖构建编号 (例如 1)。留空则使用默认配置中的构建版本号。' ) // 🧹 清理构建 booleanParam( name: 'CLEAN_BUILD', defaultValue: true, description: '🧹 是否执行清理构建 (生产环境推荐开启)' ) } 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)" else echo "❌ 错误: 未找到FVM环境" exit 1 fi ''' } } stage('🏗️ 生成与构建') { steps { script { def appsToBuild = [] if (params.APP_NAME == 'all') { // 动态读取 flavors 目录下的所有 yaml 文件 def flavorFiles = sh(script: "ls flavors/*.yaml | awk -F'/' '{print \$2}' | awk -F'.' '{print \$1}'", returnStdout: true).trim().split('\n') appsToBuild = flavorFiles.toList() } else { appsToBuild = [params.APP_NAME] } for (int i = 0; i < appsToBuild.size(); i++) { def currentApp = appsToBuild[i] if (!currentApp) continue echo "🤖 开始处理应用: ${currentApp}..." // 步骤1:生成 App 工程 (如果不存在) sh """ #!/bin/zsh set +x [ -f "\\$HOME/.zshrc" ] && source "\\$HOME/.zshrc" > /dev/null 2>&1 if [ ! -d "apps/${currentApp}" ]; then echo " - 应用目录 apps/${currentApp} 不存在,正在执行 generate_app 脚本生成项目..." dart run tool/generate_app.dart ${currentApp} else echo " - 应用目录 apps/${currentApp} 已存在,跳过生成工程阶段。" fi """ // 步骤2:执行 Flutter 构建 echo " - 正在为 ${currentApp} 构建 Android APK..." sh """ #!/bin/zsh set +x [ -f "\\$HOME/.zshrc" ] && source "\\$HOME/.zshrc" > /dev/null 2>&1 cd apps/${currentApp} # 先写入 shell 变量,避免 GString 里出现 "\${params.xxx}" 触发 Groovy 解析错误 CLEAN_BUILD='${params.CLEAN_BUILD}' VERSION_NAME='${params.VERSION_NAME}' BUILD_NUMBER='${params.BUILD_NUMBER}' BUILD_ENV='${params.BUILD_ENVIRONMENT}' if [ "\$CLEAN_BUILD" = "true" ]; then echo " - 执行 flutter clean..." fvm flutter clean fi fvm flutter pub get EXTRA_ARGS="" if [ -n "\$VERSION_NAME" ]; then EXTRA_ARGS="\$EXTRA_ARGS --build-name=\$VERSION_NAME" fi if [ -n "\$BUILD_NUMBER" ]; then EXTRA_ARGS="\$EXTRA_ARGS --build-number=\$BUILD_NUMBER" fi EXTRA_ARGS="\$EXTRA_ARGS --dart-define=APP_ENV=\$BUILD_ENV" echo " - 开始构建 APK 参数: \$EXTRA_ARGS" echo " - 开始执行 flutter build apk(构建期间每30秒输出一次心跳)..." run_build_with_heartbeat() { (fvm flutter build apk --release \$EXTRA_ARGS) & BUILD_PID=\$! while kill -0 \$BUILD_PID 2>/dev/null; do echo " - 构建仍在进行中... \$(date '+%H:%M:%S')" sleep 30 done wait \$BUILD_PID return \$? } if ! run_build_with_heartbeat; then echo " ⚠️ 首次构建失败,尝试清理 Gradle wrapper 缓存后重试一次..." rm -rf "\$HOME/.gradle/wrapper/dists" run_build_with_heartbeat fi """ echo " ✅ ${currentApp} Android APK 构建完成" } } } } } post { always { script { echo "🧹 处理构建产物" // 仅归档 APK 产物 def findCount = sh( script: "find apps/*/build/app/outputs/flutter-apk -name 'app-release.apk' 2>/dev/null | wc -l | tr -d ' '", returnStdout: true ).trim() if (findCount && findCount != '0') { echo " - 归档Android Release APK..." archiveArtifacts artifacts: 'apps/*/build/app/outputs/flutter-apk/app-release.apk', fingerprint: true echo " ✅ 归档完成 (找到 ${findCount} 个 APK 文件)" } else { echo " ⚠️ 未找到任何构建的 APK" } // 归档FVM配置便于追踪环境记录 if (fileExists('.fvmrc')) { archiveArtifacts artifacts: '.fvmrc', fingerprint: true, allowEmptyArchive: true } } } success { script { echo "🎉 构建流程成功完成!" echo "✅ 详情:" echo " - 目标: ${params.APP_NAME}" echo " - 环境: ${params.BUILD_ENVIRONMENT}" if (params.VERSION_NAME) echo " - 版本名: ${params.VERSION_NAME}" if (params.BUILD_NUMBER) echo " - 构建号: ${params.BUILD_NUMBER}" } } failure { echo "❌ 构建流程遇到错误并终止。请检查上方日志找出失败原由。" } } }