pipeline { agent any // jenkins.xuqinmin.com (Windows) parameters { booleanParam(name: 'SNAPSHOT', defaultValue: true, description: '勾选=SNAPSHOT(推送 main,不升版本,不打 tag);不勾选=正式 Release(升版本、打 tag、创建 Gitea Release)') choice(name: 'VERSION_BUMP', choices: ['patch', 'minor', 'major'], description: '版本升级策略(Release 时有效): patch 1.0.0→1.0.1 / minor 1.0.0→1.1.0 / major 1.0.0→2.0.0') booleanParam(name: 'MOD_CORE', defaultValue: false, description: '本次包含 XuqmCoreSDK 改动') booleanParam(name: 'MOD_IM', defaultValue: false, description: '本次包含 XuqmImSDK 改动') booleanParam(name: 'MOD_PUSH', defaultValue: false, description: '本次包含 XuqmPushSDK 改动') booleanParam(name: 'MOD_UPDATE', defaultValue: false, description: '本次包含 XuqmUpdateSDK 改动') booleanParam(name: 'MOD_FILE', defaultValue: false, description: '本次包含 XuqmFileSDK 改动') booleanParam(name: 'MOD_WEBVIEW', defaultValue: false, description: '本次包含 XuqmWebViewSDK 改动') booleanParam(name: 'MOD_BUGCOLLECT', defaultValue: false, description: '本次包含 XuqmBugCollectSDK 改动') } environment { GITEA_REPO_URL = 'https://xuqinmin.com/xuqmGroup/XuqmGroup-iOSSDK.git' GITEA_REPO_PATH = 'xuqmGroup/XuqmGroup-iOSSDK' GITEA_API_BASE = 'https://xuqinmin.com/api/v1' } options { disableConcurrentBuilds() buildDiscarder(logRotator(numToKeepStr: '20')) } stages { // ───────────────────────────────────────────────────────────────────── stage('Checkout') { steps { checkout scm bat 'git config user.email "jenkins@xuqm.com"' bat 'git config user.name "Jenkins CI"' script { def moduleChecks = [ 'XuqmCoreSDK': params.MOD_CORE, 'XuqmImSDK': params.MOD_IM, 'XuqmPushSDK': params.MOD_PUSH, 'XuqmUpdateSDK': params.MOD_UPDATE, 'XuqmFileSDK': params.MOD_FILE, 'XuqmWebViewSDK': params.MOD_WEBVIEW, 'XuqmBugCollectSDK': params.MOD_BUGCOLLECT, ] def changed = moduleChecks.findAll { k, v -> v }.collect { k, v -> k } if (changed.isEmpty()) { error('没有勾选任何模块,请至少标记一个有改动的模块') } env.CHANGED_MODULES = changed.join(', ') echo "改动模块: ${env.CHANGED_MODULES}" } } } // ───────────────────────────────────────────────────────────────────── stage('Resolve Version') { steps { script { def propsText = readFile('version.properties') def match = (propsText =~ /SDK_VERSION=(\d+)\.(\d+)\.(\d+)/) if (!match) error('version.properties 中找不到 SDK_VERSION=x.y.z') def major = match[0][1] as int def minor = match[0][2] as int def patch = match[0][3] as int if (params.SNAPSHOT) { env.PUBLISH_VERSION = "${major}.${minor}.${patch}-SNAPSHOT" env.RELEASE_TAG = '' echo "SNAPSHOT 模式 — 标记版本: ${env.PUBLISH_VERSION}" } else { switch (params.VERSION_BUMP) { case 'major': major++; minor = 0; patch = 0; break case 'minor': minor++; patch = 0; break default: patch++; break } env.RELEASE_TAG = "${major}.${minor}.${patch}" env.PUBLISH_VERSION = env.RELEASE_TAG def newProps = propsText.replaceAll(/SDK_VERSION=\d+\.\d+\.\d+/, "SDK_VERSION=${env.RELEASE_TAG}") writeFile file: 'version.properties', text: newProps echo "Release 模式 — 版本升级至 ${env.RELEASE_TAG}" } currentBuild.description = "${params.SNAPSHOT ? 'SNAPSHOT' : 'Release'} ${env.PUBLISH_VERSION} [${env.CHANGED_MODULES}]" } } } // ───────────────────────────────────────────────────────────────────── // SNAPSHOT:只推代码到 main,不改版本文件,不打 tag stage('SNAPSHOT: Push main') { when { expression { params.SNAPSHOT } } steps { script { withCredentials([sshUserPrivateKey(credentialsId: 'jenkins-ssh-key', keyFileVariable: 'GIT_SSH_KEY')]) { writeFile file: 'git-ssh.cmd', text: '@echo off\r\nssh -i "%GIT_SSH_KEY%" -o IdentitiesOnly=yes %*\r\n', encoding: 'UTF-8' try { withEnv(["GIT_SSH=${pwd()}\\git-ssh.cmd"]) { bat "git push origin HEAD:main" } } finally { bat 'if exist git-ssh.cmd del /f /q git-ssh.cmd' } } } echo "SNAPSHOT 已推送到 main 分支" echo "应用端 project.yml 保持 branch: main 即可自动获取最新代码" } } // ───────────────────────────────────────────────────────────────────── // Release:commit version.properties → 打 tag → 推送 commit + tag stage('Release: Commit & Tag') { when { expression { !params.SNAPSHOT } } steps { bat "git add version.properties" bat """git commit -m "ci: release %RELEASE_TAG% [%CHANGED_MODULES%]" """ bat "git tag %RELEASE_TAG%" script { withCredentials([sshUserPrivateKey(credentialsId: 'jenkins-ssh-key', keyFileVariable: 'GIT_SSH_KEY')]) { writeFile file: 'git-ssh.cmd', text: '@echo off\r\nssh -i "%GIT_SSH_KEY%" -o IdentitiesOnly=yes %*\r\n', encoding: 'UTF-8' try { withEnv(["GIT_SSH=${pwd()}\\git-ssh.cmd"]) { bat "git push origin HEAD:main" bat "git push origin %RELEASE_TAG%" } } finally { bat 'if exist git-ssh.cmd del /f /q git-ssh.cmd' } } } } } // ───────────────────────────────────────────────────────────────────── // Release:在 Gitea 创建 Release 记录 stage('Release: Gitea Release') { when { expression { !params.SNAPSHOT } } steps { script { def moduleList = env.CHANGED_MODULES.split(', ').collect { "- ${it}" }.join('\\n') def bodyEscaped = "## 改动模块\\n${moduleList}\\n\\n## 集成方式\\n```yaml\\npackages:\\n XuqmGroupSDK:\\n url: ${env.GITEA_REPO_URL}\\n from: \\\"${env.RELEASE_TAG}\\\"\\n```" def json = """{"tag_name":"${env.RELEASE_TAG}","name":"${env.RELEASE_TAG}","body":"${bodyEscaped}","draft":false,"prerelease":false}""" writeFile file: 'release_payload.json', text: json withCredentials([usernamePassword( credentialsId: 'gitea-access-token', usernameVariable: 'GITEA_USER', passwordVariable: 'TOKEN' )]) { bat """curl -sf -X POST "${env.GITEA_API_BASE}/repos/${env.GITEA_REPO_PATH}/releases" -H "Authorization: token %TOKEN%" -H "Content-Type: application/json" -d @release_payload.json""" } bat 'del release_payload.json' echo "Gitea Release ${env.RELEASE_TAG} 创建完成" } } } } post { success { script { if (params.SNAPSHOT) { echo "SNAPSHOT ${env.PUBLISH_VERSION} 推送成功 — 改动模块: ${env.CHANGED_MODULES}" } else { echo "Release ${env.RELEASE_TAG} 发布成功 — 改动模块: ${env.CHANGED_MODULES}" echo "应用端 project.yml 更新: from: \"${env.RELEASE_TAG}\"" } } } failure { echo "发布失败,请检查日志" } } }