2026-05-05 16:06:32 +08:00
|
|
|
|
pipeline {
|
|
|
|
|
|
agent any
|
|
|
|
|
|
|
2026-05-08 18:47:02 +08:00
|
|
|
|
parameters {
|
2026-05-23 01:20:57 +08:00
|
|
|
|
// ── 版本升级策略 ─────────────────────────────────────────────────────
|
2026-05-23 01:25:10 +08:00
|
|
|
|
choice(name: 'VERSION_BUMP', choices: ['patch', 'minor', 'major'],
|
2026-05-23 01:20:57 +08:00
|
|
|
|
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 自定义版本号')
|
|
|
|
|
|
|
2026-05-23 01:28:41 +08:00
|
|
|
|
// 要构建并发布的模块(勾选即发布)
|
|
|
|
|
|
booleanParam(name: 'MOD_CORE', defaultValue: true, description: '发布 sdk-core')
|
|
|
|
|
|
booleanParam(name: 'MOD_IM', defaultValue: true, description: '发布 sdk-im')
|
|
|
|
|
|
booleanParam(name: 'MOD_PUSH', defaultValue: true, description: '发布 sdk-push')
|
|
|
|
|
|
booleanParam(name: 'MOD_UPDATE', defaultValue: true, description: '发布 sdk-update')
|
|
|
|
|
|
booleanParam(name: 'MOD_WEBVIEW', defaultValue: true, description: '发布 sdk-webview')
|
|
|
|
|
|
booleanParam(name: 'MOD_LICENSE', defaultValue: true, description: '发布 sdk-license')
|
2026-05-23 01:20:57 +08:00
|
|
|
|
|
|
|
|
|
|
booleanParam(name: 'RUN_TESTS', defaultValue: true, description: '是否运行自动化测试')
|
|
|
|
|
|
booleanParam(name: 'PUBLISH', defaultValue: true, description: '是否发布到 Nexus')
|
2026-05-08 18:47:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 16:06:32 +08:00
|
|
|
|
environment {
|
2026-05-23 01:20:57 +08:00
|
|
|
|
NEXUS_USER = credentials('nexus-android-user')
|
|
|
|
|
|
NEXUS_PASS = credentials('nexus-android-pass')
|
2026-05-05 16:06:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stages {
|
2026-05-08 18:47:02 +08:00
|
|
|
|
stage('Checkout') {
|
|
|
|
|
|
steps {
|
2026-05-16 12:48:17 +08:00
|
|
|
|
checkout([
|
2026-05-23 01:20:57 +08:00
|
|
|
|
$class: 'GitSCM',
|
|
|
|
|
|
branches: [[name: 'main']],
|
|
|
|
|
|
extensions: [[$class: 'CleanBeforeCheckout']],
|
|
|
|
|
|
userRemoteConfigs: scm.userRemoteConfigs
|
2026-05-16 12:48:17 +08:00
|
|
|
|
])
|
2026-05-08 18:47:02 +08:00
|
|
|
|
script {
|
2026-05-23 01:20:57 +08:00
|
|
|
|
// 构建 -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(' ')
|
|
|
|
|
|
|
2026-05-23 01:28:41 +08:00
|
|
|
|
// 根据复选框收集要发布的模块
|
|
|
|
|
|
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 }
|
2026-05-23 01:20:57 +08:00
|
|
|
|
if (resolved.isEmpty()) {
|
2026-05-23 01:28:41 +08:00
|
|
|
|
error("没有选择任何模块,请至少勾选一个")
|
2026-05-23 01:20:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保存为逗号分隔的环境变量供后续阶段使用
|
|
|
|
|
|
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}"
|
2026-05-08 18:47:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stage('Build AARs & APKs') {
|
2026-05-05 16:06:32 +08:00
|
|
|
|
steps {
|
2026-05-23 01:20:57 +08:00
|
|
|
|
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"
|
|
|
|
|
|
}
|
2026-05-05 16:06:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stage('Start Emulators') {
|
2026-05-08 18:47:02 +08:00
|
|
|
|
when { expression { return params.RUN_TESTS } }
|
2026-05-05 16:06:32 +08:00
|
|
|
|
steps {
|
2026-05-08 18:47:02 +08:00
|
|
|
|
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
|
|
|
|
|
|
'''
|
2026-05-05 16:06:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stage('Install APKs') {
|
2026-05-08 18:47:02 +08:00
|
|
|
|
when { expression { return params.RUN_TESTS } }
|
2026-05-05 16:06:32 +08:00
|
|
|
|
steps {
|
2026-05-08 18:47:02 +08:00
|
|
|
|
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%"
|
|
|
|
|
|
)
|
|
|
|
|
|
'''
|
2026-05-05 16:06:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stage('Single-Device Tests') {
|
2026-05-08 18:47:02 +08:00
|
|
|
|
when { expression { return params.RUN_TESTS } }
|
2026-05-05 16:06:32 +08:00
|
|
|
|
steps {
|
2026-05-08 18:47:02 +08:00
|
|
|
|
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
|
|
|
|
|
|
'''
|
2026-05-05 16:06:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stage('Cross-Device Tests') {
|
2026-05-08 18:47:02 +08:00
|
|
|
|
when { expression { return params.RUN_TESTS } }
|
2026-05-05 16:06:32 +08:00
|
|
|
|
parallel {
|
|
|
|
|
|
stage('Sender (5554)') {
|
|
|
|
|
|
steps {
|
2026-05-08 18:47:02 +08:00
|
|
|
|
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
|
|
|
|
|
|
'''
|
2026-05-05 16:06:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
stage('Receiver (5556)') {
|
|
|
|
|
|
steps {
|
2026-05-08 18:47:02 +08:00
|
|
|
|
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
|
|
|
|
|
|
'''
|
2026-05-05 16:06:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stage('Publish to Nexus') {
|
2026-05-23 01:20:57 +08:00
|
|
|
|
when { expression { return params.PUBLISH } }
|
2026-05-05 16:06:32 +08:00
|
|
|
|
steps {
|
2026-05-08 18:47:02 +08:00
|
|
|
|
script {
|
2026-05-23 01:20:57 +08:00
|
|
|
|
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"
|
2026-05-08 18:47:02 +08:00
|
|
|
|
}
|
2026-05-05 16:06:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
post {
|
|
|
|
|
|
always {
|
2026-05-08 18:47:02 +08:00
|
|
|
|
bat 'adb emu kill || exit 0'
|
2026-05-05 16:06:32 +08:00
|
|
|
|
archiveArtifacts artifacts: 'test-results-*.txt, cross-*.txt', allowEmptyArchive: true
|
|
|
|
|
|
}
|
|
|
|
|
|
failure {
|
2026-05-08 18:47:02 +08:00
|
|
|
|
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
|
|
|
|
|
|
'''
|
2026-05-05 16:06:32 +08:00
|
|
|
|
archiveArtifacts artifacts: 'logcat-*.txt', allowEmptyArchive: true
|
|
|
|
|
|
}
|
2026-05-23 01:20:57 +08:00
|
|
|
|
success { echo "Android SDK 构建成功" }
|
2026-05-05 16:06:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|