Files
Genarrative/jenkins/Jenkinsfile.agc-global-version-issue
T
suzmii d354e5e7c3
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 19s
Project CI / AI game creator shell Rust smoke (pull_request) Failing after 20s
Project CI / AI game creator shell Rust crates (pull_request) Failing after 20s
Project CI / Native shell tests (pull_request) Failing after 19s
Project CI / Backend tests (pull_request) Failing after 20s
Project CI / Frontend tests (pull_request) Failing after 9s
Project CI / Repository checks (pull_request) Failing after 13s
Project CI / AI game creator shell web tests (pull_request) Failing after 13s
修复发号Job未授权调度器读取归档导致整轮调度失败
发号Jenkinsfile显式copyArtifactPermission授权调度管线
生产门禁新增该授权规则避免回归
运维文档补充生产者到调度器的Copy Artifact映射与失败后果
2026-09-20 22:58:39 +08:00

166 lines
7.9 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// AGC 总版本号发号 Job:唯一的发号入口。
//
// 设计要点(见 docs/technical/【技术方案】AGC总版本号与发号-2026-09-20.md):
// - 发号顺序固定为「先写总号 → 再构建 → 再发渠道清单」,失败不回滚,只烧号;
// - 各渠道构建只接收号(AGC_RELEASE_VERSION),不自己加号;
// - 本 Job 用 disableConcurrentBuilds() 串行化发号(集群未安装 lockable-resources
// 插件,因此以 Job 级串行 + 写后回读校验兜底并发);
// - dry-run 只预览下一位,不写回、不烧号。
pipeline {
agent {
label 'linux && genarrative-build'
}
options {
disableConcurrentBuilds()
skipDefaultCheckout(true)
buildDiscarder(logRotator(numToKeepStr: '100', artifactNumToKeepStr: '20'))
// Copy Artifact 保持 Production 权限模式:生产者必须显式授权消费者。
// 调度管线要 copy 本 Job 的 agc-global-version.txt 才能把总号透传给渠道构建;
// 缺这条授权时 copyArtifacts 会报「Unable to find project for artifact copy」,
// 整轮调度失败,且失败点在发号之后 —— 号已烧、Windows 也没被触发
// (2026-09-20 首次统一发号 #103 即命中)。改完本文件后必须先单独跑一次本 Job,
// 让 Declarative Pipeline 把 Job property 写回 Jenkins,再重跑调度。
copyArtifactPermission('Genarrative-Scheduled-Revision-Trigger')
}
environment {
GIT_REMOTE_URL = 'ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git'
GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-ssh'
GLOBAL_VERSION_OUT = 'agc-global-version.txt'
OSSUTIL_TOOLS_DIR = '.jenkins-ossutil'
}
parameters {
choice(name: 'AGC_CHANNEL', choices: ['unified', 'dev-win', 'dev-mac'], description: '发号用途:unified 为统一构建(一个号同时给 dev-win 与 dev-mac),其余为单渠道热修')
string(name: 'SOURCE_BRANCH', defaultValue: 'master', description: '取发号脚本的源码分支;默认 master')
string(name: 'COMMIT_HASH', defaultValue: '', description: '可选:固定提交;留空则解析 SOURCE_BRANCH 当前 revision')
booleanParam(name: 'SEED_ONLY', defaultValue: false, description: '仅做一次性播种:写入渠道与仓库版本的最大值作为基线,不递增、不烧号')
booleanParam(name: 'AGC_RELEASE_DRY_RUN', defaultValue: false, description: '勾选后只预览下一个总号,不写回、不烧号')
string(name: 'OSSUTIL_BIN', defaultValue: 'ossutil', description: 'ossutil 命令名或绝对路径;缺省时本 Job 会下载到工作区私有目录')
}
stages {
stage('Checkout') {
steps {
deleteDir()
script {
def checkoutFromRemote = { String remoteUrl, String credentialsId ->
checkout([
$class: 'GitSCM',
branches: [[name: params.COMMIT_HASH?.trim() ?: "*/${params.SOURCE_BRANCH}"]],
doGenerateSubmoduleConfigurations: false,
extensions: [
[$class: 'CleanBeforeCheckout'],
[$class: 'CloneOption', shallow: true, depth: 1, noTags: true, timeout: 30, honorRefspec: true],
],
userRemoteConfigs: [[
url: remoteUrl,
credentialsId: credentialsId,
refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}",
]],
])
}
checkoutFromRemote(env.GIT_REMOTE_URL, env.GIT_REMOTE_CREDENTIAL_ID)
env.SOURCE_COMMIT = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
if (params.COMMIT_HASH?.trim() && env.SOURCE_COMMIT != params.COMMIT_HASH.trim()) {
// 浅取分支不再含目标提交时立即失败,避免拿到错版本发号。
def contains = sh(
script: "git merge-base --is-ancestor ${params.COMMIT_HASH.trim()} HEAD && echo yes || echo no",
returnStdout: true,
).trim()
if (contains != 'yes') {
error("COMMIT_HASH ${params.COMMIT_HASH.trim()} 不属于 ${params.SOURCE_BRANCH}")
}
sh "git checkout --detach ${params.COMMIT_HASH.trim()}"
env.SOURCE_COMMIT = params.COMMIT_HASH.trim()
}
}
}
}
stage('Ensure ossutil') {
steps {
script {
def requested = params.OSSUTIL_BIN?.trim() ?: 'ossutil'
def resolved = sh(
script: "command -v ${requested} 2>/dev/null || true",
returnStdout: true,
).trim()
if (resolved) {
env.EFFECTIVE_OSSUTIL_BIN = resolved
} else {
// 构建节点没有 ossutil 时下载到工作区私有目录,不改主机环境。
sh '''#!/usr/bin/env bash
set -euo pipefail
version="${AGC_OSSUTIL_VERSION:-2.1.2}"
mkdir -p "${OSSUTIL_TOOLS_DIR}"
if [[ ! -x "${OSSUTIL_TOOLS_DIR}/ossutil" ]]; then
curl -fsSL -o "${OSSUTIL_TOOLS_DIR}/ossutil.zip" \\
"https://gosspublic.alicdn.com/ossutil/v2/${version}/ossutil-${version}-linux-amd64.zip"
# 官方 zip 解压后是带版本号的目录,二进制不在包根,必须先定位再落到固定路径。
unzip -o -q "${OSSUTIL_TOOLS_DIR}/ossutil.zip" -d "${OSSUTIL_TOOLS_DIR}"
extracted="$(find "${OSSUTIL_TOOLS_DIR}" -maxdepth 3 -type f -name ossutil -print -quit)"
if [[ -z "${extracted}" ]]; then
echo "ossutil 解压后未找到可执行文件:${OSSUTIL_TOOLS_DIR}" >&2
exit 1
fi
cp -f "${extracted}" "${OSSUTIL_TOOLS_DIR}/ossutil"
chmod +x "${OSSUTIL_TOOLS_DIR}/ossutil"
fi
# ossutil v2 没有 `--version` 选项,这里用可执行性校验代替版本打印,避免误判失败。
if [[ ! -x "${OSSUTIL_TOOLS_DIR}/ossutil" ]]; then
echo "ossutil 未就绪:${OSSUTIL_TOOLS_DIR}/ossutil" >&2
exit 1
fi
echo "ossutil ready: ${OSSUTIL_TOOLS_DIR}/ossutil"
'''
env.EFFECTIVE_OSSUTIL_BIN = "${env.WORKSPACE}/${env.OSSUTIL_TOOLS_DIR}/ossutil"
}
}
}
}
stage('Issue Global Version') {
steps {
withCredentials([
string(credentialsId: 'AliyunAccessKeyId', variable: 'AGC_OSS_ACCESS_KEY_ID'),
string(credentialsId: 'AliyunaccessKeySecret', variable: 'AGC_OSS_ACCESS_KEY_SECRET'),
]) {
withEnv([
"OSSUTIL_BIN=${env.EFFECTIVE_OSSUTIL_BIN}",
"AGC_RELEASE_DRY_RUN=${params.AGC_RELEASE_DRY_RUN ? '1' : '0'}",
]) {
sh '''#!/usr/bin/env bash
set -euo pipefail
seed_args=()
if [[ "${SEED_ONLY}" == "true" ]]; then seed_args+=(--seed-only); fi
dry_args=()
if [[ "${AGC_RELEASE_DRY_RUN}" == "1" ]]; then dry_args+=(--dry-run); fi
node apps/ai-game-creator-shell/scripts/issue-global-version.mjs \\
--channel "${AGC_CHANNEL}" \\
--commit "${SOURCE_COMMIT}" \\
--build-id "${BUILD_NUMBER}" \\
--out "${GLOBAL_VERSION_OUT}" \\
"${seed_args[@]}" "${dry_args[@]}"
'''
script {
env.AGC_GLOBAL_VERSION = readFile(env.GLOBAL_VERSION_OUT).trim()
currentBuild.description =
"AGC 总版本号 ${env.AGC_GLOBAL_VERSION}(channel=${params.AGC_CHANNEL}" +
"${params.SEED_ONLY ? ',播种' : ''}${params.AGC_RELEASE_DRY_RUN ? ',dry-run' : ''})"
echo currentBuild.description
}
}
}
}
}
}
post {
always {
archiveArtifacts artifacts: "agc-global-version.txt", allowEmptyArchive: true, fingerprint: true
}
}
}