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 逻辑 - 同步开发运维、技术方案和回归测试
275 lines
14 KiB
Plaintext
275 lines
14 KiB
Plaintext
pipeline {
|
||
agent {
|
||
label 'windows && win2022'
|
||
}
|
||
|
||
options {
|
||
disableConcurrentBuilds()
|
||
skipDefaultCheckout(true)
|
||
buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20'))
|
||
}
|
||
|
||
environment {
|
||
GIT_REMOTE_URL = 'ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git'
|
||
GIT_REMOTE_CREDENTIAL_ID = 'genarrative-local-gitea-ssh'
|
||
GENARRATIVE_NPM_VERSION = '10.9.7'
|
||
AGC_OSS_BUCKET = 'agc-dev'
|
||
AGC_OSS_ENDPOINT = 'oss-rg-china-mainland.aliyuncs.com'
|
||
AGC_WINDOWS_PATH = 'C:\\Tools\\Git\\cmd;C:\\Program Files\\nodejs;C:\\Program Files\\dotnet;C:\\Users\\Administrator\\.cargo\\bin;C:\\Tools\\ossutil;C:\\BuildTools\\Common7\\IDE\\CommonExtensions\\Microsoft\\CMake\\CMake\\bin;C:\\Python312;C:\\Python312\\Scripts;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Windows\\System32\\OpenSSH\\'
|
||
}
|
||
|
||
parameters {
|
||
string(name: 'SOURCE_BRANCH', defaultValue: 'master', description: '源码分支')
|
||
string(name: 'COMMIT_HASH', defaultValue: '', description: '可选,指定属于 SOURCE_BRANCH 的 Git commit')
|
||
string(name: 'AGC_RELEASE_VERSION', defaultValue: '', description: '三段版本号;统一构建由 Genarrative-Agc-Global-Version-Issue 发号后透传;留空时本地兜底发号(读 OSS 总号 +1 并写回)')
|
||
string(name: 'AGC_UPDATE_CHANNEL', defaultValue: 'dev', description: 'AGC 发布渠道:dev、release 或自定义小写名称;此 Job 构建 Windows,macOS 在对应构建机执行')
|
||
booleanParam(name: 'AGC_RELEASE_DRY_RUN', defaultValue: false, description: '勾选后只构建并打印将要执行的上传命令,不写入 OSS')
|
||
text(name: 'AGC_UPDATE_RELEASE_NOTES', defaultValue: '', description: '可选,支持多行文本;留空则本次发布不携带更新摘要')
|
||
string(name: 'OSSUTIL_BIN', defaultValue: 'ossutil', description: 'ossutil 或 ossutil.exe 的绝对路径/命令名')
|
||
string(name: 'NOTIFICATION_EMAILS', defaultValue: '', description: '本次运行追加邮件通知收件人;会与持久收件人凭据合并发送')
|
||
}
|
||
|
||
stages {
|
||
stage('Checkout') {
|
||
steps {
|
||
withCredentials([sshUserPrivateKey(credentialsId: env.GIT_REMOTE_CREDENTIAL_ID, keyFileVariable: 'GIT_SSH_KEY', usernameVariable: 'GIT_SSH_USER')]) {
|
||
powershell '''
|
||
$ErrorActionPreference = 'Stop'
|
||
$env:GIT_SSH_COMMAND = "ssh -i `"$env:GIT_SSH_KEY`" -o StrictHostKeyChecking=no"
|
||
$git = 'C:\\Tools\\Git\\cmd\\git.exe'
|
||
if (-not (Test-Path $git)) { throw "找不到 Git: $git" }
|
||
if (Test-Path '.git') {
|
||
& $git fetch --tags --force --prune origin "+refs/heads/$($env:SOURCE_BRANCH):refs/remotes/origin/$($env:SOURCE_BRANCH)"
|
||
if ($LASTEXITCODE -ne 0) { throw 'Git fetch 失败' }
|
||
# Tauri 的 NSIS 工具缓存位于工作区内的 target/.tauri:裸 git clean -fdx
|
||
# 会把它一起删掉,导致每个构建都重新从 GitHub 下载工具链(下载无重试,
|
||
# 响应截断即 `io: unexpected end of file`)。这里只排除该缓存目录,
|
||
# 其余 target 内容照常清空。
|
||
& $git clean -fdx -e 'apps/ai-game-creator-shell/src-tauri/target/.tauri'
|
||
& $git reset --hard "origin/$($env:SOURCE_BRANCH)"
|
||
} else {
|
||
& $git clone --no-single-branch $env:GIT_REMOTE_URL .
|
||
if ($LASTEXITCODE -ne 0) { throw 'Git clone 失败' }
|
||
& $git fetch --tags --force origin "refs/heads/$($env:SOURCE_BRANCH):refs/remotes/origin/$($env:SOURCE_BRANCH)"
|
||
if ($LASTEXITCODE -ne 0) { throw 'Git fetch 分支失败' }
|
||
& $git checkout --detach "origin/$($env:SOURCE_BRANCH)"
|
||
}
|
||
$commit = ([string]$env:COMMIT_HASH).Trim()
|
||
if ($commit) {
|
||
if ($commit -notmatch '^[0-9a-fA-F]{7,64}$') {
|
||
throw 'COMMIT_HASH 必须是 7-64 位十六进制 Git commit。'
|
||
}
|
||
& $git cat-file -e "${commit}^{commit}"
|
||
if ($LASTEXITCODE -ne 0) {
|
||
throw "COMMIT_HASH 不存在: $commit"
|
||
}
|
||
$branchRef = "refs/remotes/origin/$($env:SOURCE_BRANCH)"
|
||
& $git merge-base --is-ancestor $commit $branchRef
|
||
if ($LASTEXITCODE -ne 0) {
|
||
throw "COMMIT_HASH 不属于 SOURCE_BRANCH: $($env:SOURCE_BRANCH)"
|
||
}
|
||
& $git checkout --detach $commit
|
||
}
|
||
$resolved = (& $git rev-parse HEAD).Trim()
|
||
Set-Content -Path '.jenkins-source-commit' -Value $resolved -NoNewline
|
||
Write-Host "源码 commit=$resolved"
|
||
'''
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('Toolchain preflight') {
|
||
steps {
|
||
withEnv(["PATH=${env.AGC_WINDOWS_PATH}", "OSSUTIL_BIN=${params.OSSUTIL_BIN}"]) {
|
||
powershell '''
|
||
$ErrorActionPreference = 'Stop'
|
||
$actualNpm = (npm --version).Trim()
|
||
if ($actualNpm -ne $env:GENARRATIVE_NPM_VERSION) {
|
||
throw "npm 版本不匹配:期望 $($env:GENARRATIVE_NPM_VERSION),实际 $actualNpm"
|
||
}
|
||
foreach ($commandName in @('node', 'npm', 'rustc', 'cargo', 'dotnet', 'cmake', 'python')) {
|
||
if (-not (Get-Command $commandName -ErrorAction SilentlyContinue)) {
|
||
throw "Windows AGC 节点缺少 $commandName,请安装并加入 AGC_WINDOWS_PATH。"
|
||
}
|
||
}
|
||
$dotnetSdks = @(dotnet --list-sdks)
|
||
if ($LASTEXITCODE -ne 0 -or -not ($dotnetSdks -match '^10[.]')) {
|
||
throw 'Windows AGC 节点需要 .NET 10 SDK 才能构建 Unity Attach helper。'
|
||
}
|
||
$cmakeVersion = ((cmake --version) | Select-Object -First 1).Trim()
|
||
if ($cmakeVersion -notmatch '([0-9]+)[.]([0-9]+)[.]([0-9]+)') {
|
||
throw "无法解析 CMake 版本:$cmakeVersion"
|
||
}
|
||
$cmakeMajor = [int]$Matches[1]
|
||
$cmakeMinor = [int]$Matches[2]
|
||
if ($cmakeMajor -lt 3 -or ($cmakeMajor -eq 3 -and $cmakeMinor -lt 25)) {
|
||
throw "Godot 原生扩展需要 CMake 3.25 及以上,当前:$cmakeVersion"
|
||
}
|
||
if (-not (@(cmake --help) -match 'Visual Studio 17 2022')) {
|
||
throw "Windows AGC 节点缺少 Visual Studio 2022 C++ 生成器,无法构建 Godot 原生扩展。"
|
||
}
|
||
$pythonVersion = ((python --version 2>&1) | Select-Object -First 1).Trim()
|
||
if ($pythonVersion -notmatch '^Python 3[.]') {
|
||
throw "Godot 原生扩展需要 Python 3,当前:$pythonVersion"
|
||
}
|
||
$ossutil = ([string]$env:OSSUTIL_BIN).Trim()
|
||
if (-not $ossutil) { $ossutil = 'ossutil' }
|
||
if (-not (Get-Command $ossutil -ErrorAction SilentlyContinue)) {
|
||
throw "Windows AGC 节点缺少 ossutil:$ossutil"
|
||
}
|
||
Write-Host "node=$((node --version).Trim())"
|
||
Write-Host "npm=$actualNpm"
|
||
Write-Host "rustc=$((rustc --version).Trim())"
|
||
Write-Host "cargo=$((cargo --version).Trim())"
|
||
Write-Host "dotnet-sdk=$((dotnet --version).Trim())"
|
||
Write-Host "cmake=$cmakeVersion"
|
||
Write-Host "python=$pythonVersion"
|
||
Write-Host "jenkins-user=$([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)"
|
||
Write-Host "local-app-data=$env:LOCALAPPDATA"
|
||
$targetTools = Join-Path $PWD 'apps/ai-game-creator-shell/src-tauri/target/.tauri'
|
||
New-Item -ItemType Directory -Force -Path $targetTools | Out-Null
|
||
$probe = Join-Path $targetTools '.jenkins-write-probe'
|
||
Set-Content -Path $probe -Value 'ok' -NoNewline
|
||
Remove-Item -LiteralPath $probe -Force
|
||
Write-Host "tauri-tools-dir=$targetTools"
|
||
'''
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('Install dependencies') {
|
||
steps {
|
||
withEnv(["PATH=${env.AGC_WINDOWS_PATH}"]) {
|
||
powershell '''
|
||
$ErrorActionPreference = 'Stop'
|
||
npm ci --no-audit --no-fund
|
||
'''
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('Tauri NSIS toolchain') {
|
||
steps {
|
||
withEnv(["PATH=${env.AGC_WINDOWS_PATH}"]) {
|
||
// 必须早于 Rust 编译与打包:Tauri bundler 自己下载 NSIS 工具链时不重试,
|
||
// 响应截断只会报 `io: unexpected end of file`,让发布在数分钟后才失败。
|
||
powershell '''
|
||
$ErrorActionPreference = 'Stop'
|
||
node --test apps/ai-game-creator-shell/scripts/nsis-toolset.test.mjs
|
||
if ($LASTEXITCODE -ne 0) { throw "NSIS 工具链回归测试失败,退出码=$LASTEXITCODE" }
|
||
node apps/ai-game-creator-shell/scripts/ensure-nsis-toolset.mjs
|
||
if ($LASTEXITCODE -ne 0) { throw "NSIS 工具链预置失败,退出码=$LASTEXITCODE" }
|
||
$nsis = Join-Path $PWD 'apps/ai-game-creator-shell/src-tauri/target/.tauri/NSIS/makensis.exe'
|
||
if (-not (Test-Path $nsis)) { throw "NSIS makensis.exe 缺失:$nsis" }
|
||
& $nsis -VERSION
|
||
if ($LASTEXITCODE -ne 0) { throw "NSIS makensis.exe 无法执行,退出码=$LASTEXITCODE" }
|
||
'''
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('Build and upload') {
|
||
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([
|
||
"PATH=${env.AGC_WINDOWS_PATH}",
|
||
"OSSUTIL_BIN=${params.OSSUTIL_BIN}",
|
||
"AGC_RELEASE_VERSION=${params.AGC_RELEASE_VERSION}",
|
||
"AGC_UPDATE_CHANNEL=${params.AGC_UPDATE_CHANNEL}",
|
||
'AGC_BUILD_TARGET=x86_64-pc-windows-msvc',
|
||
"AGC_RELEASE_DRY_RUN=${params.AGC_RELEASE_DRY_RUN ? '1' : '0'}",
|
||
"AGC_UPDATE_RELEASE_NOTES=${params.AGC_UPDATE_RELEASE_NOTES}",
|
||
]) {
|
||
powershell '''
|
||
$ErrorActionPreference = 'Stop'
|
||
npm run ai-game-creator-shell:release:upload
|
||
$buildExitCode = $LASTEXITCODE
|
||
if ($buildExitCode -ne 0) {
|
||
$nsis = Join-Path $PWD 'apps/ai-game-creator-shell/src-tauri/target/.tauri/NSIS/makensis.exe'
|
||
Write-Host "Tauri NSIS path=$nsis"
|
||
if (Test-Path $nsis) {
|
||
Get-Item $nsis | Select-Object FullName, Length, LastWriteTime
|
||
try { & $nsis -VERSION } catch { Write-Host "无法直接启动 Tauri NSIS:$($_.Exception.Message)" }
|
||
} else {
|
||
Write-Host 'Tauri NSIS makensis.exe 不存在。'
|
||
}
|
||
throw "AGC 发布构建失败,退出码=$buildExitCode"
|
||
}
|
||
'''
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('Archive release') {
|
||
steps {
|
||
archiveArtifacts artifacts: 'apps/ai-game-creator-shell/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/**/*.exe,apps/ai-game-creator-shell/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/**/*.sig,apps/ai-game-creator-shell/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/latest.json,apps/ai-game-creator-shell/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/legacy-latest.json,apps/ai-game-creator-shell/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/release-notes.txt,.jenkins-source-commit', fingerprint: true
|
||
}
|
||
}
|
||
}
|
||
|
||
post {
|
||
always {
|
||
script {
|
||
def ossDownloadUrl = ''
|
||
def releaseVersion = params.AGC_RELEASE_VERSION ?: ''
|
||
def sourceCommit = params.COMMIT_HASH ?: ''
|
||
def manifestPath = 'apps/ai-game-creator-shell/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/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'}-win"),
|
||
string(name: 'OSS_DOWNLOAD_URL', value: ossDownloadUrl),
|
||
string(name: 'SUMMARY', value: params.AGC_RELEASE_DRY_RUN
|
||
? 'AGC Windows 渠道演练结束,未写入 OSS'
|
||
: 'AGC Windows 渠道构建与 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}"
|
||
}
|
||
}
|
||
}
|
||
success {
|
||
echo params.AGC_RELEASE_DRY_RUN
|
||
? "AGC ${params.AGC_UPDATE_CHANNEL} 渠道演练完成:已构建并生成清单,未写入 OSS。"
|
||
: "AGC ${params.AGC_UPDATE_CHANNEL} 渠道安装包、签名与渠道清单已构建并上传 OSS。"
|
||
}
|
||
}
|
||
}
|