pipeline { agent any environment { GITEA_TOKEN = credentials('gitea') JAVA_HOME = tool 'java17' GRADLE_USER_HOME = "${WORKSPACE}/.gradle" } options { timestamps() disableConcurrentBuilds() } stages { /* --------------------------------------------------- * 1. Checkout PR * --------------------------------------------------- */ stage('Checkout PR') { steps { script { checkout([ $class: 'GitSCM', branches: [[name: env.SOURCE_BRANCH]], userRemoteConfigs: [[ url: "https://git.sino-assist.com/${REPO_OWNER}/${REPO_NAME}.git", credentialsId: 'gitlab' ]] ]) } } } /* --------------------------------------------------- * 2. 计算变更文件 * --------------------------------------------------- */ stage('计算增量变更') { steps { script { sh """ git fetch origin ${TARGET_BRANCH} git diff --name-only origin/${TARGET_BRANCH}...HEAD > changed_files.txt """ def diff = readFile('changed_files.txt').trim() if (!diff) { echo "✅ 无代码变更,跳过扫描" currentBuild.result = 'SUCCESS' return } env.CHANGED_FILES = diff echo "变更文件:\\n${env.CHANGED_FILES}" } } } /* --------------------------------------------------- * 3. 解析 Gradle 增量模块 * --------------------------------------------------- */ stage('Gradle 增量编译') { tools { jdk "java17" gradle 'gradle' } steps { script { def modules = sh( script: ''' awk -F/ ' { if (NF >= 2) { module=":"$1 for (i=2; i<=NF-1; i++) { if ($i == "src") break module=module":"$i } print module } }' changed_files.txt | sort -u ''', returnStdout: true ).trim() if (!modules) { modules = ":classes" } echo "✅ 受影响模块:\\n${modules}" def tasks = modules .split("\\n") .collect { "${it}:classes" } .join(" ") sh """ gradle ${tasks} \ -x test \ --parallel \ --build-cache \ --configure-on-demand """ } } } /* --------------------------------------------------- * 4. SonarQube 社区版【增量扫描】 * --------------------------------------------------- */ stage('SonarQube 增量扫描(社区版)') { tools { jdk "java17" gradle 'gradle' } steps { script { // 仅扫描 src/main/java 下的变更 Java 文件 def sonarIncludes = sh( script: ''' grep -E "src/main/java/.*\\.java$" changed_files.txt \ | sed 's#^#./#' \ | tr '\\n' ',' ''', returnStdout: true ).trim() if (!sonarIncludes) { echo "✅ 无 Java 代码变更,跳过 Sonar" return } echo "Sonar 增量扫描文件:\\n${sonarIncludes}" withSonarQubeEnv('sonar-server') { sh """ gradle sonar \ -Dsonar.projectKey=${REPO_NAME} \ -Dsonar.inclusions=${sonarIncludes} \ -Dsonar.sourceEncoding=UTF-8 \ -Dsonar.gradle.skipCompile=true \ -Dsonar.exclusions=**/* """ } } } } /* --------------------------------------------------- * 5. 质量门禁 + PR 反馈 * --------------------------------------------------- */ stage('反馈审查结果') { steps { script { def report = waitForQualityGate() if (report.status != 'OK') { sh """ curl -X POST \ "https://git.sino-assist.com/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/issues/${PR_ID}/comments" \ -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "body": "❌ SonarQube 社区版增量扫描未通过\\n\\n📊 报告:${report.dashboardUrl}" }' """ error "❌ 质量门禁失败" } else { sh """ curl -X POST \ "https://git.sino-assist.com/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/statuses/${GIT_COMMIT}" \ -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "context": "sonarqube-check", "state": "success", "description": "✅ 社区版增量扫描通过" }' """ } } } } } }