Files
Genarrative/jenkins/Jenkinsfile.agc-global-version-issue
T
kdletters fdc14404b2
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
新增 AGC release 每日调度
新增 Genarrative-Scheduled-Release-Trigger,每天 04:00 发布 Windows/macOS release 渠道

release 调度复用统一发号源,并为 macOS 启用过期构建让位

现有小时 dev 调度保持同时触发 Windows 与 macOS

更新生产运维门禁、发号权限、技术方案与共享记忆
2026-09-22 16:32:11 +08:00

169 lines
8.3 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」(用户触发的构建按「认证用户」判权,SYSTEM 定时构建会短路放行,
// 所以定时调度能过、手动发布必挂;2026-09-20 #103 与 2026-09-21 手动发布
// #6/#7 均命中)。这里必须列出全部消费者:Genarrative-Scheduled-Revision-Trigger、
// Genarrative-Scheduled-Release-Trigger 与 Genarrative-Manual-Build-And-Deploy。
// 改完本文件后必须先单独跑一次本 Job,让 Declarative Pipeline 把 Job property
// 写回 Jenkins,再重跑调度或手动发布。
copyArtifactPermission('Genarrative-Scheduled-Revision-Trigger,Genarrative-Scheduled-Release-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', 'release-unified', 'release-win', 'release-mac'], description: '发号用途标签:unified / release-unified 为一个号同时给两个平台,其余为单渠道热修')
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
}
}
}