把macOS渠道纳入定时发布并共用总版本号
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 17s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 19s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 19s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 19s
Project CI / AI game creator shell Rust smoke (pull_request) Failing after 16s
Project CI / Native shell tests (pull_request) Failing after 16s
Project CI / Backend tests (pull_request) Failing after 16s
Project CI / AI game creator shell Rust crates (pull_request) Failing after 16s
Project CI / Frontend tests (pull_request) Failing after 7s
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / AI game creator shell web tests (pull_request) Failing after 11s

调度器在发号后同时触发macOS渠道构建,透传同一个总版本号与固定revision
macOS Job改为默认真实发布,演练需显式勾选
新增SKIP_IF_SUPERSEDED:节点离线期间排队的旧构建在恢复后自行让位
补充生产门禁规则与回归断言
同步调度与运维文档口径
This commit is contained in:
2026-09-20 22:47:01 +08:00
parent 88090fcc6e
commit dbbbef8a0d
6 changed files with 75 additions and 7 deletions
@@ -90,10 +90,25 @@ test('CI pipeline is manual, publishes the macOS partition and never reuses a de
!pipeline.includes('genarrative-agc-macos-local'),
'Jenkinsfile 不得写死具体节点名',
);
// dry-run 必须是默认值:不显式取消勾选就不得写入 OSS
// 这条管线是正式发布入口(与 Windows 对称):默认真发布,演练需显式勾选
assert.match(
pipeline,
/booleanParam\(name: 'AGC_RELEASE_DRY_RUN', defaultValue: true/u,
/booleanParam\(name: 'AGC_RELEASE_DRY_RUN', defaultValue: false/u,
'Channel 发布默认必须是真发布,演练只能显式勾选',
);
// 节点是办公机:离线期间排队的旧构建必须自行让位,且跳过要覆盖后续全部阶段。
assert.match(
pipeline,
/booleanParam\(name: 'SKIP_IF_SUPERSEDED', defaultValue: false/u,
);
assert.ok(
pipeline.includes('.jenkins-superseded-by'),
'必须记录被推进的标记供后续阶段判定',
);
assert.equal(
(pipeline.match(/env\.AGC_BUILD_SUPERSEDED != 'true'/gu) ?? []).length,
3,
'Toolchain / Package / Archive 三个阶段都必须按跳过标记收口',
);
for (const forbidden of [
'triggers {',
@@ -90,7 +90,7 @@ SpacetimeDB 任务统一先读取 `.codex/skills/genarrative-spacetimedb/SKILL.m
## Jenkins 定时版本调度
定时与版本比较只保留在 `Genarrative-Scheduled-Revision-Trigger` 一处:每小时用 `git ls-remote` 解析 `SOURCE_BRANCH` 远端 HEAD,与上一次触发过的 revision 比较,变化时才把同一个 `COMMIT_HASH` 同时传给 `Genarrative-Full-Build-And-Deploy` `Genarrative-Agc-Windows-Build`,保证两条管线构建同一个版本`Genarrative-Full-Build-And-Deploy``Genarrative-Agc-Windows-Build` 不得自带 `triggers` / `cron`,也不得在管线内再做一套版本去重;`npm run check:production-ops` 会拦住这两类回退。调度状态与生效步骤见 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
定时与版本比较只保留在 `Genarrative-Scheduled-Revision-Trigger` 一处:每小时用 `git ls-remote` 解析 `SOURCE_BRANCH` 远端 HEAD,与上一次触发过的 revision 比较,变化时才把同一个 `COMMIT_HASH` 传给 `Genarrative-Full-Build-And-Deploy`,并先经 `Genarrative-Agc-Global-Version-Issue` 发号、再把同一个版本号透传给 `Genarrative-Agc-Windows-Build``Genarrative-Agc-MacOS-Build`,保证两个客户端的平台分区发布同一个版本。这三个下游 Job 都不得自带 `triggers` / `cron`,也不得在管线内再做一套版本去重;`npm run check:production-ops` 会拦住这两类回退。macOS 节点是日常办公机,调度触发它时置 `SKIP_IF_SUPERSEDED=true`:节点离线期间排队的旧构建在恢复后会自行让位,不发布过期版本。调度状态与生效步骤见 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## Gitea CI 依赖闭合
File diff suppressed because one or more lines are too long
@@ -12,7 +12,8 @@ pipeline {
string(name: 'COMMIT_HASH', defaultValue: '', description: '可选,固定属于源码分支的提交;不递增应用版本')
string(name: 'AGC_UPDATE_CHANNEL', defaultValue: 'dev', description: 'AGC 发布渠道(基础名,不含系统):dev、release 或自定义小写名称;写入的分区固定为 <channel>-mac')
string(name: 'AGC_RELEASE_VERSION', defaultValue: '', description: '可选三段版本号;留空则按 <channel>-mac 分区清单高水位递增 patch。首次发布建议显式指定,避免版本链回退')
booleanParam(name: 'AGC_RELEASE_DRY_RUN', defaultValue: true, description: '勾选后只构建、验签并打印将上传的对象,不写 OSS;取消勾选才真正发布到 <channel>-mac 分区')
booleanParam(name: 'AGC_RELEASE_DRY_RUN', defaultValue: false, description: '默认直接发布到 <channel>-mac 分区;勾选后只构建、验签并打印将上传的对象,不写 OSS(演练)')
booleanParam(name: 'SKIP_IF_SUPERSEDED', defaultValue: false, description: '置真时:本次 COMMIT_HASH 若已被源码分支推进,则直接跳过而不构建。调度器触发本 Job 时置真,避免节点离线期间排队的旧构建在恢复后发布过期版本')
string(name: 'AGC_UPDATE_RELEASE_NOTES', defaultValue: '', description: '可选单行更新摘要;留空则由发布脚本按提交自动汇总')
string(name: 'OSSUTIL_BIN', defaultValue: 'ossutil', description: 'ossutil 命令名或绝对路径(Mac 节点默认装在 ~/.local/bin/ossutil')
string(name: 'CARGO_BUILD_JOBS', defaultValue: '8', description: '并行 rustc 任务数,默认吃满节点 8 核(4P+4E)。该值同时作为 rustc codegen 的 jobserver 令牌上限;节点只有 24 GB 内存且是日常办公机,若构建期间出现明显换页可临时调低。只影响本次构建')
@@ -73,11 +74,31 @@ pipeline {
# 不清除 node_modules/target 缓存;被跟踪内容始终来自上述 commit。
git clean -fd
git rev-parse HEAD > .jenkins-source-commit
if [ "${SKIP_IF_SUPERSEDED}" = "true" ] && [ -n "$COMMIT_HASH" ]; then
latest="$(git rev-parse "refs/remotes/origin/$SOURCE_BRANCH")"
if [ "$latest" != "$(git rev-parse "$COMMIT_HASH^{commit}")" ]; then
# 只置标记,由后面的 when 条件统一收口;不在 shell 里 exit 0
# 否则会带着旧 commit 继续往下构建。
echo "[agc-macos] 本次 commit 已被 $SOURCE_BRANCH 推进:$COMMIT_HASH -> $latest,跳过发布"
printf '%s' "$latest" > .jenkins-superseded-by
fi
fi
'''
}
script {
if (fileExists('.jenkins-superseded-by')) {
env.AGC_BUILD_SUPERSEDED = 'true'
currentBuild.result = 'NOT_BUILT'
currentBuild.description = "${env.COMMIT_HASH} 已被 ${env.SOURCE_BRANCH} 推进到 ${readFile('.jenkins-superseded-by').trim().take(12)},跳过本轮发布"
echo currentBuild.description
}
}
}
}
stage('Toolchain and dependencies') {
when {
expression { return env.AGC_BUILD_SUPERSEDED != 'true' }
}
steps {
sh '''
set -eu
@@ -96,6 +117,9 @@ pipeline {
}
}
stage('Package, verify and publish') {
when {
expression { return env.AGC_BUILD_SUPERSEDED != 'true' }
}
steps {
withCredentials([
string(credentialsId: 'AliyunAccessKeyId', variable: 'AGC_OSS_ACCESS_KEY_ID'),
@@ -147,6 +171,9 @@ pipeline {
}
}
stage('Archive release') {
when {
expression { return env.AGC_BUILD_SUPERSEDED != 'true' }
}
steps {
archiveArtifacts artifacts: 'artifacts/*.dmg,artifacts/*.sha256,artifacts/latest.json,artifacts/*.sig,artifacts/release-notes.txt,artifacts/build-manifest.json,.jenkins-source-commit', fingerprint: true, allowEmptyArchive: false
}
+15 -1
View File
@@ -1,5 +1,5 @@
// 定时版本调度管线:解析远端分支版本,只有和上一次触发过的版本不同时,
// 才用同一个固定 commit 触发 Full Build 与 AGC Windows Build 两个下游管线
// 才用同一个固定 commit 触发 Full Build、AGC 发号与 AGC Windows/macOS 渠道构建
pipeline {
agent {
label 'linux && genarrative-build'
@@ -20,6 +20,7 @@ pipeline {
GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-ssh'
FULL_BUILD_JOB_NAME = 'Genarrative-Full-Build-And-Deploy'
AGC_BUILD_JOB_NAME = 'Genarrative-Agc-Windows-Build'
AGC_MACOS_BUILD_JOB_NAME = 'Genarrative-Agc-MacOS-Build'
AGC_GLOBAL_VERSION_JOB_NAME = 'Genarrative-Agc-Global-Version-Issue'
AGC_GLOBAL_VERSION_ARTIFACT = 'agc-global-version.txt'
REVISION_STATE_FILE = '.jenkins-last-triggered-revision'
@@ -177,6 +178,18 @@ pipeline {
string(name: 'AGC_RELEASE_VERSION', value: env.AGC_GLOBAL_VERSION),
]
build job: env.AGC_BUILD_JOB_NAME, wait: false, propagate: false, parameters: agcParameters
// macOS 与 Windows 共用发号 Job 下发的同一个总号:调度只透传号与固定 revision,
// 不允许 Mac 侧自行发号,否则同一轮两个平台会拿到不同的号。
def agcMacosParameters = [
string(name: 'SOURCE_BRANCH', value: env.SOURCE_BRANCH),
string(name: 'COMMIT_HASH', value: pinnedRevision),
string(name: 'AGC_UPDATE_CHANNEL', value: 'dev'),
string(name: 'AGC_RELEASE_VERSION', value: env.AGC_GLOBAL_VERSION),
// Mac 节点是办公机:离线期间排队的旧构建在节点回来后必须自行让位,
// 不允许发布一轮已经过期的版本。
booleanParam(name: 'SKIP_IF_SUPERSEDED', value: true),
]
build job: env.AGC_MACOS_BUILD_JOB_NAME, wait: false, propagate: false, parameters: agcMacosParameters
agcTriggered = true
} else {
echo "本轮提交不含 AGC 相关路径,跳过 ${env.AGC_BUILD_JOB_NAME};需要强制发布时勾选 FORCE_TRIGGER"
@@ -185,6 +198,7 @@ pipeline {
def triggered = []
if (fullTriggered) { triggered.add(env.FULL_BUILD_JOB_NAME) }
if (agcTriggered) { triggered.add(env.AGC_BUILD_JOB_NAME) }
if (agcTriggered) { triggered.add(env.AGC_MACOS_BUILD_JOB_NAME) }
def target = "${env.SOURCE_BRANCH}@${pinnedRevision.take(12)}"
currentBuild.description = triggered.isEmpty()
? "本轮提交与两条下游都无关,未触发任何发布:${target}"
@@ -7723,6 +7723,18 @@ for (const [snippet, reason] of [
"AGC_GLOBAL_VERSION_ARTIFACT = 'agc-global-version.txt'",
'必须从发号 Job 的归档产物读取总版本号',
],
[
"AGC_MACOS_BUILD_JOB_NAME = 'Genarrative-Agc-MacOS-Build'",
'必须把 macOS 渠道构建纳入同一轮调度',
],
[
'build job: env.AGC_MACOS_BUILD_JOB_NAME, wait: false, propagate: false, parameters: agcMacosParameters',
'必须用独立参数列表触发 macOS 渠道构建(pinnedParameters 只允许 Full Build 使用一次)',
],
[
"booleanParam(name: 'SKIP_IF_SUPERSEDED', value: true)",
'必须让节点离线期间排队的 macOS 旧构建自行让位,不发布过期版本',
],
]) {
if (!scheduledRevisionTriggerContent.includes(snippet)) {
failed = true;