[CI] Give the Windows leg a compiler cache (#7797)

ccache drives MSVC since 4.7, but it rejects /Zi and /ZI because the debug
information lives in a shared PDB it cannot restore. /Z7 puts it in the
object file instead, which is what makes the compilation cacheable and
also removes the shared PDB as a serialisation point on a 4-core runner.

Allow ccache on Windows in CMake, install it with Chocolatey, and persist
it under ccache-Windows10 the same way the Linux legs do.
This commit is contained in:
BruebachL 2026-11-07 20:58:28 +01:00 • committed by GitHub
parent 3931447729
commit ee66e73b2c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 70 additions and 21 deletions

View file

@ -248,20 +248,23 @@ jobs:
# Debian 13 Release + suite 625 MB 800M # Debian 13 Release + suite 625 MB 800M
# Fedora 45 debug Debug + suite 660 MB 850M # Fedora 45 debug Debug + suite 660 MB 850M
# Fedora 45 release Release + package 614 MB 800M # Fedora 45 release Release + package 614 MB 800M
# Windows 10 Release + package 1.2 GB working set 1300M
# #
# That is 3.3 GB of bounds, and with the fixed entries above (about 4.4 GB) it puts # That is 4.6 GB of bounds; what lands in the quota is 4.3 GB of compiler cache,
# the quota at roughly 7.7 GB of 10 - so only legs worth their share get one. A leg # 2.97 GB stored by the Linux legs and 1.30 GB by Windows, for 8.4 GB of 10 in total.
# keeps no cache when it duplicates another leg's coverage (Ubuntu 26.04 behind # So only legs worth their share get one. A leg keeps no cache when it duplicates
# Debian 13's Release + suite; the macOS legs behind the Linux ones) or finishes well # another leg's coverage (Ubuntu 26.04 behind Debian 13's Release + suite; the macOS
# inside the Windows leg (Servatrice covers the server alone in 4.7 min). A macOS # legs behind the Linux ones) or finishes well inside the Windows leg (Servatrice
# covers the server alone in 4.7 min). A macOS
# cache served 42% and saved about 32s on a 20 min leg that is already shorter than # cache served 42% and saved about 32s on a 20 min leg that is already shorter than
# Windows, so that bound is better spent here. The four Linux legs that stay compile # Windows, so that bound is better spent here. The four Linux legs that stay compile
# the client on the toolchains a change can break and are the ones a cache shortens # the client on the toolchains a change can break and are the ones a cache shortens
# by the most. # by the most.
# Measured job totals on master 2026-11-04 also show the ceiling this leaves: # Measured job totals on master 2026-11-04 also show the ceiling this leaves:
# Windows 10 is 29.5 min and compiles for 23.7 of it with no compiler cache at all, so # Windows 10 is 29.5 min and compiles for 23.7 of it, so Windows sets the
# Windows still sets the pull-request wall clock no matter how warm these legs are # pull-request wall clock no matter how warm these legs are. That is why the Windows
# (see build-vcpkg). # leg is the one macOS's share went to (see build-vcpkg): MSVC has no -g level to
# shrink, and with /Z7 the whole configuration is cacheable, PCH included.
# #
# Steady-state master data corrected this on 2026-11-07: Arch 90.1% and Debian 13 # Steady-state master data corrected this on 2026-11-07: Arch 90.1% and Debian 13
# 96.75% per-run hits at the 850M/800M bounds, Fedora 45 debug 99.18% and release # 96.75% per-run hits at the 850M/800M bounds, Fedora 45 debug 99.18% and release
@ -676,6 +679,9 @@ jobs:
qt_version: 6.11.* qt_version: 6.11.*
qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
type: Release type: Release
ccache_eviction_age: 7d
ccache_size: 1300M # Release plus the package: 1.2 GB working set at /Z7
use_ccache: 1
# Scope is named after the vs2026 toolchain, not the generator: the # Scope is named after the vs2026 toolchain, not the generator: the
# ABI hash covers cl.exe, which is identical, so this leg keeps # ABI hash covers cl.exe, which is identical, so this leg keeps
# restoring the warm archives. Renaming would orphan them. # restoring the warm archives. Renaming would orphan them.
@ -689,8 +695,13 @@ jobs:
runs-on: ${{ matrix.runner }} runs-on: ${{ matrix.runner }}
timeout-minutes: 100 timeout-minutes: 100
env: env:
# No leg in this job keeps a compiler cache: neither the macOS legs nor the Windows # Only the Windows leg keeps a compiler cache; the macOS legs build without one
# leg can fill one across runs. See the note on the removed macOS ccache steps below. # (see the note where their ccache steps used to be). ccache drives MSVC as long as
# the compiler emits /Z7, which CMakeLists.txt sets for exactly this reason, and it
# caches the PCH-backed targets with the sloppiness that .ci/compile.sh re-asserts.
CCACHE_DIR: ${{ github.workspace }}/.cache/
CCACHE_SIZE: ${{ matrix.ccache_size || '700M' }}
PERSIST_CCACHE: ${{ matrix.persist_ccache || 'true' }}
# MSVC has no -g level, so the Windows leg does not ask for one. # MSVC has no -g level, so the Windows leg does not ask for one.
COCKATRICE_DEBUG_INFO_LEVEL: ${{ matrix.os != 'Windows' && github.ref_type != 'tag' && '1' || '' }} COCKATRICE_DEBUG_INFO_LEVEL: ${{ matrix.os != 'Windows' && github.ref_type != 'tag' && '1' || '' }}
@ -707,6 +718,19 @@ jobs:
with: with:
msbuild-architecture: x64 msbuild-architecture: x64
- name: "[Windows] Setup ccache"
if: matrix.os == 'Windows' && matrix.use_ccache == 1
shell: bash
run: choco install ccache --no-progress -y
- name: "[Windows] Restore compiler cache (ccache)"
if: matrix.os == 'Windows' && matrix.use_ccache == 1 && env.PERSIST_CCACHE == 'true'
id: restore_ccache
uses: actions/cache/restore@v6
with:
key: ccache-${{ matrix.os }}${{ matrix.target }}
path: ${{ env.CCACHE_DIR }}
# No macOS leg runs ccache. A restored macOS cache served 42% of calls and saved # No macOS leg runs ccache. A restored macOS cache served 42% of calls and saved
# about 32s on a 20 min leg that is already shorter than the Windows leg, which did # about 32s on a 20 min leg that is already shorter than the Windows leg, which did
# not pay for its 800M slice of the quota; with nothing restored, the remaining # not pay for its 800M slice of the quota; with nothing restored, the remaining
@ -820,6 +844,7 @@ jobs:
shell: bash shell: bash
env: env:
BUILDTYPE: '${{ matrix.type }}' BUILDTYPE: '${{ matrix.type }}'
CCACHE_EVICTION_AGE: ${{ matrix.ccache_eviction_age }}
CMAKE_GENERATOR: ${{ matrix.cmake_generator }} CMAKE_GENERATOR: ${{ matrix.cmake_generator }}
# Tag builds only: artifacts from master pushes and PR branches must not report to # Tag builds only: artifacts from master pushes and PR branches must not report to
# Sentry. Forks get no secrets on pull_request, so they keep the mock fallback either way. # Sentry. Forks get no secrets on pull_request, so they keep the mock fallback either way.
@ -834,6 +859,7 @@ jobs:
MAKE_PACKAGE: '${{ matrix.make_package }}' MAKE_PACKAGE: '${{ matrix.make_package }}'
PACKAGE_SUFFIX: '${{ matrix.package_suffix }}' PACKAGE_SUFFIX: '${{ matrix.package_suffix }}'
TARGET_MACOS_VERSION: ${{ matrix.override_target }} TARGET_MACOS_VERSION: ${{ matrix.override_target }}
USE_CCACHE: ${{ matrix.use_ccache }}
# Only pushes to the default branch may write the shared binary cache: the # Only pushes to the default branch may write the shared binary cache: the
# archives a run compiles are only as trustworthy as the code they were # archives a run compiles are only as trustworthy as the code they were
# built from, and only merged code may populate the scope that master and # built from, and only merged code may populate the scope that master and
@ -1034,6 +1060,27 @@ jobs:
path: ${{ runner.temp }}/vcpkg-new path: ${{ runner.temp }}/vcpkg-new
retention-days: 1 retention-days: 1
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342
- name: "[Windows] Delete remote compiler cache (ccache)"
if: matrix.os == 'Windows' && matrix.use_ccache == 1 && env.PERSIST_CCACHE == 'true' && github.ref == 'refs/heads/master' && steps.restore_ccache.outputs.cache-hit
continue-on-error: true
# Bash, not the runner's default pwsh, which cannot parse this `if`.
shell: bash
env:
CACHE_PRIMARY_KEY: ${{ steps.restore_ccache.outputs.cache-primary-key }}
GH_TOKEN: ${{ github.token }}
run: |
if gh cache delete --repo "$GITHUB_REPOSITORY" "$CACHE_PRIMARY_KEY"; then
echo "Cache deleted successfully"
fi
- name: "[Windows] Cache updated compiler cache (ccache)"
if: matrix.os == 'Windows' && matrix.use_ccache == 1 && env.PERSIST_CCACHE == 'true' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v6
with:
key: ${{ steps.restore_ccache.outputs.cache-primary-key }}
path: ${{ env.CCACHE_DIR }}
- name: "[macOS] Sign app bundle" - name: "[macOS] Sign app bundle"
if: matrix.os == 'macOS' && matrix.make_package && needs.configure.outputs.tag != null if: matrix.os == 'macOS' && matrix.make_package && needs.configure.outputs.tag != null
id: sign_macos id: sign_macos
@ -1222,7 +1269,7 @@ jobs:
# The nightly legs and the pull-request legs that finish under the Windows leg # The nightly legs and the pull-request legs that finish under the Windows leg
# keep none, so their leftover entries are reclaimed here instead of waiting out # keep none, so their leftover entries are reclaimed here instead of waiting out
# the age rule. # the age rule.
LIVE_CCACHE_KEYS: 'ccache-Arch ccache-Debian13 ccache-Fedora45debug ccache-Fedora45release' LIVE_CCACHE_KEYS: 'ccache-Arch ccache-Debian13 ccache-Fedora45debug ccache-Fedora45release ccache-Windows10'
run: | run: |
gh api --paginate "repos/$GITHUB_REPOSITORY/actions/caches?per_page=100" \ gh api --paginate "repos/$GITHUB_REPOSITORY/actions/caches?per_page=100" \
--jq '.actions_caches[] | [.id, .ref, .key, .last_accessed_at] | @tsv' >"$RUNNER_TEMP/caches.tsv" --jq '.actions_caches[] | [.id, .ref, .key, .last_accessed_at] | @tsv' >"$RUNNER_TEMP/caches.tsv"

View file

@ -48,11 +48,12 @@ if(NOT CMAKE_BUILD_TYPE)
) )
endif() endif()
# ccache does not support MSVC and must not auto-engage on Windows # ccache drives MSVC too (since 4.7), but only when the compiler writes debug
# (it is installed unintentionally on the Windows CI runner). # information into the object file: /Zi and /ZI keep it in a shared PDB, and ccache
# NOTE: this keys off the target OS, so a mingw/Ninja configuration on Windows # reports them as unsupported rather than risk a stale one. The MSVC flags below
# also opts out of ccache even though the GNUCXX branch below supports it. # therefore use /Z7. MSVC precompiled headers are cached as well, with the same
if(USE_CCACHE AND NOT WIN32) # sloppiness the other toolchains need.
if(USE_CCACHE)
find_program(CCACHE_PROGRAM ccache) find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM) if(CCACHE_PROGRAM)
# Support Unix Makefiles and Ninja # Support Unix Makefiles and Ninja
@ -66,10 +67,10 @@ if(USE_CCACHE AND NOT WIN32)
# check for every project on the machine, not just this one. # check for every project on the machine, not just this one.
execute_process(COMMAND ${CCACHE_PROGRAM} --set-config sloppiness=pch_defines,time_macros) execute_process(COMMAND ${CCACHE_PROGRAM} --set-config sloppiness=pch_defines,time_macros)
message(STATUS "Found CCache ${CCACHE_PROGRAM}") message(STATUS "Found CCache ${CCACHE_PROGRAM}")
else()
# An explicit opt-in must not disappear silently.
message(STATUS "ccache requested but not found; building without a compiler cache")
endif() endif()
elseif(USE_CCACHE AND WIN32)
# An explicit opt-in must not disappear silently on Windows.
message(STATUS "ccache disabled: not supported for the MSVC toolchain on Windows")
endif() endif()
if(WIN32 OR USE_VCPKG) if(WIN32 OR USE_VCPKG)
@ -174,8 +175,9 @@ if(MSVC) # Microsoft Visual C++ compiler
# /utf-8 Set source file encoding and execution character set to UTF-8 # /utf-8 Set source file encoding and execution character set to UTF-8
# /W4 Enable warning level 4 # /W4 Enable warning level 4
# /Zc:__cplusplus Enable correct __cplusplus reporting # /Zc:__cplusplus Enable correct __cplusplus reporting
# /Zi Generate debugging information (Program Database, PDB) # /Z7 Generate debugging information in each object file (ccache-cacheable)
set(CMAKE_CXX_FLAGS "/EHsc /MP /permissive- /utf-8 /W4 /Zc:__cplusplus /Zi") # instead of a compiler-shared PDB, which ccache rejects
set(CMAKE_CXX_FLAGS "/EHsc /MP /permissive- /utf-8 /W4 /Zc:__cplusplus /Z7")
# /O2 Balanced optimization # /O2 Balanced optimization
# /MD Link against the multi-threaded DLL runtime library (Release CRT) # /MD Link against the multi-threaded DLL runtime library (Release CRT)
set(CMAKE_CXX_FLAGS_RELEASE "/O2 /MD") set(CMAKE_CXX_FLAGS_RELEASE "/O2 /MD")