f947807fba
Project CI / AI game creator shell Rust crates (push) Successful in 2m58s
Project CI / AI game creator shell Rust smoke (push) Successful in 3m56s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 7m17s
Project CI / Backend tests (push) Successful in 6m48s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 9m58s
Project CI / Native shell tests (push) Successful in 8m17s
Project CI / Frontend tests (push) Successful in 5m58s
Project CI / AI game creator shell web tests (push) Successful in 3m35s
Project CI / Repository checks (push) Successful in 4m1s
- 发号 Job 的 copyArtifactPermission 补齐手动发布消费者,用户触发的构建不再报 Unable to find project for artifact copy - 同步总版本号技术方案与共享记忆排障记录,写明 SYSTEM 定时构建短路、用户触发按认证用户判权的差异与回写 Job property 的步骤
168 lines
8.2 KiB
Plaintext
168 lines
8.2 KiB
Plaintext
// 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」(用户触发的构建按「认证用户」判权,SYSTEM 定时构建会短路放行,
|
||
// 所以定时调度能过、手动发布必挂;2026-09-20 #103 与 2026-09-21 手动发布
|
||
// #6/#7 均命中)。这里必须同时列出两条消费者:Genarrative-Scheduled-Revision-Trigger
|
||
// 与 Genarrative-Manual-Build-And-Deploy。改完本文件后必须先单独跑一次本 Job,
|
||
// 让 Declarative Pipeline 把 Job property 写回 Jenkins,再重跑调度或手动发布。
|
||
copyArtifactPermission('Genarrative-Scheduled-Revision-Trigger,Genarrative-Manual-Build-And-Deploy')
|
||
}
|
||
|
||
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
|
||
}
|
||
}
|
||
}
|