f426944701
Project CI / AI game creator shell Rust crates (push) Successful in 1m18s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m46s
Project CI / Backend tests (push) Successful in 3m36s
Project CI / Frontend tests (push) Successful in 1m43s
Project CI / Native shell tests (push) Successful in 5m40s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 7m35s
Project CI / Repository checks (push) Successful in 1m50s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m31s
Project CI / AI game creator shell web tests (push) Successful in 1m16s
- 更新弹窗改为窗口居中纵向排版 - 停止自动生成渠道清单更新摘要 - 仅保留 AGC_UPDATE_RELEASE_NOTES 手动更新说明 - 清理 Jenkins 摘要锚点与 macOS 额外拉取 master 逻辑 - 同步开发运维、技术方案和回归测试
257 lines
15 KiB
Plaintext
257 lines
15 KiB
Plaintext
pipeline {
|
||
agent { label 'genarrative-agc-macos' }
|
||
options {
|
||
disableConcurrentBuilds()
|
||
skipDefaultCheckout(true)
|
||
// 实测参考:合并主线后的双架构构建 68.5 分钟、更早一次 78 分钟;节点是共用办公机,
|
||
// 冷 sccache + 机器被占用时会更慢,因此上限放到 150 分钟而不是贴着观测值。
|
||
timeout(time: 150, unit: 'MINUTES')
|
||
buildDiscarder(logRotator(numToKeepStr: '10', artifactNumToKeepStr: '3'))
|
||
timestamps()
|
||
}
|
||
parameters {
|
||
string(name: 'SOURCE_BRANCH', defaultValue: 'master', description: '必须含 macOS 单架构(arm64)发布实现的受信任分支')
|
||
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: 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 内存且是日常办公机,若构建期间出现明显换页可临时调低。只影响本次构建')
|
||
string(name: 'NOTIFICATION_EMAILS', defaultValue: '', description: '本次运行追加邮件通知收件人;会与持久收件人凭据合并发送')
|
||
}
|
||
environment {
|
||
GIT_REMOTE_URL = 'ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git'
|
||
GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-ssh'
|
||
AGC_OSS_BUCKET = 'agc-dev'
|
||
AGC_OSS_ENDPOINT = 'oss-rg-china-mainland.aliyuncs.com'
|
||
// 与 Linux 生产管线(api-build / stdb-module-build)对齐:Rust 编译走 sccache 对象缓存。
|
||
// 本 Job 只编译 aarch64-apple-darwin 单架构(Intel 未支持):sccache 与工作区 target 目录跨构建复用。
|
||
// 缓存根固定在 HOME 下的稳定目录,避免 WORKSPACE 重建导致近似冷构建;可用下面的变量覆盖。
|
||
GENARRATIVE_AGC_MACOS_CACHE_ROOT = 'caches/genarrative-jenkins/agc-macos'
|
||
RUSTC_WRAPPER = 'sccache'
|
||
SCCACHE_CACHE_SIZE = '20G'
|
||
CARGO_INCREMENTAL = '0'
|
||
// 不把节点用户名写进仓库:PATH 在下面的 shell 步骤里按 $HOME 展开。
|
||
AGC_EXTRA_PATH = '/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin'
|
||
}
|
||
stages {
|
||
stage('Checkout') {
|
||
steps {
|
||
// 只允许在专用 Agent 目录下构建:默认按约定匹配 $HOME/Library/Jenkins/agents/<node>/workspace/,
|
||
// 不写死某个节点名(节点改名后仍成立),也可用 AGC_AGENT_ROOT 显式覆盖。
|
||
// 目的是防止把开发 checkout 当成 Jenkins workspace。
|
||
sh '''
|
||
set -eu
|
||
# 手工「立即构建」时可选参数可能根本没定义(不是空值),先统一成空串/默认值,
|
||
# 否则 set -u 会让 Checkout 直接失败。
|
||
SOURCE_BRANCH="${SOURCE_BRANCH:-master}"
|
||
if [ -n "${AGC_AGENT_ROOT:-}" ]; then
|
||
case "$WORKSPACE" in "$AGC_AGENT_ROOT"/workspace/*) ;; *) echo "拒绝非专用 Agent 工作区:$WORKSPACE"; exit 1;; esac
|
||
else
|
||
case "$WORKSPACE" in "$HOME"/Library/Jenkins/agents/*/workspace/*) ;; *) echo "拒绝非专用 Agent 工作区:$WORKSPACE"; exit 1;; esac
|
||
fi
|
||
test "$(uname -s)" = Darwin
|
||
git check-ref-format --branch "$SOURCE_BRANCH" >/dev/null
|
||
'''
|
||
withCredentials([sshUserPrivateKey(credentialsId: env.GIT_REMOTE_CREDENTIAL_ID, keyFileVariable: 'GIT_SSH_KEY', usernameVariable: 'GIT_SSH_USER')]) {
|
||
sh '''
|
||
set -eu
|
||
# 同上:可选参数在「立即构建」里可能是未定义变量,统一兜底再使用。
|
||
SOURCE_BRANCH="${SOURCE_BRANCH:-master}"
|
||
COMMIT_HASH="${COMMIT_HASH:-}"
|
||
SKIP_IF_SUPERSEDED="${SKIP_IF_SUPERSEDED:-false}"
|
||
export GIT_SSH_COMMAND="ssh -i \\"$GIT_SSH_KEY\\" -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes"
|
||
if [ ! -d .git ]; then
|
||
git init
|
||
git remote add origin "$GIT_REMOTE_URL"
|
||
fi
|
||
test "$(git remote get-url origin)" = "$GIT_REMOTE_URL"
|
||
git fetch --no-tags origin "+refs/heads/$SOURCE_BRANCH:refs/remotes/origin/$SOURCE_BRANCH"
|
||
ref="refs/remotes/origin/$SOURCE_BRANCH"
|
||
if [ -n "$COMMIT_HASH" ]; then
|
||
case "$COMMIT_HASH" in *[!0-9a-fA-F]* ) echo 'COMMIT_HASH 必须为十六进制'; exit 1;; esac
|
||
test "${#COMMIT_HASH}" -ge 7 && test "${#COMMIT_HASH}" -le 40
|
||
git cat-file -e "$COMMIT_HASH^{commit}"
|
||
git merge-base --is-ancestor "$COMMIT_HASH" "$ref"
|
||
ref="$COMMIT_HASH"
|
||
fi
|
||
git reset --hard "$ref"
|
||
# 不清除 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
|
||
# LaunchAgent 只给最小 PATH:按 $HOME 展开工具链位置,不把节点用户名写进仓库。
|
||
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:${AGC_EXTRA_PATH}"
|
||
test "$(uname -m)" = arm64
|
||
node --version
|
||
npm --version
|
||
cargo --version
|
||
xcrun --find clang
|
||
xcrun --find lipo
|
||
arch -x86_64 /usr/bin/uname -m
|
||
if command -v sccache >/dev/null 2>&1; then sccache --version; else echo '[agc-macos] 未找到 sccache;本次回退到 rustc 直接构建。'; fi
|
||
rustup target add aarch64-apple-darwin x86_64-apple-darwin
|
||
npm ci --no-audit --no-fund
|
||
node apps/ai-game-creator-shell/scripts/prepare-macos-codex.mjs
|
||
'''
|
||
}
|
||
}
|
||
stage('Package, verify and publish') {
|
||
when {
|
||
expression { return env.AGC_BUILD_SUPERSEDED != 'true' }
|
||
}
|
||
steps {
|
||
withCredentials([
|
||
string(credentialsId: 'AliyunAccessKeyId', variable: 'AGC_OSS_ACCESS_KEY_ID'),
|
||
string(credentialsId: 'AliyunaccessKeySecret', variable: 'AGC_OSS_ACCESS_KEY_SECRET'),
|
||
string(credentialsId: 'AgcUpdaterSigningKey', variable: 'TAURI_SIGNING_PRIVATE_KEY'),
|
||
string(credentialsId: 'AgcUpdaterSigningKeyPassword', variable: 'TAURI_SIGNING_PRIVATE_KEY_PASSWORD'),
|
||
]) {
|
||
withEnv([
|
||
"OSSUTIL_BIN=${params.OSSUTIL_BIN}",
|
||
"CARGO_BUILD_JOBS=${params.CARGO_BUILD_JOBS}",
|
||
"AGC_UPDATE_CHANNEL=${params.AGC_UPDATE_CHANNEL}",
|
||
"AGC_RELEASE_VERSION=${params.AGC_RELEASE_VERSION}",
|
||
"AGC_RELEASE_DRY_RUN=${params.AGC_RELEASE_DRY_RUN ? '1' : '0'}",
|
||
"AGC_UPDATE_RELEASE_NOTES=${params.AGC_UPDATE_RELEASE_NOTES}",
|
||
]) {
|
||
sh '''
|
||
set -eu
|
||
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:${AGC_EXTRA_PATH}"
|
||
echo "[agc-macos] 渠道=${AGC_UPDATE_CHANNEL} 分区=${AGC_UPDATE_CHANNEL}-mac 目标=aarch64-apple-darwin(单架构,Intel 未支持)dry-run=${AGC_RELEASE_DRY_RUN}"
|
||
ossutil_bin="${OSSUTIL_BIN:-ossutil}"
|
||
if command -v "${ossutil_bin}" >/dev/null 2>&1; then
|
||
"${ossutil_bin}" --version | head -1
|
||
elif [ "${AGC_RELEASE_DRY_RUN}" = "1" ]; then
|
||
echo "[agc-macos] dry-run:未找到 ${ossutil_bin},只打印上传计划,不写 OSS"
|
||
else
|
||
echo "[agc-macos] 正式发布缺少 ossutil:${ossutil_bin};请安装或指定 OSSUTIL_BIN"
|
||
exit 1
|
||
fi
|
||
cache_root="${GENARRATIVE_AGC_MACOS_CACHE_ROOT:-caches/genarrative-jenkins/agc-macos}"
|
||
case "${cache_root}" in /*) ;; *) cache_root="${HOME:?HOME 不能为空}/${cache_root}" ;; esac
|
||
if command -v sccache >/dev/null 2>&1; then
|
||
export SCCACHE_DIR="${cache_root}/sccache"
|
||
mkdir -p "${SCCACHE_DIR}"
|
||
echo "[agc-macos] sccache 缓存目录: ${SCCACHE_DIR}"
|
||
else
|
||
echo '[agc-macos] 未找到 sccache,改用 rustc 直接构建。'
|
||
unset RUSTC_WRAPPER
|
||
fi
|
||
node --test apps/ai-game-creator-shell/scripts/prepare-macos-codex.test.mjs
|
||
node --test apps/ai-game-creator-shell/scripts/verify-updater-signature.test.mjs
|
||
node --test apps/ai-game-creator-shell/scripts/build-release.test.mjs apps/ai-game-creator-shell/scripts/cargo-features.test.mjs
|
||
node apps/ai-game-creator-shell/scripts/build-macos-ci.mjs
|
||
if command -v sccache >/dev/null 2>&1; then
|
||
echo '[agc-macos] sccache 统计(自 server 启动累计):'
|
||
sccache --show-stats 2>&1 | sed -n '1,6p'
|
||
fi
|
||
'''
|
||
}
|
||
}
|
||
}
|
||
}
|
||
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
|
||
}
|
||
}
|
||
}
|
||
post {
|
||
always {
|
||
script {
|
||
def ossDownloadUrl = ''
|
||
def releaseVersion = params.AGC_RELEASE_VERSION ?: ''
|
||
def sourceCommit = params.COMMIT_HASH ?: ''
|
||
def manifestPath = 'artifacts/latest.json'
|
||
if (currentBuild.currentResult == 'SUCCESS' && fileExists(manifestPath)) {
|
||
try {
|
||
def manifest = new groovy.json.JsonSlurperClassic().parseText(readFile(file: manifestPath))
|
||
releaseVersion = manifest?.version?.toString()?.trim() ?: releaseVersion
|
||
if (!params.AGC_RELEASE_DRY_RUN) {
|
||
def firstDownload = manifest?.downloads instanceof Map
|
||
? manifest.downloads.values().find { it instanceof Map && it.url?.toString()?.trim() }
|
||
: null
|
||
ossDownloadUrl = firstDownload?.url?.toString()?.trim() ?: ''
|
||
}
|
||
} catch (error) {
|
||
echo "读取 AGC OSS 下载链接失败,邮件不附加链接:${error.message}"
|
||
}
|
||
}
|
||
if (!sourceCommit && fileExists('.jenkins-source-commit')) {
|
||
sourceCommit = readFile(file: '.jenkins-source-commit').trim()
|
||
}
|
||
def notificationParameters = [
|
||
string(name: 'SOURCE_JOB_NAME', value: env.JOB_NAME),
|
||
string(name: 'SOURCE_BUILD_NUMBER', value: env.BUILD_NUMBER),
|
||
string(name: 'SOURCE_BUILD_URL', value: env.BUILD_URL ?: ''),
|
||
string(name: 'SOURCE_RESULT', value: currentBuild.currentResult ?: 'UNKNOWN'),
|
||
string(name: 'SOURCE_BRANCH', value: params.SOURCE_BRANCH ?: ''),
|
||
string(name: 'SOURCE_COMMIT', value: sourceCommit),
|
||
string(name: 'BUILD_VERSION', value: releaseVersion),
|
||
string(name: 'DEPLOY_TARGET', value: "${params.AGC_UPDATE_CHANNEL ?: 'dev'}-mac"),
|
||
string(name: 'OSS_DOWNLOAD_URL', value: ossDownloadUrl),
|
||
string(name: 'SUMMARY', value: params.AGC_RELEASE_DRY_RUN
|
||
? 'AGC macOS 渠道演练结束,未写入 OSS'
|
||
: 'AGC macOS 渠道构建与 OSS 发布结束'),
|
||
]
|
||
def notificationRecipients = params.NOTIFICATION_EMAILS?.trim()
|
||
if (notificationRecipients) {
|
||
notificationParameters.add(string(name: 'EMAIL_RECIPIENTS', value: notificationRecipients))
|
||
}
|
||
try {
|
||
build job: 'Genarrative-Notify-Email',
|
||
wait: false,
|
||
propagate: false,
|
||
parameters: notificationParameters
|
||
} catch (error) {
|
||
echo "邮件通知触发失败: ${error.message}"
|
||
}
|
||
}
|
||
}
|
||
failure {
|
||
echo 'macOS 发布失败:先看本构建控制台末尾。常见原因——更新包缺 .sig(误传 --no-sign 或签名私钥未注入)、DMG 幂等失败(workspace 残留同名产物)、可用空间低于 8 GiB、以及被 SKIP_IF_SUPERSEDED 之外的提交校验拒绝。'
|
||
}
|
||
aborted {
|
||
echo 'macOS 发布被中断(超时或人工中止):构建未走到归档阶段时不会有任何产物,也不会写 OSS。'
|
||
}
|
||
success {
|
||
echo params.AGC_RELEASE_DRY_RUN
|
||
? 'macOS arm64 渠道演练完成:已构建、验签并生成清单,未写入 OSS;Apple 签名与公证暂缺。'
|
||
: "macOS arm64 更新包、签名、首装包与 ${params.AGC_UPDATE_CHANNEL}-mac 分区清单已上传 OSS(清单只登记 darwin-aarch64);Apple 签名与公证暂缺,首装需手动放行。"
|
||
}
|
||
}
|
||
}
|