Files
Genarrative/jenkins/Jenkinsfile.scheduled-release-trigger
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

182 lines
9.0 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 release 每日调度管线:每天 04:00 检查源码分支,
// 只有上一轮 release 调度之后出现客户端相关路径变更时,才用同一个固定 commit
// 触发 AGC Windows/macOS 两个 release 分区构建。
pipeline {
agent {
label 'linux && genarrative-build'
}
options {
disableConcurrentBuilds()
skipDefaultCheckout(true)
buildDiscarder(logRotator(numToKeepStr: '100', artifactNumToKeepStr: '10'))
}
triggers {
cron('0 4 * * *')
}
environment {
GIT_REMOTE_URL = 'ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git'
GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-ssh'
AGC_WINDOWS_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-release-revision'
AGC_SCOPE_CACHE_DIR = '.agc-release-scope-cache'
}
parameters {
string(name: 'SOURCE_BRANCH', defaultValue: 'master', description: '被检查的源码分支;默认 master')
booleanParam(name: 'FORCE_TRIGGER', defaultValue: false, description: '勾选后忽略版本与路径比较,强制发布 AGC release 渠道')
}
stages {
stage('Resolve Revision') {
steps {
withCredentials([sshUserPrivateKey(credentialsId: env.GIT_REMOTE_CREDENTIAL_ID, keyFileVariable: 'GENARRATIVE_GIT_SSH_KEY')]) {
sh '''#!/usr/bin/env bash
set -euo pipefail
export GIT_SSH_COMMAND="ssh -i ${GENARRATIVE_GIT_SSH_KEY:?缺少 Git SSH 凭据} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new"
revision="$(git ls-remote "${GIT_REMOTE_URL:?缺少 GIT_REMOTE_URL}" "refs/heads/${SOURCE_BRANCH}" | awk 'NR == 1 { print $1 }')"
if [[ ! "${revision}" =~ ^[0-9a-f]{40}$ ]]; then
echo "无法解析远端分支版本: branch=${SOURCE_BRANCH} revision=${revision:-<empty>}" >&2
exit 1
fi
printf '%s' "${revision}" > .jenkins-remote-revision
echo "release 远端版本: branch=${SOURCE_BRANCH} revision=${revision}"
'''
}
script {
env.REMOTE_REVISION = readFile('.jenkins-remote-revision').trim()
env.LAST_TRIGGERED_REVISION = fileExists(env.REVISION_STATE_FILE) ? readFile(env.REVISION_STATE_FILE).trim() : ''
env.REVISION_CHANGED = (params.FORCE_TRIGGER || env.LAST_TRIGGERED_REVISION != env.REMOTE_REVISION) ? 'true' : 'false'
if (env.REVISION_CHANGED != 'true') {
currentBuild.result = 'NOT_BUILT'
currentBuild.description = "release 版本未变化,跳过:${env.SOURCE_BRANCH} 仍是 ${env.REMOTE_REVISION.take(12)}"
echo currentBuild.description
}
}
}
}
stage('Resolve AGC Release Scope') {
when {
expression { return env.REVISION_CHANGED == 'true' }
}
steps {
withCredentials([sshUserPrivateKey(credentialsId: env.GIT_REMOTE_CREDENTIAL_ID, keyFileVariable: 'GENARRATIVE_GIT_SSH_KEY')]) {
script {
// 判定失败一律按「需要发布」处理,避免路径过滤阻塞客户端发布。
def output = 'agc=changed'
try {
output = sh(script: '''#!/usr/bin/env bash
set -uo pipefail
export GIT_SSH_COMMAND="ssh -i ${GENARRATIVE_GIT_SSH_KEY:?缺少 Git SSH 凭据} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new"
previous="$(cat "${REVISION_STATE_FILE}" 2>/dev/null || true)"
if [[ -z "${previous}" ]]; then
printf 'agc=changed\n'
exit 0
fi
mkdir -p "${AGC_SCOPE_CACHE_DIR}"
if [[ ! -d "${AGC_SCOPE_CACHE_DIR}/.git" ]]; then
git -C "${AGC_SCOPE_CACHE_DIR}" init --quiet
git -C "${AGC_SCOPE_CACHE_DIR}" remote add origin "${GIT_REMOTE_URL}" 2>/dev/null || true
fi
refspec="+refs/heads/${SOURCE_BRANCH}:refs/remotes/origin/${SOURCE_BRANCH}"
if ! git -C "${AGC_SCOPE_CACHE_DIR}" fetch --quiet --depth=200 --no-tags --filter=blob:none origin "${refspec}"; then
git -C "${AGC_SCOPE_CACHE_DIR}" fetch --quiet --depth=200 --no-tags origin "${refspec}" || { printf 'agc=changed\n'; exit 0; }
fi
if ! git -C "${AGC_SCOPE_CACHE_DIR}" cat-file -e "${previous}^{commit}" 2>/dev/null; then
echo "浅取窗口内没有 ${previous},按需要发布处理" >&2
printf 'agc=changed\n'
exit 0
fi
changed_paths="$(git -C "${AGC_SCOPE_CACHE_DIR}" diff --name-only "${previous}" "${REMOTE_REVISION}" 2>/dev/null || true)"
agc_scope=unchanged
while IFS= read -r changed_path; do
[[ -z "${changed_path}" ]] && continue
case "${changed_path}" in
apps/ai-game-creator-shell/*|packages/*|server-rs/crates/*|plugins/agc-cocos-editor/*|plugins/agc-unity-editor/*|plugins/agc-godot-editor/*|apps/desktop-shell/src-tauri/icons/*|package.json|package-lock.json)
agc_scope=changed
;;
esac
done <<< "${changed_paths}"
printf 'agc=%s\n' "${agc_scope}"
''', returnStdout: true).trim()
} catch (error) {
echo "release 发布范围判定失败,按需要发布处理:${error}"
output = 'agc=changed'
}
def values = output.split('\n').collect { it.trim() }.findAll { it }
def entry = values.find { it.startsWith('agc=') }
def value = entry == null ? '' : entry.split('=')[1]
env.AGC_RELEASE_SCOPE = (value == 'unchanged') ? 'unchanged' : 'changed'
echo "release 发布范围:AGC=${env.AGC_RELEASE_SCOPE}(上一轮已触发 revision=${env.LAST_TRIGGERED_REVISION ?: '无'}"
}
}
}
}
stage('Trigger AGC Release Builds') {
when {
expression { return env.REVISION_CHANGED == 'true' }
}
steps {
script {
def pinnedRevision = env.REMOTE_REVISION
def agcTriggered = false
if (params.FORCE_TRIGGER || env.AGC_RELEASE_SCOPE != 'unchanged') {
// Windows 与 macOS 共用一个总号;发号 Job 是唯一发号源。
def versionRun = build job: env.AGC_GLOBAL_VERSION_JOB_NAME,
wait: true,
propagate: true,
parameters: [
string(name: 'SOURCE_BRANCH', value: env.SOURCE_BRANCH),
string(name: 'COMMIT_HASH', value: pinnedRevision),
string(name: 'AGC_CHANNEL', value: 'release-unified'),
]
copyArtifacts(
projectName: env.AGC_GLOBAL_VERSION_JOB_NAME,
selector: specific(versionRun.number.toString()),
filter: env.AGC_GLOBAL_VERSION_ARTIFACT,
target: '.',
flatten: true,
)
env.AGC_GLOBAL_VERSION = readFile(env.AGC_GLOBAL_VERSION_ARTIFACT).trim()
echo "本轮 AGC release 客户端总版本号: ${env.AGC_GLOBAL_VERSION}"
def agcParameters = [
string(name: 'SOURCE_BRANCH', value: env.SOURCE_BRANCH),
string(name: 'COMMIT_HASH', value: pinnedRevision),
string(name: 'AGC_UPDATE_CHANNEL', value: 'release'),
string(name: 'AGC_RELEASE_VERSION', value: env.AGC_GLOBAL_VERSION),
]
build job: env.AGC_WINDOWS_BUILD_JOB_NAME, wait: false, propagate: false, parameters: agcParameters
// macOS 节点是办公机:离线期间排队的旧构建在节点回来后必须自行让位。
def agcMacosParameters = agcParameters + [
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 "上一轮 release 调度后没有 AGC 客户端相关变更,跳过 ${env.AGC_WINDOWS_BUILD_JOB_NAME} 与 ${env.AGC_MACOS_BUILD_JOB_NAME};需要强制发布时勾选 FORCE_TRIGGER"
}
writeFile file: env.REVISION_STATE_FILE, text: pinnedRevision
def target = "${env.SOURCE_BRANCH}@${pinnedRevision.take(12)}"
currentBuild.description = agcTriggered
? "已触发 AGC release${env.AGC_WINDOWS_BUILD_JOB_NAME} 与 ${env.AGC_MACOS_BUILD_JOB_NAME} ${target}"
: "本轮没有 AGC 客户端相关变更,未触发 release 发布:${target}"
echo currentBuild.description
}
}
}
}
post {
always {
echo "release 调度结束: branch=${env.SOURCE_BRANCH} revision=${env.REMOTE_REVISION ?: ''} changed=${env.REVISION_CHANGED ?: ''} agcScope=${env.AGC_RELEASE_SCOPE ?: ''}"
}
}
}