XuqmGroup-AndroidSDK/Jenkinsfile
XuqmGroup 998e5384ff chore(ci): 更新 Jenkins 配置以支持多模块版本管理
- 为 Android、Flutter、iOS 和 RN SDK 的 Jenkinsfile 添加模块化版本控制
- 引入版本升级策略选择(major/minor/patch)和自定义版本号功能
- 实现多模块独立版本管理和选择性构建发布
- 更新 iOS SDK Package.swift 以支持独立模块化库
- 修改 iOS SDK podspec 文件以适应新的标签命名约定
- 优化 Jenkins 构建流程以支持按需选择特定模块进行构建和发布
- 修复 iOS 测试中的可选类型转换问题以提高代码健壮性
2026-05-23 01:20:57 +08:00

232 行
11 KiB
Groovy

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

pipeline {
agent any
parameters {
// ── 版本升级策略 ─────────────────────────────────────────────────────
choice(name: 'VERSION_BUMP', defaultValue: 'patch',
description: '版本升级策略: major(1.0.0→2.0.0), minor(1.0.0→1.1.0), patch(1.0.0→1.0.1)')
booleanParam(name: 'CUSTOM_VERSION', defaultValue: false,
description: '勾选后使用下方自定义版本号忽略VERSION_BUMP')
// 模块自定义版本号仅CUSTOM_VERSION=true时生效
string(name: 'CORE_VERSION', defaultValue: '', description: 'sdk-core 自定义版本号仅CUSTOM_VERSION=true时生效')
string(name: 'IM_VERSION', defaultValue: '', description: 'sdk-im 自定义版本号')
string(name: 'PUSH_VERSION', defaultValue: '', description: 'sdk-push 自定义版本号')
string(name: 'UPDATE_VERSION', defaultValue: '', description: 'sdk-update 自定义版本号')
string(name: 'WEBVIEW_VERSION', defaultValue: '', description: 'sdk-webview 自定义版本号')
string(name: 'LICENSE_VERSION', defaultValue: '', description: 'sdk-license 自定义版本号')
// 要构建并发布的模块列表,每行一个(空 = 全部)
string(
name: 'MODULES',
defaultValue: 'sdk-core\nsdk-im\nsdk-push\nsdk-update\nsdk-webview\nsdk-license',
description: '要构建并发布的模块列表,每行一个(空 = 全部)'
)
booleanParam(name: 'RUN_TESTS', defaultValue: true, description: '是否运行自动化测试')
booleanParam(name: 'PUBLISH', defaultValue: true, description: '是否发布到 Nexus')
}
environment {
NEXUS_USER = credentials('nexus-android-user')
NEXUS_PASS = credentials('nexus-android-pass')
}
stages {
stage('Checkout') {
steps {
checkout([
$class: 'GitSCM',
branches: [[name: 'main']],
extensions: [[$class: 'CleanBeforeCheckout']],
userRemoteConfigs: scm.userRemoteConfigs
])
script {
// 构建 -P 参数字符串
def versionMap = [
'SDK_CORE_VERSION' : params.CORE_VERSION?.trim(),
'SDK_IM_VERSION' : params.IM_VERSION?.trim(),
'SDK_PUSH_VERSION' : params.PUSH_VERSION?.trim(),
'SDK_UPDATE_VERSION' : params.UPDATE_VERSION?.trim(),
'SDK_WEBVIEW_VERSION' : params.WEBVIEW_VERSION?.trim(),
'SDK_LICENSE_VERSION' : params.LICENSE_VERSION?.trim(),
]
env.VERSION_ARGS = versionMap
.findAll { k, v -> v }
.collect { k, v -> "-P${k}=${v}" }
.join(' ')
// 解析要操作的模块列表
def allModules = ['sdk-core', 'sdk-im', 'sdk-push', 'sdk-update', 'sdk-webview', 'sdk-license']
def requestedRaw = params.MODULES?.trim()
def requested = requestedRaw
? requestedRaw.split(/[\r\n]+/).collect { it.trim() }.findAll { it }
: allModules
// 过滤出已知模块名
def resolved = requested.findAll { allModules.contains(it) }
if (resolved.isEmpty()) {
error("MODULES param produced an empty list after filtering. Check spelling. Provided: ${requestedRaw}")
}
// 保存为逗号分隔的环境变量供后续阶段使用
env.PUBLISH_MODULES = resolved.join(',')
echo "Version args : ${env.VERSION_ARGS ?: '(none — using gradle.properties values)'}"
echo "Modules : ${env.PUBLISH_MODULES}"
}
}
}
stage('Resolve Versions') {
steps {
script {
if (!params.CUSTOM_VERSION) {
// 从 gradle.properties 读取当前版本号
def currentVer = sh(
script: "grep '^PUBLISH_VERSION=' gradle.properties | cut -d= -f2",
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
sh "sed -i '' 's/^PUBLISH_VERSION=.*/PUBLISH_VERSION=${newVer}/' gradle.properties"
// 用新版本号重建所有模块的 VERSION_ARGS
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 "Updated 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_USER% -PNEXUS_PASSWORD=%NEXUS_PASS%"
bat "gradlew.bat ${publishTasks} ${env.VERSION_ARGS} ${credArgs} --no-daemon"
}
}
}
}
post {
always {
bat 'adb emu kill || exit 0'
archiveArtifacts artifacts: 'test-results-*.txt, cross-*.txt', allowEmptyArchive: true
}
failure {
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 构建成功" }
}
}