Files
OpenZen/.github/workflows/build-loader.yml
T
Shirona1337 9f094b4aa8 fix(ci): persist vcpkg binary archives so qtbase stops rebuilding
The Qt cache hit every run yet qtbase was still compiled from source
(~32 min) each time. Two root causes:

1. The cache path included vcpkg\installed, but the build runs
   `gradlew clean` which deletes native/build/vcpkg_installed (the
   manifest-mode install dir). vcpkg restores packages from the
   file-backed binary cache (vcpkg-archives), which is the only thing
   that lets it skip a rebuild — and it restored 0 packages.

2. vcpkg-archives was never persisted across runs: the cache key was
   static (hash of vcpkg.json, which rarely changes), and actions/cache
   never overwrites an existing key. So every run got a cache hit on
   restore (empty archives), rebuilt qtbase, repopulated vcpkg-archives,
   then hit "cache hit, not saving" at Post — throwing the rebuilt
   archive away. Infinite rebuild loop.

Fix: cache only vcpkg-archives (drop the ~1.2 GB dead vcpkg\installed),
and rotate the key with github.run_id so Post always saves a fresh entry,
with layered restore-keys to warm-start from the newest prior archive.
The next build still primes the archive once (~32 min); subsequent builds
restore qtbase and finish in minutes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 01:32:11 +08:00

283 lines
13 KiB
YAML
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.
name: Build Loader
on:
push:
branches: [master]
# Only build when something that actually affects the output changed.
# README / docs / issue templates / .gitignore / .claude/ get to skip.
# Use workflow_dispatch (below) to force a run for anything else.
paths:
- 'src/**'
- 'native/**'
- 'build.gradle'
- 'settings.gradle'
- 'gradle.properties'
- 'gradle/wrapper/**'
- 'gradlew'
- 'gradlew.bat'
- '.github/workflows/build-loader.yml'
workflow_dispatch: {}
# contents: write so the [Release] commit-marker path can create a
# GitHub Release and upload the built artifacts.
# actions: write needed for vcpkg's GitHub Actions binary cache (x-gha backend).
permissions:
contents: write
actions: write
jobs:
build:
runs-on: windows-2022
timeout-minutes: 180
# Honour a [SKIP CI] marker (case-insensitive — GitHub's contains() is
# case-insensitive for string operands) anywhere in the head commit
# message. workflow_dispatch always runs since head_commit is null
# there and the !contains() short-circuits to true.
if: ${{ github.event_name != 'push' || !contains(github.event.head_commit.message, '[SKIP CI]') }}
env:
# NOTE: do not declare VCPKG_ROOT here. The windows-2022 runner
# ships VS Enterprise which pre-sets a system-wide VCPKG_ROOT
# pointing at its own (empty) vcpkg dir, and that pre-set value
# wins over a job-level env in PowerShell expansions. We export
# the right VCPKG_ROOT into $GITHUB_ENV inside the clone step
# so every later step sees our copy.
#
# We rely on actions/cache for the installed Qt artifacts; the
# vcpkg x-gha backend depends on the v1 GitHub Actions cache API
# which has been deprecated, and a fresh runner image often does
# not have ACTIONS_RESULTS_URL set for non-Node actions yet.
# Setting "files" as the binary source means vcpkg will still
# populate ${VCPKG_ROOT}/archives, which actions/cache then
# snapshots along with installed/.
VCPKG_BINARY_SOURCES: "clear;files,${{ github.workspace }}\\vcpkg-archives,readwrite"
# Opt every JavaScript action into the Node.js 24 runtime ahead of the
# 2026-06-02 forced switchover. The v4 / v1 pins below all still ship
# a Node 20 binary in their action.yml, and GitHub deprecated Node 20
# on 2025-09-19; setting this flag makes the runner execute them under
# Node 24 regardless, which silences the deprecation warning and
# de-risks the upcoming default flip.
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Resolve git revision
id: rev
shell: pwsh
run: |
$sha = git rev-parse --short=7 HEAD
"sha=$sha" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
Write-Host "Build revision: $sha"
- name: Setup JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
- name: Setup MSVC (x64)
uses: ilammy/msvc-dev-cmd@v1
with:
arch: x64
- name: Clone & bootstrap vcpkg
shell: pwsh
run: |
# Pin vcpkg to the SAME commit as native/vcpkg.json's
# "builtin-baseline". A floating HEAD clone made the qtbase port
# (and thus its package ABI) drift every time upstream vcpkg moved,
# so the restored Qt cache never matched and qtbase was rebuilt
# from source (~31 min) on every run. Tag 2026.04.27 ==
# 56bb2411609227288b70117ead2c47585ba07713 and ships qtbase 6.10.2.
# We pin 6.10.2 (not the newer 6.11.0 from 2026.05.25) because MSVC
# 14.44.x crashes with an internal compiler error (C1001) building
# Qt 6.11.0 from source; 6.10.2 builds cleanly on every toolset we use.
# Keep this tag and the builtin-baseline in vcpkg.json in lockstep.
$vcpkgTag = '2026.04.27'
$vcpkg = Join-Path $env:GITHUB_WORKSPACE 'vcpkg'
if (Test-Path $vcpkg) {
Write-Host "Reusing existing $vcpkg"
} else {
git clone --depth=1 --branch $vcpkgTag https://github.com/microsoft/vcpkg.git $vcpkg
}
& "$vcpkg\bootstrap-vcpkg.bat" -disableMetrics
& "$vcpkg\vcpkg.exe" version
"VCPKG_ROOT=$vcpkg" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
Write-Host "Exported VCPKG_ROOT=$vcpkg to GITHUB_ENV"
# Cache the vcpkg *binary archives* — the only thing that lets vcpkg skip
# rebuilding qtbase. The build runs `gradlew clean`, which deletes
# native/build/ (and thus the manifest install dir native/build/vcpkg_installed),
# so on every run vcpkg re-resolves the manifest and restores packages from
# this file-backed binary cache. If it's empty, qtbase is compiled from
# source (~32 min). (We previously cached vcpkg\installed too, but in
# manifest mode that tree is never consulted for the rebuild decision — only
# the binary archive is — so it was ~1.2 GB of dead weight.)
#
# The key MUST rotate per run: actions/cache never overwrites an existing
# key, so a static key froze whatever archives the first run captured (none)
# and every later run hit "cache hit, not saving" at Post — discarding the
# qtbase archive it had just rebuilt, guaranteeing a fresh 32-min rebuild
# forever. Appending run_id makes Post always save a new entry; the layered
# restore-keys warm-start from the newest prior archive so qtbase is
# restored, not rebuilt. (GitHub LRU-evicts old entries within the 10 GB
# budget; the archive is only a few hundred MB.)
- name: Cache vcpkg binary archives
uses: actions/cache@v4
with:
path: vcpkg-archives
key: vcpkg-qt-${{ runner.os }}-${{ hashFiles('native/vcpkg.json') }}-${{ github.run_id }}
restore-keys: |
vcpkg-qt-${{ runner.os }}-${{ hashFiles('native/vcpkg.json') }}-
vcpkg-qt-${{ runner.os }}-
- name: Install UPX
shell: pwsh
run: choco install upx -y --no-progress
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle', '**/gradle-wrapper.properties') }}
restore-keys: |
gradle-${{ runner.os }}-
- name: Build (clean dll upxCompress)
shell: pwsh
env:
OPENZEN_BUILD_REVISION: ${{ steps.rev.outputs.sha }}
run: |
Write-Host "VCPKG_ROOT = $env:VCPKG_ROOT"
Write-Host "JAVA_HOME = $env:JAVA_HOME"
Write-Host "OPENZEN_BUILD_REV = $env:OPENZEN_BUILD_REVISION"
.\gradlew.bat --no-daemon clean dll upxCompress
# Rename the two distributable artifacts so their final filenames carry
# the build sha. We rename rather than re-publish at build time so the
# local `./gradlew dll` workflow keeps producing stable filenames.
- name: Stage release artifacts
shell: pwsh
run: |
$sha = "${{ steps.rev.outputs.sha }}"
$exeSrc = "build\dist\OpenZenLoader.exe"
$jarSrc = "build\libs\hey-1.0.jar"
# The class-name obfuscator emits a fresh, random old->new mapping on every
# build; rename-mapping.txt is the ONLY way to de-obfuscate a stack trace, so
# ship it with the artifacts/release.
$mapSrc = "build\rename-mapping.txt"
if (-not (Test-Path $exeSrc)) { throw "missing $exeSrc" }
if (-not (Test-Path $jarSrc)) { throw "missing $jarSrc" }
if (-not (Test-Path $mapSrc)) { throw "missing $mapSrc" }
$release = "build\release"
New-Item -ItemType Directory -Force -Path $release | Out-Null
$exeDst = Join-Path $release "OpenZenLoader-$sha.exe"
$jarDst = Join-Path $release "OpenZen-$sha.jar"
$mapDst = Join-Path $release "OpenZen-$sha-mapping.txt"
Copy-Item -Force $exeSrc $exeDst
Copy-Item -Force $jarSrc $jarDst
Copy-Item -Force $mapSrc $mapDst
$exeSz = (Get-Item $exeDst).Length
$jarSz = (Get-Item $jarDst).Length
Write-Host ("OpenZenLoader-{0}.exe : {1:N0} bytes ({2:N2} MB)" -f $sha, $exeSz, ($exeSz/1MB))
Write-Host ("OpenZen-{0}.jar : {1:N0} bytes ({2:N2} MB)" -f $sha, $jarSz, ($jarSz/1MB))
Write-Host ("OpenZen-{0}-mapping.txt : {1:N0} bytes" -f $sha, (Get-Item $mapDst).Length)
# NOTE: actions/upload-artifact always wraps its content in a zip; that
# is a platform limitation we cannot disable. By giving each artifact a
# single file whose name already encodes the sha, the download is
# OpenZenLoader-<sha>.exe.zip / OpenZen-<sha>.jar.zip, each containing
# just the named file (no nested directory). For raw exe/jar downloads
# without the zip wrapper, attach to a GitHub Release instead.
- name: Upload OpenZenLoader exe
uses: actions/upload-artifact@v4
with:
name: OpenZenLoader-${{ steps.rev.outputs.sha }}.exe
path: build/release/OpenZenLoader-${{ steps.rev.outputs.sha }}.exe
if-no-files-found: error
retention-days: 30
- name: Upload OpenZen jar
uses: actions/upload-artifact@v4
with:
name: OpenZen-${{ steps.rev.outputs.sha }}.jar
path: build/release/OpenZen-${{ steps.rev.outputs.sha }}.jar
if-no-files-found: error
retention-days: 30
- name: Upload de-obfuscation mapping
uses: actions/upload-artifact@v4
with:
name: OpenZen-${{ steps.rev.outputs.sha }}-mapping.txt
path: build/release/OpenZen-${{ steps.rev.outputs.sha }}-mapping.txt
if-no-files-found: error
retention-days: 30
# ===== Optional GitHub Release publish =====
# If the HEAD commit message contains the literal marker "[Release]",
# cut a GitHub Release tagged build-<sha> and attach the exe + jar.
# Without the marker, this step is skipped — every push still produces
# the Actions artifacts above, only tagged releases are gated.
- name: Detect [Release] marker
id: relmark
shell: pwsh
run: |
$msg = (git log -1 --pretty=%B HEAD | Out-String)
# Case-insensitive match so [release], [Release], [RELEASE] all
# qualify; .Contains in .NET is case-sensitive by default.
$isRelease = $msg.IndexOf("[Release]", [System.StringComparison]::OrdinalIgnoreCase) -ge 0
"is_release=$($isRelease.ToString().ToLower())" |
Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
Write-Host "HEAD commit message:"
Write-Host $msg
Write-Host "[Release] marker present: $isRelease"
- name: Publish GitHub Release
if: steps.relmark.outputs.is_release == 'true'
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SHA: ${{ steps.rev.outputs.sha }}
run: |
$tag = "build-$env:SHA"
$title = "Build $env:SHA"
# Write notes via a file so quoting / [brackets] / newlines in the
# commit message can't corrupt the gh command line.
$notes = "release-notes.md"
# Prepend a PRE-BUILT warning to the release body: these artifacts all share
# one fixed obfuscation mapping, so an anti-cheat class-name blacklist can
# target them. Tell users to self-compile for unique, per-build random names.
# Build the banner as a string array (one line each) to avoid PowerShell
# here-string column-0 terminator issues inside a YAML block scalar.
$warn = @(
'> ⚠️ **这是预构建版本(PRE-BUILT)**'
'>'
'> 本 Release 里的 `OpenZenLoader.exe` / `OpenZen-*.jar` 是 GitHub Actions 编译的成品,**所有人下载到的是同一套混淆类名**。这套固定的名字随时可能被反作弊(如布吉岛)收录进**类名黑名单**而失效。'
'>'
'> 想要一套**独一无二、别人都不知道**的类名,请**自己编译**(每次构建都会生成全新随机类名):'
'> - **Fork 本仓库**,在你自己的 GitHub Actions 里跑 `Build Loader` 工作流,下载你自己的 artifact;**或**'
'> - **clone 到本地**自己 `gradlew jar` / `gradlew dll`。'
'>'
'> 详见仓库 README 的「编译时类名混淆」。`OpenZen-*-mapping.txt` 是本次构建的反混淆映射(每次构建都不同)。'
''
'---'
''
)
$warn | Out-File -FilePath $notes -Encoding utf8
git log -1 --pretty=%B HEAD | Out-File -FilePath $notes -Encoding utf8 -Append
gh release create $tag `
--title $title `
--notes-file $notes `
"build/release/OpenZenLoader-$env:SHA.exe" `
"build/release/OpenZen-$env:SHA.jar" `
"build/release/OpenZen-$env:SHA-mapping.txt"