pipeline { agent any parameters { // ── 版本升级策略 ───────────────────────────────────────────────────── choice(name: 'VERSION_BUMP', choices: ['patch', 'minor', 'major'], description: '版本升级策略: major(1.0.0→2.0.0), minor(1.0.0→1.1.0), patch(1.0.0→1.0.1)') // 要构建并发布的模块(勾选即发布) booleanParam(name: 'MOD_CORE', defaultValue: false, description: '发布 sdk-core') booleanParam(name: 'MOD_IM', defaultValue: false, description: '发布 sdk-im') booleanParam(name: 'MOD_PUSH', defaultValue: false, description: '发布 sdk-push') booleanParam(name: 'MOD_UPDATE', defaultValue: false, description: '发布 sdk-update') booleanParam(name: 'MOD_WEBVIEW', defaultValue: false, description: '发布 sdk-webview') booleanParam(name: 'MOD_LICENSE', defaultValue: false, description: '发布 sdk-license') booleanParam(name: 'RUN_TESTS', defaultValue: true, description: '是否运行自动化测试') booleanParam(name: 'PUBLISH', defaultValue: true, description: '是否发布到 Nexus') } environment { NEXUS_CREDS = credentials('NEXUS_CREDS') } stages { stage('Checkout') { steps { checkout([ $class: 'GitSCM', branches: [[name: 'main']], extensions: [[$class: 'CleanBeforeCheckout']], userRemoteConfigs: scm.userRemoteConfigs ]) script { // 根据复选框收集要发布的模块 def moduleChecks = [ 'sdk-core': params.MOD_CORE, 'sdk-im': params.MOD_IM, 'sdk-push': params.MOD_PUSH, 'sdk-update': params.MOD_UPDATE, 'sdk-webview': params.MOD_WEBVIEW, 'sdk-license': params.MOD_LICENSE, ] def resolved = moduleChecks.findAll { k, v -> v }.collect { k, v -> k } if (resolved.isEmpty()) { error("没有选择任何模块,请至少勾选一个") } env.PUBLISH_MODULES = resolved.join(',') echo "Modules: ${env.PUBLISH_MODULES}" } } } stage('Resolve Versions') { steps { script { // 从 gradle.properties 读取当前版本号 def currentVer = bat( script: "@powershell -Command \"(Get-Content gradle.properties | Select-String '^PUBLISH_VERSION=').Line.Split('=')[1]\"", returnStdout: true ).trim() echo "Current PUBLISH_VERSION: ${currentVer}" def parts = currentVer.tokenize('.') while (parts.size() < 3) { parts.add('0') } switch (params.VERSION_BUMP) { case 'major': parts[0] = (parts[0].toInteger() + 1).toString() parts[1] = '0' parts[2] = '0' break case 'minor': parts[1] = (parts[1].toInteger() + 1).toString() parts[2] = '0' break case 'patch': default: parts[2] = (parts[2].toInteger() + 1).toString() break } def newVer = parts.join('.') echo "Auto-bumped PUBLISH_VERSION: ${currentVer} → ${newVer}" // 更新 gradle.properties bat "powershell -Command \"(Get-Content gradle.properties) -replace '^PUBLISH_VERSION=.*', 'PUBLISH_VERSION=${newVer}' | Set-Content gradle.properties\"" // 用新版本号设置所有模块的版本参数 def modules = env.PUBLISH_MODULES.split(',').toList() def versionMap = [:] for (mod in modules) { def propName = "SDK_${mod.replace('sdk-', '').toUpperCase()}_VERSION" versionMap[propName] = newVer } env.VERSION_ARGS = versionMap .collect { k, v -> "-P${k}=${v}" } .join(' ') echo "VERSION_ARGS: ${env.VERSION_ARGS}" } } } stage('Build AARs & APKs') { steps { script { // 构建 sample-app(测试用)和各选中模块的 release def modules = env.PUBLISH_MODULES.split(',').toList() def moduleTasks = modules.collect { ":${it}:assembleRelease" }.join(' ') def testTasks = ':sample-app:assembleDebug :sample-app:assembleDebugAndroidTest' bat "gradlew.bat ${moduleTasks} ${testTasks} ${env.VERSION_ARGS} --no-daemon" } } } stage('Start Emulators') { when { expression { return params.RUN_TESTS } } steps { bat ''' start /B emulator -avd Pixel_9_Pro -no-audio -no-boot-anim -no-snapshot-save -no-window start /B emulator -avd Pixel_9_Pro_2 -no-audio -no-boot-anim -no-snapshot-save -no-window adb wait-for-device timeout /t 20 /nobreak >nul adb devices ''' } } stage('Install APKs') { when { expression { return params.RUN_TESTS } } steps { bat ''' set APK=sample-app\\build\\outputs\\apk\\debug\\sample-app-debug.apk set TEST_APK=sample-app\\build\\outputs\\apk\\androidTest\\debug\\sample-app-debug-androidTest.apk for %%D in (emulator-5554 emulator-5556) do ( adb -s %%D install -r "%APK%" adb -s %%D install -r "%TEST_APK%" ) ''' } } stage('Single-Device Tests') { when { expression { return params.RUN_TESTS } } steps { bat ''' set ALL_SINGLE=com.xuqm.sdk.sample.SdkIntegrationTest,com.xuqm.sdk.sample.PushSdkTest,com.xuqm.sdk.sample.NetworkResilienceTest adb -s emulator-5554 shell am instrument -w -r -e class "%ALL_SINGLE%" "com.xuqm.demo.test/androidx.test.runner.AndroidJUnitRunner" > test-results-5554.txt findstr /C:"FAILURES" test-results-5554.txt && exit 1 || exit 0 ''' } } stage('Cross-Device Tests') { when { expression { return params.RUN_TESTS } } parallel { stage('Sender (5554)') { steps { bat ''' adb -s emulator-5554 shell am instrument -w -r -e class "com.xuqm.sdk.sample.CrossDeviceSenderTest" "com.xuqm.demo.test/androidx.test.runner.AndroidJUnitRunner" > cross-sender.txt findstr /C:"FAILURES" cross-sender.txt && exit 1 || exit 0 ''' } } stage('Receiver (5556)') { steps { bat ''' adb -s emulator-5556 shell am instrument -w -r -e class "com.xuqm.sdk.sample.CrossDeviceReceiverTest" "com.xuqm.demo.test/androidx.test.runner.AndroidJUnitRunner" > cross-receiver.txt findstr /C:"FAILURES" cross-receiver.txt && exit 1 || exit 0 ''' } } } } stage('Publish to Nexus') { when { expression { return params.PUBLISH } } steps { script { def modules = env.PUBLISH_MODULES.split(',').toList() def publishTasks = modules.collect { ":${it}:publish" }.join(' ') def credArgs = "-PNEXUS_USER=%NEXUS_CREDS_USR% -PNEXUS_PASSWORD=%NEXUS_CREDS_PSW%" bat "gradlew.bat ${publishTasks} ${env.VERSION_ARGS} ${credArgs} --no-daemon" } } } } post { always { script { node { bat 'adb emu kill || exit 0' archiveArtifacts artifacts: 'test-results-*.txt, cross-*.txt', allowEmptyArchive: true } } } failure { script { node { bat ''' adb -s emulator-5554 logcat -d -s XuqmImSDK:D XuqmPushSDK:W XuqmUpdateSDK:D > logcat-5554.txt || exit 0 adb -s emulator-5556 logcat -d -s XuqmImSDK:D XuqmPushSDK:W XuqmUpdateSDK:D > logcat-5556.txt || exit 0 ''' archiveArtifacts artifacts: 'logcat-*.txt', allowEmptyArchive: true } } } success { echo "Android SDK 构建成功" } } }