sets the name of the build dir, default is "build"
# --cmake-generator sets CMAKE_GENERATOR as used by cmake
# --target-macos-version sets the min os version - only used for macOS builds
-# --profile writes an MSBuild performance summary and binary log, Windows only
-# uses env: BUILDTYPE MAKE_INSTALL MAKE_PACKAGE PACKAGE_TYPE PACKAGE_SUFFIX MAKE_SERVER MAKE_NO_CLIENT MAKE_TEST USE_CCACHE CCACHE_SIZE CCACHE_EVICTION_AGE BUILD_DIR CMAKE_GENERATOR TARGET_MACOS_VERSION MAKE_SANITIZE MAKE_COVERAGE VCPKG_TARGET_MACOS_VERSION BUILD_PARALLEL_LEVEL BUILD_PROFILE
-# (correspond to args: --debug/--release --install --package --suffix --server --test --sanitize --coverage --ccache --dir --profile)
+# uses env: BUILDTYPE MAKE_INSTALL MAKE_PACKAGE PACKAGE_TYPE PACKAGE_SUFFIX MAKE_SERVER MAKE_NO_CLIENT MAKE_TEST USE_CCACHE CCACHE_SIZE CCACHE_EVICTION_AGE BUILD_DIR CMAKE_GENERATOR TARGET_MACOS_VERSION MAKE_SANITIZE MAKE_COVERAGE
+# (correspond to args: --debug/--release --install --package --suffix --server --test --sanitize --coverage --ccache --dir )
# exitcode: 1 for failure, 3 for invalid arguments
# Read arguments
@@ -116,10 +115,6 @@ while [[ $# != 0 ]]; do
export CMAKE_GENERATOR=$1
shift
;;
- '--profile')
- BUILD_PROFILE=1
- shift
- ;;
'--target-macos-version')
shift
if [[ $# == 0 ]]; then
@@ -175,9 +170,9 @@ fi
if [[ $MAKE_SANITIZE ]]; then
flags+=("-DENABLE_SANITIZERS=ON")
else
- # Every leg states the flag explicitly rather than relying on the CMake
- # default, so a build directory reused from a sanitized configuration can
- # never leak libasan/libubsan into a shipped binary.
+ # The release-package step re-runs cmake in the same build dir, so an
+ # ENABLE_SANITIZERS=ON left over from the debug step would otherwise leak
+ # into the shipped binaries (libasan/libubsan Depends in the DEB).
flags+=("-DENABLE_SANITIZERS=OFF")
fi
if [[ $MAKE_COVERAGE ]]; then
@@ -185,6 +180,9 @@ if [[ $MAKE_COVERAGE ]]; then
fi
if [[ $USE_CCACHE ]]; then
flags+=("-DUSE_CCACHE=1")
+ # PCH-aware caching is required or ccache refuses to cache any TU that
+ # consumes a precompiled header, silently recompiling everything on every run.
+ ccache --set-config sloppiness=pch_defines,time_macros
if [[ $CCACHE_SIZE ]]; then
# note, this setting persists after running the script
ccache --max-size "$CCACHE_SIZE"
@@ -197,25 +195,6 @@ if [[ $USE_VCPKG ]]; then
flags+=("-DUSE_VCPKG=1")
flags+=("-DVCPKG_INSTALL_OPTIONS=--x-abi-tools-use-exact-versions")
fi
-if [[ ${WITH_CRASH_REPORTING:-} == "ON" ]]; then
- # Release CI opts into the sentry-native SDK (default is OFF so from-source, distro and
- # offline package builds never fetch it). Debug builds stay SDK-free so crashes from
- # developer builds never reach Sentry.
- flags+=("-DWITH_CRASH_REPORTING=ON")
-fi
-if [[ ${COCKATRICE_DEBUG_INFO_LEVEL:-} ]]; then
- # CI lowers the DWARF level so one build configuration fits its compiler-cache bound;
- # see the option in CMakeLists.txt. Empty keeps the built-in flags.
- flags+=("-DCOCKATRICE_DEBUG_INFO_LEVEL=$COCKATRICE_DEBUG_INFO_LEVEL")
-fi
-if [[ ${BUILDTYPE:-Release} == "Release" && -n ${COCKATRICE_SENTRY_DSN:-} ]]; then
- # Release-only: Debug builds stay DSN-free so crashes from developer builds
- # never reach Sentry. Empty (local builds, forks without the secret, and non-tag
- # CI builds, which never receive the secret) keeps today's mock fallback via the
- # compiled-in default. CI passes the value from the SENTRY_DSN secret on tag
- # Release builds only, so pre-release test artifacts never report to Sentry.
- flags+=("-DCOCKATRICE_SENTRY_DSN=$COCKATRICE_SENTRY_DSN")
-fi
# Add cmake --build flags
buildflags=(--config "$BUILDTYPE")
@@ -230,22 +209,6 @@ function ccachestatsverbose() {
fi
}
-function cpuinfo() {
- # The number of cores decides how much build parallelism is useful; the hosted
- # runners are not all sized alike, so read it off the machine instead of assuming.
- case "$RUNNER_OS" in
- Windows)
- echo "cores: ${NUMBER_OF_PROCESSORS:-unknown}"
- ;;
- macOS)
- echo "cores: $(sysctl -n hw.ncpu)"
- ;;
- *)
- echo "cores: $(nproc 2>/dev/null || echo unknown)"
- ;;
- esac
-}
-
# Compile
if [[ $RUNNER_OS == macOS ]]; then
# QTDIR is needed for macOS since we actually only use the cached thin Qt binaries instead of the install-qt-action,
@@ -280,14 +243,8 @@ if [[ $RUNNER_OS == macOS ]]; then
triplet_source="../vcpkg/triplets/community/$arch-osx.cmake"
fi
cp "$triplet_source" "$triplet_file"
- # The triplet is what vcpkg hashes into the ABI hash of every port, so two legs that
- # agree here reuse each other's binary cache. Targets 14, 15 and 26 on the same Xcode
- # produce identical binaries when they share the lowest deployment target, so they all
- # build against 14 to keep one vcpkg cache scope instead of three.
- # Keep in sync with "vcpkg_scope" and "vcpkg_deployment_target" in desktop-build.yml.
- vcpkg_target="${VCPKG_TARGET_MACOS_VERSION:-$TARGET_MACOS_VERSION}"
- echo "set(VCPKG_CMAKE_SYSTEM_VERSION $vcpkg_target)" >>"$triplet_file"
- echo "set(VCPKG_OSX_DEPLOYMENT_TARGET $vcpkg_target)" >>"$triplet_file"
+ echo "set(VCPKG_CMAKE_SYSTEM_VERSION $TARGET_MACOS_VERSION)" >>"$triplet_file"
+ echo "set(VCPKG_OSX_DEPLOYMENT_TARGET $TARGET_MACOS_VERSION)" >>"$triplet_file"
flags+=("-DVCPKG_OVERLAY_TRIPLETS=$triplets_dir")
flags+=("-DVCPKG_HOST_TRIPLET=$triplet_version")
flags+=("-DVCPKG_TARGET_TRIPLET=$triplet_version")
@@ -313,24 +270,16 @@ if [[ $RUNNER_OS == macOS ]]; then
if [[ $MAKE_PACKAGE ]]; then
# Workaround https://github.com/actions/runner-images/issues/7522
- # have hdiutil repeat the command 10 times in hope of success.
- # The wrapper has to exit non-zero once it gives up: CPack reads that status to
- # decide whether the disk image was created, so a zero exit turns a failed hdiutil
- # into a much later, misleading "Problem copying the package" error that hides
- # hdiutil's own message.
+ # have hdiutil repeat the command 10 times in hope of success
hdiutil_script="/tmp/hdiutil.sh"
# shellcheck disable=SC2016
echo '#!/bin/bash
- attempts=10
i=0
- while true; do
- hdiutil "$@" && exit 0
- rc=$?
- if (( ++i >= attempts )); then
- echo "::error::hdiutil $1 failed $i times (last status $rc), giving up" >&2
- exit "$rc"
+ while ! hdiutil "$@"; do
+ if (( ++i >= 10 )); then
+ echo "Error: hdiutil failed $i times!" >&2
+ break
fi
- echo "hdiutil $1 attempt $i/$attempts failed (status $rc), retrying" >&2
sleep 1
done' >"$hdiutil_script"
chmod +x "$hdiutil_script"
@@ -338,34 +287,9 @@ if [[ $RUNNER_OS == macOS ]]; then
fi
elif [[ $RUNNER_OS == Windows ]]; then
- if [[ $CMAKE_GENERATOR == "Visual Studio"* ]]; then
- # cmake only hands MSBuild a job count when one is asked for, and without it
- # MSBuild builds a single node: one project at a time, whatever MTT does inside
- # it. Ask for a node per core.
- if [[ ! $BUILD_PARALLEL_LEVEL ]]; then
- BUILD_PARALLEL_LEVEL="${NUMBER_OF_PROCESSORS:-1}"
- fi
- buildflags+=(--parallel "$BUILD_PARALLEL_LEVEL")
-
- # Enable MTT, see https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/
- # and https://devblogs.microsoft.com/cppblog/cpp-build-throughput-investigation-and-tune-up/#multitooltask-mtt
- # EnforceProcessCountAcrossBuilds makes the CL_MPCount ceiling apply across all
- # the projects the nodes are building, instead of each node running its own
- # cl.exe processes and /m and MTT multiplying into an oversubscribed machine.
- buildflags+=(-- -p:UseMultiToolTask=true -p:EnableClServerMode=true -p:EnforceProcessCountAcrossBuilds=true -p:CL_MPCount="$BUILD_PARALLEL_LEVEL")
- if [[ $BUILD_PROFILE ]]; then
- # A performance summary says which projects and tasks cost what, the binary log
- # keeps the full timeline; both are far too noisy to always produce. The files
- # land next to the solution, i.e. in the build dir the workflow uploads. These
- # join the MTT options above, which already carried the "--" separator.
- buildflags+=("-flp:PerformanceSummary;v=q;LogFile=msbuild-perf.log" -bl:msbuild.binlog)
- fi
- else
- # Ninja runs the compiler itself, so it neither takes the MSBuild switches
- # (it rejects -p: options outright) nor a job count: it sizes its own pool
- # for the cores it finds.
- echo "ninja $(ninja --version)"
- fi
+ # Enable MTT, see https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/
+ # and https://devblogs.microsoft.com/cppblog/cpp-build-throughput-investigation-and-tune-up/#multitooltask-mtt
+ buildflags+=(-- -p:UseMultiToolTask=true -p:EnableClServerMode=true)
fi
if [[ $USE_CCACHE ]]; then
@@ -380,35 +304,11 @@ echo "Running cmake with flags: ${flags[*]}"
cmake .. "${flags[@]}"
echo "::endgroup::"
-if [[ $USE_CCACHE ]]; then
- # PCH-aware caching is required or ccache refuses to cache any TU that consumes a
- # precompiled header, silently recompiling everything on every run. The mtime and ctime
- # entries cover files created during the build itself: the moc/uic/rcc output and the
- # protobuf headers are generated after ccache starts, so without them every TU including
- # one is read as a race and never cached. Checked-out sources are older than the compile
- # that follows them and do not trip the check.
- #
- # This has to run after configure: CMakeLists.txt sets sloppiness=pch_defines,time_macros
- # at configure time, and --set-config replaces the whole list rather than extending it, so
- # setting it earlier would leave the build below with two entries. Re-asserting all four
- # here is what actually reaches the compiler; the order is the point.
- ccache --set-config sloppiness=pch_defines,time_macros,include_file_mtime,include_file_ctime
-fi
-
echo "::group::Build project"
-cpuinfo
echo "Running cmake --build with flags: ${buildflags[*]}"
cmake --build . "${buildflags[@]}"
echo "::endgroup::"
-if [[ $BUILD_PROFILE && $RUNNER_OS == Windows && -f msbuild-perf.log ]]; then
- # Repeat the summary in the job log so the numbers are readable without
- # downloading the binary log, which is hundreds of megabytes.
- echo "::group::MSBuild performance summary"
- cat msbuild-perf.log
- echo "::endgroup::"
-fi
-
if [[ $USE_CCACHE ]]; then
if [[ $CCACHE_EVICTION_AGE ]]; then
echo "::group::evict ccache files older than $CCACHE_EVICTION_AGE"
@@ -484,206 +384,6 @@ if [[ $MAKE_COVERAGE ]]; then
echo "::endgroup::"
fi
-if [[ $RUNNER_OS == Windows && ! $MAKE_NO_CLIENT ]]; then
- echo "::group::Probe the installer's holder lookup"
- # The working directory is still the build dir here: the package steps below are what
- # leave and re-enter it, and that is what made a $BUILD_DIR-prefixed path name
- # build/build. Both paths are relative to the dir the probe was built into.
- probe="./installer-holder-probe.exe"
- if [[ ! -f $probe ]]; then
- echo "::error file=$0::installer-holder-probe.exe was not built"
- exit 1
- fi
- # The probe exists to name a holder that a process-and-module scan cannot see: one
- # that has a file open without having loaded it. Holding a temporary DLL with
- # FileShare.None is exactly that case, so this asserts the reason the probe was
- # added instead of only that the Restart Manager answers at all. It needs no NSIS
- # and no packaging, so it runs on every Windows build that gets this far.
- # The holder is a process of its own: the probe never names the process that started it,
- # which is what keeps an in-place uninstaller from being named as a holder of the
- # directory it is emptying. The same DLL held by the script that starts the probe
- # therefore has to come back as nothing, and that is the second case below.
- probe_check="./holder-probe-check.ps1"
- cat >"$probe_check" <<'POWERSHELL'
-$ErrorActionPreference = "Stop"
-$dir = Join-Path ([IO.Path]::GetTempPath()) "cockatrice-holder-probe-$PID"
-Remove-Item -Recurse -Force $dir -ErrorAction SilentlyContinue
-New-Item -ItemType Directory -Force $dir | Out-Null
-$probe = (Resolve-Path -LiteralPath $env:PROBE_EXE).Path
-
-function Invoke-Probe([string] $guardDir) {
- $start = New-Object System.Diagnostics.ProcessStartInfo
- $start.FileName = $probe
- $start.UseShellExecute = $false
- $start.RedirectStandardOutput = $true
- $start.EnvironmentVariables["COCKATRICE_GUARD_DIR"] = $guardDir
- $process = [System.Diagnostics.Process]::Start($start)
- $read = $process.StandardOutput.ReadToEndAsync()
- if (-not $process.WaitForExit(20000)) {
- try { $process.Kill() } catch { }
- throw "the holder probe did not finish within 20 s for $guardDir"
- }
- $output = $read.Result
- $code = $process.ExitCode
- $process.Dispose()
- return @{ Code = $code; Output = $output }
-}
-
-function Wait-UntilLocked([string] $path) {
- $deadline = (Get-Date).AddSeconds(15)
- while ((Get-Date) -lt $deadline) {
- try {
- $open = [IO.File]::Open($path, "Open", "ReadWrite", "None")
- $open.Dispose()
- } catch {
- return
- }
- Start-Sleep -Milliseconds 100
- }
- throw "the holder never locked $path"
-}
-
-try {
- $file = Join-Path $dir "locked.dll"
- [IO.File]::WriteAllText($file, "x")
-
- # The holder is a process of its own, not this script: the probe never names the process
- # that started it (the case below asserts that), so a file held here would be left out on
- # purpose and the check would pass for the wrong reason.
- $holderScript = Join-Path $dir "hold.ps1"
- Set-Content -LiteralPath $holderScript -Value @'
-param([string] $Path)
-$handle = [IO.File]::Open($Path, "Open", "ReadWrite", "None")
-Start-Sleep -Seconds 120
-'@
- $powershell = Join-Path $env:SystemRoot "System32\WindowsPowerShell\v1.0\powershell.exe"
- $holder = Start-Process -FilePath $powershell -PassThru -ArgumentList @(
- "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "`"$holderScript`"", "-Path", "`"$file`"")
- try {
- Wait-UntilLocked $file
- $locked = Invoke-Probe $dir
- if ($locked.Code -ne 1) { throw "expected exit 1 for a locked file, got $($locked.Code) : $($locked.Output)" }
- if ($locked.Output -notmatch "pid\s+$($holder.Id)\b") {
- throw "the lookup did not name the process holding the file: $($locked.Output)"
- }
- } finally {
- try { $holder.Kill() } catch { }
- try { $holder.WaitForExit(5000) | Out-Null } catch { }
- }
-
- # The probe is started by the installer, or by an uninstaller that runs in place from the
- # directory it is emptying, so the asking process holds files there itself. Naming it would
- # ask the user to close the program that is asking - a question neither the close nor the
- # end offer can answer, so the guard repeated it until the update was cancelled. A holder
- # that is only the asking process has to come back as nothing.
- $handle = [IO.File]::Open($file, "Open", "ReadWrite", "None")
- try {
- $self = Invoke-Probe $dir
- } finally {
- $handle.Dispose()
- }
- if ($self.Code -ne 0) { throw "expected exit 0 when the asking process is the only holder, got $($self.Code) : $($self.Output)" }
- if ($self.Output -match "pid\s+$PID\b") { throw "the lookup named the process that started it: $($self.Output)" }
-
- # A payload executable that refuses to be replaced is running, whether or not this probe
- # may name it: the Restart Manager does not report every mapping, and the image path of a
- # process under another account cannot be read from here, so both process lookups can
- # come back empty while the program is still there. Held here by the asking process, so
- # that it cannot be named - the answer has to be "could not name it", never "nothing is
- # holding it", which is what sent an update into an uninstall that could only fail
- # halfway. Exit 3 is the code the guard answers by asking the user to close it and retry.
- $image = Join-Path $dir "oracle.exe"
- [IO.File]::WriteAllText($image, "x")
- $handle = [IO.File]::Open($image, "Open", "ReadWrite", "None")
- try {
- $unnameable = Invoke-Probe $dir
- } finally {
- $handle.Dispose()
- }
- if ($unnameable.Code -ne 3) {
- throw "expected exit 3 for a payload executable that is in use but cannot be named, got $($unnameable.Code) : $($unnameable.Output)"
- }
- if ($unnameable.Output -notmatch "oracle\.exe is in use") {
- throw "the answer did not name the executable it could not replace: $($unnameable.Output)"
- }
-
- $empty = Join-Path $dir "empty"
- New-Item -ItemType Directory -Force $empty | Out-Null
- $nothing = Invoke-Probe $empty
- if ($nothing.Code -ne 0) { throw "expected exit 0 for a directory with nothing open, got $($nothing.Code) : $($nothing.Output)" }
- # A process running from the directory is a holder even when it has no file open: an
- # instance holds its payload as mapped images, not as handles, and that is the state
- # the guard used to miss - it passed, the purge failed, and the install ended in a
- # warning with no offer to close and try again. The image is a copy of a native
- # Windows program, so it starts without its usual directory next to it, and it carries
- # one of the payload's own names because that is what the lookup names.
- $copy = Join-Path $dir "cockatrice.exe"
- Copy-Item -LiteralPath (Join-Path $env:SystemRoot "System32\cmd.exe") -Destination $copy
- $running = Start-Process -FilePath $copy -ArgumentList "/c", "ping -n 20 127.0.0.1 >nul" -PassThru
- try {
- Start-Sleep -Milliseconds 1500
- $byImage = Invoke-Probe $dir
- } finally {
- try { $running.Kill() } catch { }
- # Wait for the image to be released, or the cleanup below leaves the copy behind.
- try { $running.WaitForExit(5000) | Out-Null } catch { }
- }
- if ($byImage.Code -ne 1) { throw "expected exit 1 for a process running from the directory, got $($byImage.Code) : $($byImage.Output)" }
- if ($byImage.Output -notmatch "cockatrice\.exe \(pid\s+$($running.Id)") {
- throw "the lookup did not name the process running from the directory: $($byImage.Output)"
- }
-
- # A program in a folder below the directory is not an instance of the installation, even
- # when it carries a payload program's name: the install directory can be a folder the
- # user keeps other applications in, each in its own subfolder, and naming one of those
- # asked for a close the probe never sends and an end it refuses to perform, so the guard
- # could only abort the update. A program that really does hold a payload file is still
- # named, by the Restart Manager lookup exercised above.
- $nested = Join-Path $dir "other"
- New-Item -ItemType Directory -Force $nested | Out-Null
- $nestedImage = Join-Path $nested "cockatrice.exe"
- Copy-Item -LiteralPath (Join-Path $env:SystemRoot "System32\cmd.exe") -Destination $nestedImage
- $nestedRunning = Start-Process -FilePath $nestedImage -ArgumentList "/c", "ping -n 20 127.0.0.1 >nul" -PassThru
- try {
- Start-Sleep -Milliseconds 1500
- $inSubfolder = Invoke-Probe $dir
- } finally {
- try { $nestedRunning.Kill() } catch { }
- try { $nestedRunning.WaitForExit(5000) | Out-Null } catch { }
- }
- if ($inSubfolder.Code -ne 0) {
- throw "expected exit 0 for a program in a folder below the directory, got $($inSubfolder.Code) : $($inSubfolder.Output)"
- }
-
- # A program the user keeps in the directory itself is not a payload program either: the
- # guard ends only what the payload installs, and naming anything else asks for an answer
- # that cannot free a single file.
- $foreign = Join-Path $dir "tool.exe"
- Copy-Item -LiteralPath (Join-Path $env:SystemRoot "System32\cmd.exe") -Destination $foreign
- $foreignRunning = Start-Process -FilePath $foreign -ArgumentList "/c", "ping -n 20 127.0.0.1 >nul" -PassThru
- try {
- Start-Sleep -Milliseconds 1500
- $ignored = Invoke-Probe $dir
- } finally {
- try { $foreignRunning.Kill() } catch { }
- try { $foreignRunning.WaitForExit(5000) | Out-Null } catch { }
- }
- if ($ignored.Code -ne 0) {
- throw "expected exit 0 for a program the payload does not install, got $($ignored.Code) : $($ignored.Output)"
- }
- Write-Host "holder lookup named pid $($holder.Id) for a file another process holds, named nothing for a file only the asking process holds, reported a payload executable it could not name as in use instead of free, named nothing for an empty directory, named pid $($running.Id) for a payload program running from the directory, and named nothing for a payload program in a folder below it or for a program the payload does not install"
-} finally {
- Remove-Item -Recurse -Force $dir -ErrorAction SilentlyContinue
-}
-POWERSHELL
- if ! PROBE_EXE="$probe" powershell -NoProfile -File "$probe_check"; then
- rm -f "$probe_check"
- exit 1
- fi
- rm -f "$probe_check"
- echo "::endgroup::"
-fi
-
if [[ $MAKE_INSTALL ]]; then
echo "::group::Install"
cmake --build . --target install --config "$BUILDTYPE"
diff --git a/.ci/docker.sh b/.ci/docker.sh
index c78ba3c31..412ad6a1d 100644
--- a/.ci/docker.sh
+++ b/.ci/docker.sh
@@ -175,17 +175,6 @@ function RUN ()
args+=(--mount "type=bind,source=$GITHUB_OUTPUT,target=/gh_output")
args+=(--env "GITHUB_OUTPUT=/gh_output")
fi
- # The container does not inherit host env vars: without this, WITH_CRASH_REPORTING=ON set in
- # the workflow never reaches compile.sh inside the container, so the Linux packages silently
- # build with crash reporting OFF while the libcurl packages in the Dockerfiles go unused.
- if [[ ${WITH_CRASH_REPORTING:-} ]]; then
- args+=(--env "WITH_CRASH_REPORTING=${WITH_CRASH_REPORTING}")
- fi
- # Same reason: the DWARF level reaches compile.sh only if it is handed to the container,
- # and without it CI would cache objects twice the size its bounds were measured for.
- if [[ ${COCKATRICE_DEBUG_INFO_LEVEL:-} ]]; then
- args+=(--env "COCKATRICE_DEBUG_INFO_LEVEL=${COCKATRICE_DEBUG_INFO_LEVEL}")
- fi
# shellcheck disable=2086
docker run "${args[@]}" $RUN_ARGS "$IMAGE_NAME" bash "$BUILD_SCRIPT" $RUN_OPTS "$@"
return $?
diff --git a/.ci/release_template.md b/.ci/release_template.md
index 8eb4c7ebd..23b475150 100644
--- a/.ci/release_template.md
+++ b/.ci/release_template.md
@@ -21,8 +21,8 @@ Available pre-compiled binaries for installation:
• Ubuntu 24.04 LTSNoble Numbat
• Debian 13Trixie
• Debian 12Bookworm
- • Fedora 45
• Fedora 44
+ • Fedora 43General Linux support is available via a flatpak package hosted at Flathub (x86 & ARM)!Thanks to courtesy of @FFY00, the app is also available in Arch Linux's official extra repository.
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 75494dc3e..75fbc59f1 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -60,10 +60,8 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
- ca-certificates \
cmake \
g++ \
- libcurl4-openssl-dev \
libgl-dev \
liblzma-dev \
libmariadb-dev-compat \
diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml
index b859556aa..098aabc4f 100644
--- a/.github/workflows/desktop-build.yml
+++ b/.github/workflows/desktop-build.yml
@@ -33,18 +33,10 @@ on:
- 'CMakeLists.txt'
- 'vcpkg.json'
- 'vcpkg' # needed to match submodule bumps (gitlink)
- # The legs the pull-request matrix leaves out run here instead, so the legacy distros and
- # the slow macOS runner stay covered without gating a pull request on them. Tags still
- # build every leg, because a tag is what ships.
- schedule:
- - cron: '23 3 * * *'
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on tag --> release)
concurrency:
- # The event name is part of the group because a nightly run and a push run on master share
- # refs/heads/master but must not cancel each other: the nightly would drop the push's vcpkg
- # import, and a merge landing during the nightly would drop that night's coverage.
- group: "${{ github.workflow }} @ ${{ github.ref_name }} (${{ github.event_name }})"
+ group: "${{ github.workflow }} @ ${{ github.ref_name }}"
cancel-in-progress: ${{ github.ref_type != 'tag' }}
jobs:
@@ -54,11 +46,6 @@ jobs:
outputs:
tag: ${{ steps.configure.outputs.tag }}
sha: ${{ steps.configure.outputs.sha }}
- # MAJ.MIN.PATCH[-beta[.N]] of a tag build; empty otherwise. Mirrors what
- # cmake/getversion.cmake derives as PROJECT_VERSION, which the client bakes as its
- # Sentry release (cmake/CrashReporting.cmake) — symbol uploads must use the same
- # string so stack traces de-symbolicate against this build's binaries.
- sentry_release: ${{ steps.configure.outputs.sentry_release }}
steps:
- name: "Configure"
@@ -69,9 +56,6 @@ jobs:
run: |
if [[ "$GITHUB_REF_TYPE" == 'tag' ]]; then # release
echo "tag=$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT"
- # Tag format is YYYY-MM-DD-{Release|Development}-MAJ.MIN.PATCH[-beta[.N]];
- # PROJECT_VERSION is the tail after the type (see cmake/getversion.cmake).
- echo "sentry_release=$(sed -E 's/^[0-9]{4}-[0-9]{2}-[0-9]{2}-(Release|Development)-//' <<< "$GITHUB_REF_NAME")" >> "$GITHUB_OUTPUT"
fi
echo "sha=$RESOLVED_SHA" >> "$GITHUB_OUTPUT"
@@ -109,36 +93,6 @@ jobs:
--title "$release_name" \
--notes-file "$body_path"
- vcpkg-baseline:
- name: vcpkg baseline
- runs-on: ubuntu-slim
- steps:
- - name: "Checkout"
- uses: actions/checkout@v7
-
- # vcpkg-tool reads versions/baseline.json out of the commit named by
- # `builtin-baseline`, through the `vcpkg` submodule's git history. A submodule
- # pointer that lags the manifest - e.g. a stale `vcpkg/` worktree captured by
- # `git add` in a PR - makes every vcpkg leg fail in configure with "failed to
- # `git show` versions/baseline.json". That reads like a baseline or network
- # problem, when the pointer is what has to move. Check the two agree here in
- # seconds instead of on five build legs.
- - name: "Check the submodule pointer against the manifest baseline"
- shell: bash
- run: |
- pointer="$(git rev-parse HEAD:vcpkg 2>/dev/null || true)"
- baseline="$(sed -nE 's/.*"builtin-baseline"[[:space:]]*:[[:space:]]*"([0-9a-f]{40})".*/\1/p' vcpkg.json)"
- if [[ -z "$pointer" || -z "$baseline" ]]; then
- echo "::error file=vcpkg.json::could not read the vcpkg submodule pointer or the builtin-baseline"
- exit 1
- fi
- if [[ "$pointer" != "$baseline" ]]; then
- echo "::error file=vcpkg.json::vcpkg submodule is at $pointer but builtin-baseline is $baseline"
- echo "Move them together: git -C vcpkg checkout $baseline && git add vcpkg"
- exit 1
- fi
- echo "vcpkg submodule and builtin-baseline agree on $baseline"
-
build-linux:
strategy:
fail-fast: false
@@ -149,8 +103,6 @@ jobs:
allow-failure: yes
package: skip # We are packaged in Arch already
- debug_test: yes # see the note on Fedora 45 below
- ccache_size: 850M # Debug plus the suite: 660 MB of objects at -g1
- distro: Servatrice_Debian
version: 12
@@ -158,159 +110,88 @@ jobs:
package: DEB
server_only: yes
test: skip
- # 4.7 min and the server alone: nothing here for a compiler cache to save.
- persist_ccache: 'false'
+
+ - distro: Debian
+ version: 12
+
+ package: DEB
+ test: skip # Running tests on all distros is superfluous
- distro: Debian
version: 13
package: DEB
- ccache_size: 800M # Release plus the suite: 614 MB of objects at -g1
-
- # The single Fedora leg used to build Debug for the suite and Release for the
- # package in one job (measured on Fedora 44: 30.8 min, the only leg slower than
- # Windows), so the package compile waited behind the Debug one. Two legs run in
- # parallel and each keeps a cache that holds exactly one configuration.
- #
- # Arch and Fedora 45 are the two newest toolchains, and the -Wno-error= exceptions
- # in CMakeLists.txt exist for exactly their warnings. This leg compiles Debug
- # because only Debug passes -Werror, so it is where a new warning still fails.
- - distro: Fedora
- version: 45
- variant: debug # separate image and ccache from the release leg below
-
- package: skip
- debug_test: yes
- ccache_size: 850M # Debug plus the suite: 660 MB of objects at -g1
- distro: Fedora
- version: 45
- variant: release # separate image and ccache from the debug leg above
+ version: 43
package: RPM
- test: skip # The debug leg above runs this distro's suite
- ccache_size: 800M # Release plus the package: 614 MB of objects at -g1
+ test: skip # Running tests on all distros is superfluous
+
+ - distro: Fedora
+ version: 44
+
+ package: RPM
+
+ - distro: Ubuntu
+ version: 24.04
+
+ package: DEB
+ test: skip # Running tests on all distros is superfluous
- distro: Ubuntu
version: 26.04
package: DEB
- test: skip # The sanitizer leg below runs this distro's suite
- # 18.1 min, and the same distro as the sanitize leg below, which runs this suite.
- persist_ccache: 'false'
- # The one leg that runs the suite under AddressSanitizer and
- # UndefinedBehaviorSanitizer. Debian 13 and Fedora 45 keep covering the
- # uninstrumented suite, so nothing is lost by instrumenting this one.
- #
- # It packages nothing. A sanitized Debug build is not something to ship, and
- # carrying the package build here meant 18 minutes of Release compile queued
- # behind the instrumented one in a single job. The leg above publishes this
- # distro's DEB.
+ # The one leg that runs the suite under AddressSanitizer and
+ # UndefinedBehaviorSanitizer. Debian 13 and Fedora 44 keep covering the
+ # uninstrumented suite, so nothing is lost by instrumenting this one.
+ sanitize: yes
+
- distro: Ubuntu
version: 26.04
- variant: sanitize # separate image and ccache, same distro
+ variant: cover # separate image and ccache, same distro
+ # The one leg that runs instrumented for line coverage and publishes the report as
+ # a workflow artifact. It is a separate leg rather than a second flag on the
+ # sanitizer leg because the two instruments answer different questions: the
+ # sanitizer leg reports what the run did wrong, the coverage leg reports what the
+ # run never touched at all. Combining them would also slow both down. Packaging is
+ # skipped because the leg above already publishes the DEB for this distro.
+ coverage: yes
package: skip
- sanitize: yes
- ccache_size: 1200M # instrumented objects are larger than the 614 MB measured; estimated
name: ${{ matrix.distro }} ${{ matrix.version }}${{ matrix.variant && format(' {0}', matrix.variant) || '' }}
needs: configure
- # The nightly legs live in build-linux-nightly below. A leg is in exactly one of the two
- # matrices, so nothing that reviews a change waits for a distro nobody is changing for.
- if: github.event_name != 'schedule'
runs-on: ubuntu-latest # https://github.com/actions/runner-images
continue-on-error: ${{ matrix.allow-failure == 'yes' }}
timeout-minutes: 70
env:
CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }}${{ matrix.variant }} # directory for caching docker image and ccache
CCACHE_EVICTION_AGE: 7d
- # The bound is sized against the repository's 10 GB cache quota
- # (https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy),
- # not against what one leg would like: the quota also holds about 0.9 GB of buildkit
- # layers (mode=min in docker-release.yml), 0.8 GB of vcpkg, 0.7 GB of Qt and the CodeQL
- # trap caches (about 1 GB each, which prune-caches below does not manage).
- #
- # Measured 2026-11-04: one configuration costs 1.2 GB with full DWARF and 660 MB with
- # -g1 (COCKATRICE_DEBUG_INFO_LEVEL below), and the size of the cache decides the hit
- # rate far more sharply than its fill suggests. A 6G cache served 1059 of 1059 calls;
- # the 700M bound on the same leg served 14%, because ccache fills to the limit and then
- # evicts the entries the next build is about to ask for. A cache that cannot hold a
- # whole configuration therefore buys almost nothing, and the quota goes to fewer legs
- # at a size that holds one:
- #
- # Arch Debug + suite 660 MB working set 850M
- # Debian 13 Release + suite 614 MB 800M
- # Fedora 45 debug Debug + suite 660 MB 850M
- # Fedora 45 release Release + package 614 MB 800M
- # Ubuntu 26.04 sanitize ~1.0 GB 1200M
- #
- # That is about 5.3 GB of bounds, and with the fixed entries above it puts the quota at
- # roughly 9.8 GB of 10 - so only legs worth their share get one. A leg keeps no cache
- # when it duplicates another leg's coverage (Ubuntu 26.04 behind the sanitize leg of
- # the same distro; macOS 15 and macOS 27 Debug behind macOS 14; Servatrice covers the
- # server alone and finishes in 4.7 min). Those four run cold: 4.7, 18.1, 16.5 and
- # 15.8 min. The five that stay compile the client on the toolchains a change can break
- # and are the ones a cache shortens by the most. 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 still sets the pull-request
- # wall clock no matter how warm these legs are (see build-vcpkg).
- #
- # Re-add a leg only with a bound that holds its whole configuration: a smaller one is
- # worse than none.
- #
- # Every key listed in LIVE_CCACHE_KEYS in prune-caches below must match this matrix.
- CCACHE_SIZE: ${{ matrix.ccache_size || '700M' }}
- # Debug information dominates a cached object: with the built-in flags one
- # configuration is 1.2 GB and no per-leg bound can hold it, so CI lowers the level to
- # 1 (file and line, which is what a failing test or a sanitizer backtrace reports) and
- # one configuration halves. Empty on tags and on the nightly legs, which ship binaries
- # and keep full DWARF for the Sentry symbols; a tag restores a cache it cannot use and
- # is slower, which is accepted because a tag is rare and only pull requests gate.
- COCKATRICE_DEBUG_INFO_LEVEL: ${{ github.ref_type != 'tag' && '1' || '' }}
+ CCACHE_SIZE: 600M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CMAKE_GENERATOR: 'Ninja'
NAME: ${{ matrix.distro }}${{ matrix.version }}
IMAGE_SUFFIX: ${{ matrix.variant }}
- # Which build the suite runs in, decided once per leg here so the steps' if: conditions
- # and their shells cannot drift apart. A step's own env is not visible to its if:, which
- # is why these live at job level rather than on the release step.
- # DEBUG_TEST the instrumented legs (symbols and instrumentation matter) and the two
- # newest distro toolchains, which only fail on a warning in Debug.
- # RUN_TESTS every other leg that tests runs the suite against the release build it may
- # also package, so testing and packaging cost one compile instead of two.
- DEBUG_TEST: ${{ matrix.sanitize == 'yes' || matrix.coverage == 'yes' || matrix.debug_test == 'yes' }}
- RUN_TESTS: ${{ matrix.test != 'skip' && !(matrix.sanitize == 'yes' || matrix.coverage == 'yes' || matrix.debug_test == 'yes') }}
- # Whether this leg restores and saves its compiler cache. Not every pull-request leg
- # keeps one: a cache only pays for itself when its bound holds a whole configuration
- # (see the sizing comment above), and a leg that duplicates another leg's coverage
- # spends quota without adding any. The nightly-only legs keep none either. Fedora 45
- # states it per leg: its two configurations have their own job and key, so neither
- # bound is shared with a build whose objects it would evict.
- PERSIST_CCACHE: ${{ matrix.persist_ccache || 'true' }}
- # Shared with build-linux-nightly so the two leg sets run the same steps.
- steps: &linux-steps
+ steps:
- name: "Checkout"
uses: actions/checkout@v7
- name: "Restore compiler cache (ccache)"
id: restore_ccache
- if: env.PERSIST_CCACHE == 'true'
uses: actions/cache/restore@v6
with:
key: ccache-${{ matrix.distro }}${{ matrix.version }}${{ matrix.variant }}
- path: ${{ env.CACHE }}/.ccache
+ path: ${{ env.CACHE }}
- name: "Build ${{ matrix.distro }} ${{ matrix.version }} Docker image"
shell: bash
run: source .ci/docker.sh --build
- # The instrumented legs and the two newest distro toolchains build and test Debug; every
- # other leg runs its suite against the release build below, so testing and packaging cost
- # one compile instead of two.
- name: "Build debug and test"
- if: env.DEBUG_TEST == 'true'
+ if: matrix.test != 'skip'
shell: bash
env:
sanitize: '${{ matrix.sanitize }}'
@@ -344,105 +225,34 @@ jobs:
- name: "Upload coverage artifact"
if: matrix.coverage == 'yes'
- uses: actions/upload-artifact@v7
+ uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
if-no-files-found: warn
- # One build serves both purposes: the suite runs against the same configuration that
- # gets packaged, so a packaging leg no longer pays for a full Debug compile it then
- # throws away. macOS and Windows have always tested their release build.
- #
- # Instrumented and debug-test legs reach this step only when they also package; their
- # suite already ran in the debug step above, so the tests are not repeated here.
- - name: "Build release, test and package"
+ - name: "Build release package"
id: build
- if: matrix.package != 'skip' || env.RUN_TESTS == 'true'
+ if: matrix.package != 'skip'
shell: bash
env:
SUFFIX: '-${{ matrix.distro }}${{ matrix.version }}'
package: '${{ matrix.package }}'
server_only: '${{ matrix.server_only }}'
- # Only a leg that packages builds the shipping configuration; a release build that
- # only runs tests stays free of the crash reporting SDK.
- WITH_CRASH_REPORTING: ${{ matrix.package != 'skip' && 'ON' || '' }}
- # 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.
- # configure.outputs.tag is set for ANY tag, so beta (Development) tags get the DSN
- # and the symbol upload below just like full releases.
- COCKATRICE_SENTRY_DSN: ${{ needs.configure.outputs.tag != null && secrets.SENTRY_DSN || '' }}
run: |
source .ci/docker.sh
args=()
[[ $server_only == yes ]] && args+=(--no-client)
- [[ $RUN_TESTS == true ]] && args+=(--test)
[[ $GITHUB_REF == "refs/heads/master" ]] && args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
args+=(--ccache "$CCACHE_SIZE")
args+=(--cmake-generator "$CMAKE_GENERATOR")
args+=(--suffix "$SUFFIX")
- # The container inherits no environment, so the DSN rides in explicitly. -e without
- # =value passes it through (possibly empty); compile.sh only consumes it for Release
- # configures, and empty keeps the mock fallback.
- export RUN_ARGS="${RUN_ARGS:-} -e COCKATRICE_SENTRY_DSN"
- # --package takes an optional type, so the flag and its argument have to travel
- # together or the type would become the next flag.
- package_args=()
- [[ -n $package && $package != skip ]] && package_args+=(--package "$package")
-
- # compile.sh runs the tests before the package target, so a failing suite stops the
- # leg before anything is built into an installer.
- RUN --server --release "${package_args[@]}" "${args[@]}"
-
- - name: "Install sentry-cli"
- if: matrix.package != 'skip' && needs.configure.outputs.tag != null
- # Downloads from Sentry's release registry: a Sentry outage or a broken install under
- # Git Bash must not take down the release assets below.
- continue-on-error: true
- shell: bash
- env:
- SENTRY_CLI_VERSION: '3.8.0'
- run: |
- export INSTALL_DIR="$GITHUB_WORKSPACE/.sentry-cli"
- curl -sL https://sentry.io/get-cli/ | bash
- echo "$GITHUB_WORKSPACE/.sentry-cli" >> "$GITHUB_PATH"
-
- - name: "Upload debug symbols to Sentry"
- # Like the DSN above, this runs for every tag — beta (Development) tags included.
- if: matrix.package != 'skip' && needs.configure.outputs.tag != null
- # A Sentry outage or rotated token must not take down the release assets below.
- continue-on-error: true
- shell: bash
- env:
- SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
- run: |
- paths=()
- # The binaries only, not the directories: a directory makes sentry-cli walk the
- # whole build tree, where it finds (and uploads) object files and other fragments
- # that can never match a stack frame. The packaged copy is split and stripped by
- # cpack, but keeps this binary's GNU build-id, which is how Sentry matches it.
- for app in cockatrice oracle servatrice; do
- binary="build/$app/$app"
- [[ -f $binary ]] && paths+=("$binary")
- done
- if [[ ${#paths[@]} == 0 ]]; then
- # Fail loudly instead of skipping: see the Windows/macOS leg below — this step
- # is the only way symbols reach Sentry and `continue-on-error` keeps the job
- # green either way, so a silent skip looks exactly like a successful upload.
- echo "::error::No debug symbol files found; nothing would be uploaded"
- exit 1
- fi
- # Project slug on Sentry; must match the project the baked DSN points at.
- # No --release here: `debug-files upload` has no such option (sentry-cli errors on
- # the unknown flag and uploads nothing), and debug information files are matched to
- # events by debug ID via the event's debug_meta, not by release. The release grouping
- # comes from the client event itself (COCKATRICE_SENTRY_RELEASE).
- sentry-cli debug-files upload --org cockatrice --project cockatrice-desktop "${paths[@]}"
+ RUN --server --release --package "$package" "${args[@]}"
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342
- name: "Delete remote compiler cache (ccache)"
- if: github.ref == 'refs/heads/master' && env.PERSIST_CCACHE == 'true' && steps.restore_ccache.outputs.cache-hit
+ if: github.ref == 'refs/heads/master' && steps.restore_ccache.outputs.cache-hit
continue-on-error: true
env:
CACHE_PRIMARY_KEY: ${{ steps.restore_ccache.outputs.cache-primary-key }}
@@ -453,11 +263,11 @@ jobs:
fi
- name: "Save updated compiler cache (ccache)"
- if: github.ref == 'refs/heads/master' && env.PERSIST_CCACHE == 'true'
+ if: github.ref == 'refs/heads/master'
uses: actions/cache/save@v6
with:
key: ${{ steps.restore_ccache.outputs.cache-primary-key }}
- path: ${{ env.CACHE }}/.ccache
+ path: ${{ env.CACHE }}
- name: "Upload artifact"
id: upload_artifact
@@ -495,178 +305,94 @@ jobs:
GH_TOKEN: ${{ github.token }}
run: gh attestation verify "$BUILD_PATH" --repo Cockatrice/Cockatrice
- build-linux-nightly:
- strategy:
- fail-fast: false
- matrix:
- include:
- - distro: Debian
- version: 12 # previous stable, still covered by the nightly
-
- package: DEB
- test: skip # Running tests on all distros is superfluous
-
- - distro: Fedora
- version: 44 # previous release, still covered by the nightly
-
- package: RPM
- test: skip # Running tests on all distros is superfluous
-
- - distro: Ubuntu
- version: 24.04 # previous LTS, still covered by the nightly
-
- package: DEB
- test: skip # Running tests on all distros is superfluous
-
- # The one leg that runs instrumented for line coverage and publishes the report as
- # a workflow artifact. It stays a separate leg rather than a second flag on the
- # sanitizer leg because the two instruments answer different questions: the
- # sanitizer leg reports what the run did wrong, the coverage leg reports what the
- # run never touched at all. Combining them would also slow both down.
- #
- # It moved here from build-linux: gcovr.cfg sets no fail_under, so the leg cannot
- # fail a build, and nothing consumes the report per change, which made it an
- # artifact nobody read on every pull request. On the default branch it becomes a
- # trend instead. Image and ccache keys come from distro, version and variant, so
- # they are unchanged and the warm cache that leg built still matches.
- - distro: Ubuntu
- version: 26.04
- variant: cover # separate image and ccache, same distro
-
- coverage: yes
- package: skip
- # A report must not redden the nightly run: vcpkg-cache-import only imports the
- # archives of a run whose conclusion is success.
- allow-failure: yes
-
- name: ${{ matrix.distro }} ${{ matrix.version }}${{ matrix.variant && format(' {0}', matrix.variant) || '' }}
- needs: configure
- # A nightly and a tag build cover the legs a pull request or a merge does not wait for:
- # the previous release of each distro, and the one instrument that only reports.
- if: github.event_name == 'schedule' || github.ref_type == 'tag'
- runs-on: ubuntu-latest # https://github.com/actions/runner-images
- continue-on-error: ${{ matrix.allow-failure == 'yes' }}
- timeout-minutes: 70
- env:
- CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }}${{ matrix.variant }} # directory for caching docker image and ccache
- CCACHE_EVICTION_AGE: 7d
- # These legs do not keep a compiler cache: they run once a day (and on tags) and gate
- # nothing, so their share of the 10 GB quota goes to the pull-request legs instead.
- # A cold nightly build costs minutes on a runner nobody waits for; a cold pull request
- # build costs a review. CCACHE_SIZE is still passed so ccache trims its scratch dir.
- PERSIST_CCACHE: 'false'
- CCACHE_SIZE: 600M
- CMAKE_GENERATOR: 'Ninja'
- NAME: ${{ matrix.distro }}${{ matrix.version }}
- IMAGE_SUFFIX: ${{ matrix.variant }}
- # The shared steps below gate on these, so they have to be derived here too. Left
- # undefined they silently skipped the build the leg exists for: the coverage leg
- # found build/ empty and its report collection failed on every nightly and tag run.
- # Same derivation as the pull request job above.
- DEBUG_TEST: ${{ matrix.sanitize == 'yes' || matrix.coverage == 'yes' || matrix.debug_test == 'yes' }}
- RUN_TESTS: ${{ matrix.test != 'skip' && !(matrix.sanitize == 'yes' || matrix.coverage == 'yes' || matrix.debug_test == 'yes') }}
- steps: *linux-steps
-
build-vcpkg:
strategy:
fail-fast: false
matrix:
include:
- os: macOS
- target: 14 # EOL 2026-09-14
- runner: xcode-27 # https://github.com/actions/runner-images/blob/main/images/macos/xcode-27-arm64-Readme.md
+ target: 13 # EOL 2025-09-15
+ runner: macos-15-intel # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
+ # macos-26-intel is very slow and fails in CPack during DMG config if not increasing Finder timeout
+
+ ccache_eviction_age: 7d
+ cmake_generator: Ninja
+ make_package: 1
+ override_target: 13
+ package_suffix: "-macOS13_Intel"
+ qt_version: 6.11.*
+ qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
+ soc: Intel
+ type: Release
+ use_ccache: 1
+ xcode: "26.3"
+
+ - os: macOS
+ target: 14 # EOL 2026-??
+ runner: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
override_target: 14
package_suffix: "-macOS14"
- ccache_size: 800M # one Release configuration plus the suite at -g1
qt_version: 6.11.*
qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
soc: Apple
type: Release
use_ccache: 1
- vcpkg_deployment_target: 14
- vcpkg_scope: 'vcpkg/macos14-arm64-xcode26.6/'
- vcpkg_upload: 1 # only one leg per vcpkg_scope hands its archives over
+ xcode: "26.6"
- os: macOS
- target: 15 # EOL 2027-??
- runner: xcode-27 # https://github.com/actions/runner-images/blob/main/images/macos/xcode-27-arm64-Readme.md
+ target: 15
+ runner: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
override_target: 15
package_suffix: "-macOS15"
- persist_ccache: 'false' # 16.5 min: shorter than the Windows leg
qt_version: 6.11.*
qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
soc: Apple
type: Release
use_ccache: 1
- vcpkg_deployment_target: 14
- vcpkg_scope: 'vcpkg/macos14-arm64-xcode26.6/'
+ xcode: "26.6"
- os: macOS
- target: 27
- runner: xcode-27 # https://github.com/actions/runner-images/blob/main/images/macos/xcode-27-arm64-Readme.md
+ target: 26
+ runner: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
ccache_eviction_age: 7d
cmake_generator: Ninja
- persist_ccache: 'false' # 15.8 min: shorter than the Windows leg
qt_version: 6.11.*
qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
soc: Apple
type: Debug
use_ccache: 1
- vcpkg_deployment_target: 14
- vcpkg_scope: 'vcpkg/macos14-arm64-xcode26.6/'
+ xcode: "26.6"
- os: Windows
target: 10
runner: windows-2025 # https://github.com/actions/runner-images/blob/main/images/windows/Windows2025-VS2026-Readme.md
- # Ninja builds the whole dependency graph at once instead of one
- # MSBuild node at a time, runs the compiler itself, and matches the
- # Linux and macOS legs. Its median build phase measured ~17 min
- # against ~21 for the Visual Studio generator (shared 4-core hosts
- # swing either by minutes, so read medians, not single runs).
- cmake_generator: Ninja
+ cmake_generator: "Visual Studio 18 2026"
+ cmake_generator_platform: x64
make_package: 1
package_suffix: "-Win10"
qt_version: 6.11.*
qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
type: Release
- # Scope is named after the vs2026 toolchain, not the generator: the
- # ABI hash covers cl.exe, which is identical, so this leg keeps
- # restoring the warm archives. Renaming would orphan them.
- vcpkg_scope: 'vcpkg/windows10-x64-vs2026/'
- vcpkg_upload: 1 # only one leg per vcpkg_scope hands its archives over
name: ${{ matrix.os }} ${{ matrix.target }}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
- needs: [configure, vcpkg-baseline]
- # The nightly leg lives in build-vcpkg-nightly below, next to the Linux nightly legs.
- if: github.event_name != 'schedule'
+ needs: configure
runs-on: ${{ matrix.runner }}
timeout-minutes: 100
env:
CCACHE_DIR: ${{ github.workspace }}/.cache/
- # One macOS configuration is about 600 MB of objects at -g1, so its bound needs to be
- # 800M to hold one whole; a smaller cache serves a few percent and is worthless (see
- # the sizing comment in build-linux). macOS 14 keeps the one macOS cache: it runs the
- # suite against the shipping Release build and is the longest of the three at 24.2 min.
- # macOS 15 (16.5) and macOS 27 Debug (15.8) duplicate its coverage and are shorter, so
- # they run cold.
- 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; it has no compiler
- # cache to shrink anyway (see the sizing comment in build-linux).
- COCKATRICE_DEBUG_INFO_LEVEL: ${{ matrix.os != 'Windows' && github.ref_type != 'tag' && '1' || '' }}
+ CCACHE_SIZE: 600M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
- # Shared with build-vcpkg-nightly so the two leg sets run the same steps.
- steps: &vcpkg-steps
+ steps:
- name: "Checkout"
uses: actions/checkout@v7
with:
@@ -683,11 +409,11 @@ jobs:
run: brew install ccache
- name: "[macOS] Restore compiler cache (ccache)"
- if: matrix.os == 'macOS' && matrix.use_ccache == 1 && env.PERSIST_CCACHE == 'true'
+ if: matrix.os == 'macOS' && matrix.use_ccache == 1
id: restore_ccache
uses: actions/cache/restore@v6
with:
- key: ccache-${{ matrix.os }}${{ matrix.override_target }}-${{ matrix.runner }}
+ key: ccache-${{ matrix.runner }}_${{ matrix.override_target }}-Xcode${{ matrix.xcode }}
path: ${{ env.CCACHE_DIR }}
- name: "[macOS] Install aqtinstall"
@@ -750,45 +476,12 @@ jobs:
shell: bash
run: choco install nsis
- # Ninja invokes cl.exe directly and needs the MSVC environment on PATH.
- - name: "[Windows] Set up MSVC environment"
- if: matrix.os == 'Windows'
- uses: TheMrMilchmann/setup-msvc-dev@368ef7d1ee4d1171b31d4a7f67f4d954f903f5a9 # v4.1.0
- with:
- arch: x64
-
- name: "Setup vcpkg cache"
id: vcpkg-cache
uses: TAServers/vcpkg-cache@v3
with:
- # Every entry is keyed by vcpkg ABI hash, and one hash set exists per platform,
- # target and toolchain. The action restores every entry it finds under the
- # prefix, not just the hashes vcpkg asks for, so with one shared vcpkg/
- # namespace every leg downloads all 66 archives of all 5 legs on every run.
- # Each of those restores also refreshes the entry's last access date, which
- # is why a dead ABI generation is never evicted by age. Naming the scope is
- # what makes those generations identifiable, and prune-caches below able to
- # delete them.
- prefix: ${{ matrix.vcpkg_scope }}
token: ${{ secrets.GITHUB_TOKEN }}
- # The action only creates its directory when the prefix matched at least one
- # entry, and it reads that directory unguarded when it saves at the end of the
- # job. A scope nobody has written to yet therefore leaves the directory missing
- # and fails the job, which is exactly the state every scope starts in. Make the
- # directory up front so an empty scope is a cache miss instead of an error.
- # always() covers the cache step itself failing: the action's post save still runs
- # then, and it should report the real error instead of ENOENT. It cannot rescue a
- # failure before the cache step, since a post step only runs for an action whose
- # main step did. The fallback keeps this guard from failing when the cache step was
- # skipped and left no path output.
- - name: "Create vcpkg cache directory"
- if: always()
- shell: bash
- env:
- CACHE_DIR: '${{ steps.vcpkg-cache.outputs.path }}'
- run: mkdir -p "${CACHE_DIR:-$GITHUB_WORKSPACE/.vcpkg-cache}"
-
# Uses environment variables, see compile.sh for more details
- name: "Build Cockatrice"
id: build
@@ -797,11 +490,8 @@ jobs:
BUILDTYPE: '${{ matrix.type }}'
CCACHE_EVICTION_AGE: ${{ matrix.ccache_eviction_age }}
CMAKE_GENERATOR: ${{ matrix.cmake_generator }}
- # 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.
- # configure.outputs.tag is set for ANY tag, so beta (Development) tags get the DSN
- # and the symbol upload below just like full releases.
- COCKATRICE_SENTRY_DSN: ${{ needs.configure.outputs.tag != null && secrets.SENTRY_DSN || '' }}
+ CMAKE_GENERATOR_PLATFORM: ${{ matrix.cmake_generator_platform }}
+ DEVELOPER_DIR: '/Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer'
GITHUB_TOKEN: ${{ github.token }} # needed for vcpkg dependency graph updates, see VCPKG_FEATURE_FLAGS
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
@@ -811,138 +501,11 @@ jobs:
PACKAGE_SUFFIX: '${{ matrix.package_suffix }}'
TARGET_MACOS_VERSION: ${{ matrix.override_target }}
USE_CCACHE: ${{ matrix.use_ccache }}
- # 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
- # built from, and only merged code may populate the scope that master and
- # release builds restore and link. Pull requests (same-repository ones
- # included) restore the shared cache but never write it; with the source
- # left at read the collect step below finds nothing on PRs, so no
- # pull-request-scoped entry is ever saved that no later run could restore.
- # Forks pushing to their own default branch resolve github.ref against
- # their own repository, so they keep a working cache of their own there.
VCPKG_BINARY_SOURCES: "clear;files,${{ steps.vcpkg-cache.outputs.path }},${{ case(github.ref == 'refs/heads/master', 'readwrite', 'read') }}"
VCPKG_DISABLE_METRICS: 1
VCPKG_FEATURE_FLAGS: dependencygraph
- VCPKG_TARGET_MACOS_VERSION: ${{ matrix.vcpkg_deployment_target }}
- # Release legs compile the sentry-native SDK; Debug legs stay SDK-free.
- WITH_CRASH_REPORTING: ${{ matrix.type == 'Release' && 'ON' || '' }}
run: .ci/compile.sh --server --test --vcpkg
- - name: "Install sentry-cli"
- if: needs.configure.outputs.tag != null && matrix.make_package == '1'
- # Same as the Linux leg above: the install downloads from Sentry's registry, so a
- # failure here must not block the release upload below.
- continue-on-error: true
- shell: bash
- env:
- SENTRY_CLI_VERSION: '3.8.0'
- run: |
- export INSTALL_DIR="$GITHUB_WORKSPACE/.sentry-cli"
- curl -sL https://sentry.io/get-cli/ | bash
- echo "$GITHUB_WORKSPACE/.sentry-cli" >> "$GITHUB_PATH"
-
- - name: "Upload debug symbols to Sentry"
- # Like the DSN above, this runs for every tag — beta (Development) tags included.
- if: needs.configure.outputs.tag != null && matrix.make_package == '1'
- # A Sentry outage or rotated token must not take down the release assets below.
- continue-on-error: true
- shell: bash
- env:
- SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
- run: |
- paths=()
- if [[ '${{ matrix.os }}' == 'Windows' ]]; then
- # Top-level CMakeLists.txt links /DEBUG even in release, so the linker PDBs
- # land next to the binaries. Ninja is single-config, so that is
- # build// and NOT build//Release/ — the same globs the symbol
- # artifact step below uses.
- #
- # The executables are uploaded as well, in the same invocation. On 64-bit
- # Windows the Call Frame Information (.pdata/.xdata) lives in the PE and is
- # not copied into the PDB, so a PDB on its own leaves Sentry reporting
- # "Stack Unwinding: Missing" for the image. sentry-cli also needs to see the
- # PE and its PDB together: only then does it associate the PE's code
- # identifier with the PDB's debug identifier.
- shopt -s nullglob
- for target in cockatrice oracle servatrice; do
- paths+=("build/$target"/*.exe "build/$target"/*.pdb)
- done
- shopt -u nullglob
-
- # Qt's own runtime DLLs, taken from required-runtime.txt — the manifest
- # .ci/compile.sh asserts the installer against — so the set uploaded is
- # the set an install actually carries. The PE is the only carrier of
- # unwind information for a 64-bit image, and sending it in the same
- # invocation as a PDB is what lets sentry-cli associate the PE's code
- # identifier with the PDB's debug identifier. Qt ships no PDBs with the
- # SDK, so Qt frames resolve through the export table: names, no lines.
- qt_dir=
- qt_core_dir=
- # Existence guards matter: the step runs under `bash -eo pipefail`, so a
- # redirection-less sed over a missing file would abort the step outright
- # and continue-on-error would hide it.
- if [[ -f build/CMakeCache.txt ]]; then
- qt_core_dir=$(sed -n 's/^Qt6Core_DIR:PATH=//p' build/CMakeCache.txt | head -1)
- fi
- if [[ -n $qt_core_dir ]]; then
- qt_dir=$(dirname "$(dirname "$(dirname "$qt_core_dir")")")
- fi
- if [[ -z $qt_dir || ! -d $qt_dir ]]; then
- # Warnings, not failures: the upload of our own binaries is what this
- # step is for, and continue-on-error already means an upload failure
- # cannot be told apart from a successful one.
- echo "::warning::Qt runtime directory not resolved from build/CMakeCache.txt; Qt images get no unwind information"
- elif [[ ! -f build/required-runtime.txt ]]; then
- echo "::warning::build/required-runtime.txt is missing; cannot tell which Qt DLLs ship"
- else
- while IFS= read -r runtime; do
- name=${runtime##*/}
- if [[ $runtime == */* ]]; then
- # A Qt plugin: the install root keeps it as plugins//.
- # `|| true`: an unmatched glob makes ls exit non-zero, and pipefail
- # turns that into a step abort.
- runtime_path=$(ls "$qt_dir/plugins"/*/"$name" 2>/dev/null | head -1) || true
- else
- runtime_path="$qt_dir/bin/$name"
- fi
- if [[ -f ${runtime_path:-} ]]; then
- paths+=("$runtime_path")
- else
- echo "::warning::shipped Qt runtime $runtime not found under $qt_dir"
- fi
- done < <(grep -E '^(Qt6[^/]*\.dll|Plugins/)' build/required-runtime.txt)
- fi
- else
- # Release builds carry DWARF (-g, kept by CrashReporting.cmake); macOS keeps its
- # debug info in dSYM bundles, so materialize them before uploading.
- for app in cockatrice oracle servatrice; do
- binary="build/$app/$app.app/Contents/MacOS/$app"
- if [[ -f $binary ]]; then
- if [[ '${{ matrix.os }}' == 'macOS' ]]; then
- dsymutil "$binary" -o "$binary.dSYM"
- paths+=("$binary.dSYM")
- # The dSYM holds the debug information, but the unwind information
- # Sentry needs to walk a minidump stack lives in the Mach-O itself.
- paths+=("$binary")
- else
- paths+=("$binary")
- fi
- fi
- done
- fi
- if [[ ${#paths[@]} == 0 ]]; then
- # Fail loudly instead of skipping: this step is the only way symbols reach
- # Sentry, and `continue-on-error` keeps the job green either way, so a
- # silent skip is indistinguishable from a successful upload. It also hid a
- # wrong Windows path (build//Release, which Ninja never creates).
- echo "::error::No debug symbol files found; nothing would be uploaded"
- exit 1
- fi
- # Project slug on Sentry; must match the project the baked DSN points at.
- # No --release: see the Linux leg above — the flag does not exist on
- # `debug-files upload` and DIFs are matched by debug ID, not release.
- sentry-cli debug-files upload --org cockatrice --project cockatrice-desktop "${paths[@]}"
-
# CPack reports a failed makensis run as "Problem running NSIS command"
# plus a path to NSISOutput.log, and that log lives only on the runner, so
# the actual compile error is otherwise nowhere in the workflow output.
@@ -954,66 +517,9 @@ jobs:
shell: bash
run: find . -name NSISOutput.log -exec cat {} +
- # A master run hands over the archives it had to build itself so that
- # vcpkg-cache-import.yml, which runs in the default branch scope, can save them
- # where later runs can restore them. Pull requests hand over nothing: their cache
- # entries would be scoped to refs/pull//merge and stay unreadable for every
- # other run, so writing them only burns the repository's quota.
- # vcpkg_upload keeps this to one leg per scope: macOS 14, 15 and 26 share a scope
- # by design, and all three rebuild the same archives, so uploading from each of them
- # would publish three identical 269 MB artifacts under one artifact name and make
- # the importer spawn a save job per copy.
- - name: "Collect new vcpkg entries"
- id: vcpkg_new
- if: github.ref == 'refs/heads/master' && matrix.vcpkg_upload == 1
- shell: bash
- env:
- CACHE_DIR: '${{ steps.vcpkg-cache.outputs.path }}'
- GH_TOKEN: ${{ github.token }}
- VCPKG_SCOPE: '${{ matrix.vcpkg_scope }}'
- run: |
- # The archive name is the ABI hash, so a missing key is exactly what vcpkg had
- # to compile from source in this job.
- gh api --paginate \
- "repos/$GITHUB_REPOSITORY/actions/caches?ref=refs/heads/master&key=$VCPKG_SCOPE&per_page=100" \
- --jq '.actions_caches[].key' | sort -u >"$RUNNER_TEMP/master-keys.txt"
- find "$CACHE_DIR" -type f -name '*.zip' -exec basename {} .zip \; |
- sed "s|^|$VCPKG_SCOPE|" | sort -u >"$RUNNER_TEMP/local-keys.txt"
- comm -23 "$RUNNER_TEMP/local-keys.txt" "$RUNNER_TEMP/master-keys.txt" >"$RUNNER_TEMP/new-keys.txt"
-
- mkdir -p "$RUNNER_TEMP/vcpkg-new"
- : >"$RUNNER_TEMP/vcpkg-new/keys.txt"
- while read -r key; do
- hash="${key##*/}"
- archive="$(find "$CACHE_DIR" -type f -name "$hash.zip" -print -quit)"
- dir="$RUNNER_TEMP/vcpkg-new/${hash:0:2}" # vcpkg's own two character fanout
- mkdir -p "$dir"
- cp "$archive" "$dir/"
- echo "$key ${hash:0:2}/$hash.zip" >>"$RUNNER_TEMP/vcpkg-new/keys.txt"
- done <"$RUNNER_TEMP/new-keys.txt"
-
- count="$(wc -l <"$RUNNER_TEMP/new-keys.txt")"
- echo "vcpkg scope $VCPKG_SCOPE: $count entries to import"
- echo "count=$count" >>"$GITHUB_OUTPUT"
- echo "slug=$(sed -e 's|^vcpkg/||' -e 's|/$||' -e 's|/|-|g' <<<"$VCPKG_SCOPE")" >>"$GITHUB_OUTPUT"
-
- # archive: false accepts a single file and nothing else, and a scope hands over one
- # archive per ABI hash vcpkg had to rebuild, which is 23 files on a cold runner.
- # Zipping the directory keeps the fanout at the artifact root, which is where
- # vcpkg-cache-import.yml looks for keys.txt once download-artifact unpacks it.
- - name: "Upload new vcpkg entries"
- # An empty count means the step above did not run at all, on master and on forks
- if: steps.vcpkg_new.outputs.count != '' && steps.vcpkg_new.outputs.count != '0'
- uses: actions/upload-artifact@v7
- with:
- if-no-files-found: error # a count above zero with nothing staged is a bug
- name: vcpkg-new-${{ steps.vcpkg_new.outputs.slug }}
- path: ${{ runner.temp }}/vcpkg-new
- retention-days: 1
-
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342
- name: "[macOS] Delete remote compiler cache (ccache)"
- if: matrix.os == 'macOS' && matrix.use_ccache == 1 && env.PERSIST_CCACHE == 'true' && github.ref == 'refs/heads/master' && steps.restore_ccache.outputs.cache-hit
+ if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master' && steps.restore_ccache.outputs.cache-hit
continue-on-error: true
env:
CACHE_PRIMARY_KEY: ${{ steps.restore_ccache.outputs.cache-primary-key }}
@@ -1024,7 +530,7 @@ jobs:
fi
- name: "[macOS] Cache updated compiler cache (ccache)"
- if: matrix.os == 'macOS' && matrix.use_ccache == 1 && env.PERSIST_CCACHE == 'true' && github.ref == 'refs/heads/master'
+ if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master'
uses: actions/cache/save@v6
with:
key: ${{ steps.restore_ccache.outputs.cache-primary-key }}
@@ -1086,6 +592,17 @@ jobs:
if-no-files-found: error
path: ${{ steps.build.outputs.path }}
+ - name: "[Windows] Upload PDBs (Program Databases)"
+ if: matrix.os == 'Windows' && github.ref_type != 'tag'
+ uses: actions/upload-artifact@v7
+ with:
+ if-no-files-found: error
+ name: ${{ steps.build.outputs.name }}-PDBs
+ path: |
+ build/cockatrice/Release/*.pdb
+ build/oracle/Release/*.pdb
+ build/servatrice/Release/*.pdb
+
- name: "Upload to release"
if: needs.configure.outputs.tag != null && matrix.make_package == '1'
id: upload_release
@@ -1097,33 +614,6 @@ jobs:
tag_name: ${{ needs.configure.outputs.tag }}
run: gh release upload "$tag_name" "$asset_path#$asset_name"
- # Ninja is single-config: binaries and their PDBs land directly in the
- # target directories instead of a Release subdirectory.
- #
- # Archived for tag builds only, and ordered after the release upload so
- # a missing PDB can never block a release from being published. On a tag
- # build this artifact is the only durable copy of the symbols: the Sentry
- # upload is continue-on-error, so without it a failed upload would lose
- # the ability to symbolicate that release permanently. Other builds upload
- # no symbols at all, so archiving them there would only cost storage.
- #
- # The executables travel with their PDBs: re-uploading the pair together is
- # what lets sentry-cli associate the PE's code identifier with the PDB's
- # debug identifier, and the PE is what carries the unwind information.
- - name: "[Windows] Upload debug symbols (exe + pdb)"
- if: matrix.os == 'Windows' && needs.configure.outputs.tag != null
- uses: actions/upload-artifact@v7
- with:
- if-no-files-found: error
- name: ${{ steps.build.outputs.name }}-Symbols
- path: |
- build/cockatrice/*.exe
- build/cockatrice/*.pdb
- build/oracle/*.exe
- build/oracle/*.pdb
- build/servatrice/*.exe
- build/servatrice/*.pdb
-
- name: "Attest binary provenance"
if: steps.upload_release.outcome == 'success'
id: attestation
@@ -1139,133 +629,3 @@ jobs:
BUILD_PATH: ${{ steps.build.outputs.path }}
GH_TOKEN: ${{ github.token }}
run: gh attestation verify "$BUILD_PATH" --repo Cockatrice/Cockatrice
-
- build-vcpkg-nightly:
- strategy:
- fail-fast: false
- matrix:
- include:
- - os: macOS
- target: 13 # EOL 2025-09-15
- runner: macos-15-intel # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
- # macos-26-intel is very slow and fails in CPack during DMG config if not increasing Finder timeout
-
- ccache_eviction_age: 7d
- cmake_generator: Ninja
- make_package: 1
- override_target: 13
- package_suffix: "-macOS13_Intel"
- qt_version: 6.11.*
- qt_modules: qtimageformats qtmultimedia qtshadertools qtwebsockets
- soc: Intel
- type: Release
- use_ccache: 1
- vcpkg_scope: 'vcpkg/macos13-x64-xcode26.3/'
- vcpkg_upload: 1 # only one leg per vcpkg_scope hands its archives over
-
- name: ${{ matrix.os }} ${{ matrix.target }}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
- needs: configure
- # The EOL target on the slowest runner is too slow to gate a change, so it runs in the
- # nightly and on tags only, where the vcpkg cache import also has to accept schedule runs.
- if: github.event_name == 'schedule' || github.ref_type == 'tag'
- runs-on: ${{ matrix.runner }}
- timeout-minutes: 100
- env:
- CCACHE_DIR: ${{ github.workspace }}/.cache/
- # Like build-linux-nightly: this leg keeps no compiler cache, so the quota stays with
- # the pull-request legs (see PERSIST_CCACHE there).
- PERSIST_CCACHE: 'false'
- CCACHE_SIZE: 600M
- steps: *vcpkg-steps
-
- prune-caches:
- name: Prune caches
- # Only master writes caches at all, so only master may clean them up too
- if: github.ref == 'refs/heads/master'
- runs-on: ubuntu-slim # https://github.com/actions/runner-images/blob/main/images/ubuntu-slim/ubuntu-slim-Readme.md
- continue-on-error: true # freeing space must never fail a build
-
- steps:
- # The repository holds 10 GB of cache against entries that want more, and GitHub evicts
- # the least recently used entry whenever that is exceeded. The vcpkg entries are the
- # reason that never happens: the restore above touches every entry under the prefix on
- # every run, so a dead ABI generation keeps its access date fresh forever and age based
- # eviction can never reach it. Deleting by age alone cannot fix that, only knowing which
- # scope an entry belongs to can. The same is true of a compiler cache of a leg that no
- # longer keeps one: its restore would keep touching it, so the live key list below is
- # what retires it.
- #
- # docker-release's image builds keep their layer cache in the same quota: one blob per
- # layer, keyed buildkit-blob-1-, plus one index--1- entry per image
- # scope that lists them. They are written with mode=min, so only the final image stage
- # is kept and the set stays small. A layer a build actually needs is downloaded, which
- # refreshes its access date, so an old date means the builds have stopped using it and
- # age is the right test. Dropping a blob an index still lists costs the next image build
- # one rebuilt layer; docker-release gates nothing, so that is cheaper than keeping the
- # entries forever.
- - name: "Delete unusable cache entries"
- shell: bash
- env:
- GH_TOKEN: ${{ github.token }}
- # Must list every vcpkg_scope of the build-vcpkg and build-vcpkg-nightly
- # matrices above, or the entries of a scope that is still in use get deleted
- # on the next run.
- LIVE_VCPKG_SCOPES: 'vcpkg/macos13-x64-xcode26.3/ vcpkg/macos14-arm64-xcode26.6/ vcpkg/windows10-x64-vs2026/'
- # Must list the ccache key of every leg that keeps a compiler cache
- # (PERSIST_CCACHE in build-linux and build-vcpkg), or a live entry is deleted.
- # 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
- # the age rule.
- LIVE_CCACHE_KEYS: 'ccache-Arch ccache-Debian13 ccache-Fedora45debug ccache-Fedora45release ccache-Ubuntu26.04sanitize ccache-macOS14-xcode-27'
- run: |
- 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"
-
- cutoff="$(( $(date -u +%s) - 7 * 24 * 3600 ))"
- live=" ${LIVE_VCPKG_SCOPES} "
- live_ccache=" ${LIVE_CCACHE_KEYS} "
-
- deleted=0
- while IFS=$'\t' read -r id ref key accessed; do
- [[ -n $id ]] || continue
- # Only ever touch the key shapes our workflows create (buildkit-* and index-*
- # come from docker-release.yml), so that an entry of some other workflow can
- # never be deleted by accident
- case "$key" in
- vcpkg/* | ccache-* | Qt* | buildkit-* | index-*) ;;
- *) continue ;;
- esac
-
- reason=""
- if [[ $ref != refs/heads/master ]]; then
- reason="scoped to $ref"
- elif [[ $key == ccache-* ]]; then
- # An exact token compare, because one key can be a prefix of another:
- # ccache-Ubuntu26.04 is a prefix of ccache-Ubuntu26.04sanitize, and only
- # the sanitize entry is live.
- if [[ $live_ccache != *" $key "* ]]; then
- reason="no pull-request leg keeps it"
- fi
- elif [[ $key == vcpkg/* ]]; then
- # "/", cut the hash off the end to get the scope
- scope="vcpkg/${key#vcpkg/}"
- scope="${scope%/*}/"
- if [[ $live != *" $scope "* ]]; then
- reason="no matrix leg uses $scope"
- fi
- fi
- if [[ -z $reason ]]; then
- stamp="$(date -u -d "$accessed" +%s 2>/dev/null || echo 0)"
- if [[ $stamp -ne 0 && $stamp -lt $cutoff ]]; then
- reason="unused since $accessed"
- fi
- fi
-
- if [[ -n $reason ]]; then
- echo "::notice::deleting $key, $reason"
- gh api -X DELETE "repos/$GITHUB_REPOSITORY/actions/caches/$id"
- deleted=$((deleted + 1))
- fi
- done <"$RUNNER_TEMP/caches.tsv"
-
- echo "::notice::deleted $deleted of $(wc -l <"$RUNNER_TEMP/caches.tsv") cache entries"
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
index 1537d6d4d..937386940 100644
--- a/.github/workflows/docker-release.yml
+++ b/.github/workflows/docker-release.yml
@@ -84,18 +84,12 @@ jobs:
password: ${{ github.token }}
# Don't push for non-publishing triggers
- #
- # The layer cache shares the repository's 10 GB Actions cache quota with the compiler
- # caches (see CCACHE_SIZE in desktop-build.yml). mode=min keeps only the final image
- # stage: the build stage sits behind `COPY . .`, so its compile layer is invalidated by
- # every source change and was never read back, yet it was the bulk of the ~2 GB this
- # cache held. The final runtime stage is the part that still pays off.
- name: "Build image"
if: steps.login.outcome != 'success'
uses: docker/build-push-action@v7
with:
cache-from: type=gha,scope=${{ env.CACHE_SCOPE }}
- cache-to: ${{ case(github.ref == 'refs/heads/master', format('type=gha,mode=min,scope={0}', env.CACHE_SCOPE), '') }}
+ cache-to: ${{ case(github.ref == 'refs/heads/master', format('type=gha,mode=max,scope={0}', env.CACHE_SCOPE), '') }}
context: .
platforms: ${{ matrix.platform }}
push: false
@@ -107,7 +101,7 @@ jobs:
uses: docker/build-push-action@v7
with:
cache-from: type=gha,scope=${{ env.CACHE_SCOPE }}
- cache-to: type=gha,mode=min,scope=${{ env.CACHE_SCOPE }}
+ cache-to: type=gha,mode=max,scope=${{ env.CACHE_SCOPE }}
context: .
labels: |
org.opencontainers.image.description=${{ env.OCI_DESCRIPTION }}
diff --git a/.github/workflows/vcpkg-cache-import.yml b/.github/workflows/vcpkg-cache-import.yml
deleted file mode 100644
index 0a377a8fd..000000000
--- a/.github/workflows/vcpkg-cache-import.yml
+++ /dev/null
@@ -1,123 +0,0 @@
-name: Import vcpkg cache
-
-# After a runner image update changes every port's ABI hash, the first master run
-# rebuilds protobuf, openssl and abseil from source and saves them into the shared
-# cache, where every later run can restore them.
-#
-# Only master pushes and the nightly schedule run on master are imported here: the
-# collect step, keys.txt and the zip contents all come from the workflow run being
-# imported, so importing from an unmerged pull request would let anyone who can push
-# a branch — or a bot actor such as Dependabot, whose PRs are same-repo — promote
-# arbitrary binaries into the scope that master and tag builds restore and link into
-# signed releases. Forks build untrusted code and must never reach that scope either.
-# A scheduled run is the default branch at the commit a master push already built, so
-# it carries the same trust as a push.
-#
-# repository_dispatch is not used on purpose: it would also resolve to the default branch,
-# but it counts as a low-trust trigger and may only write there when it declares a
-# write-capable cache-mode, which puts it in the same position as workflow_run.
-
-permissions:
- actions: write # needed to save entries into the default branch's cache scope
- contents: read
-
-on:
- workflow_run:
- workflows: ["Build Desktop"]
- types: [completed]
-
-# Every import writes the same scopes, so let them queue instead of racing each other
-concurrency:
- group: vcpkg-cache-import
- cancel-in-progress: false
-
-jobs:
- list:
- name: List new vcpkg entries
- # Only a successful push to the default branch has archives worth importing.
- # Only code that landed on master may populate the scope master runs restore.
- # Bot actors are excluded on top: their runs must never promote binaries into
- # the shared scope even if they somehow target the default branch.
- if: >-
- github.event.workflow_run.conclusion == 'success' &&
- (github.event.workflow_run.event == 'push' || github.event.workflow_run.event == 'schedule') &&
- github.event.workflow_run.head_branch == 'master' &&
- github.event.workflow_run.head_repository.full_name == github.repository &&
- !endsWith(github.event.workflow_run.actor.login, '[bot]') &&
- !endsWith(github.event.workflow_run.triggering_actor.login, '[bot]')
- runs-on: ubuntu-slim # https://github.com/actions/runner-images/blob/main/images/ubuntu-slim/ubuntu-slim-Readme.md
- # workflow_run is a low-trust trigger, so cache access is read-only by default even
- # though this run resolves to the default branch. Assert below that this took effect,
- # because a save under read-only access only warns and would import nothing silently.
- cache-mode: write
- outputs:
- entries: ${{ steps.scan.outputs.entries }}
-
- steps:
- - name: "Download new vcpkg entries"
- uses: actions/download-artifact@v8
- with:
- pattern: vcpkg-new-*
- path: incoming
- run-id: ${{ github.event.workflow_run.id }}
- github-token: ${{ secrets.GITHUB_TOKEN }}
-
- - name: "Assert write access"
- shell: bash
- run: |
- if [[ $ACTIONS_CACHE_MODE != *write* ]]; then
- echo "::error::expected a write-capable cache, got '$ACTIONS_CACHE_MODE'"
- exit 1
- fi
-
- - name: "List cache entries"
- id: scan
- shell: bash
- working-directory: incoming
- run: |
- entries="$(find . -name keys.txt -type f -exec sh -c '
- for manifest; do
- artifact="$(basename "$(dirname "$manifest")")"
- while read -r key path; do
- printf "{\"artifact\": \"%s\", \"key\": \"%s\", \"path\": \"%s\"}\n" \
- "$artifact" "$key" "$path"
- done <"$manifest"
- done
- ' _ {} +)"
- [[ -n $entries ]] || exit 0
- # Compact, because a multi line value cannot be written to GITHUB_OUTPUT
- entries="$(jq -cs '.' <<<"$entries")"
- echo "entries=$entries" >>"$GITHUB_OUTPUT"
- echo "::notice::importing $(jq length <<<"$entries") vcpkg entries"
-
- save:
- name: "Save ${{ matrix.entry.key }}"
- needs: list
- if: needs.list.outputs.entries
- runs-on: ubuntu-slim
- cache-mode: write
- strategy:
- # One job per archive, they run in parallel and each only uploads a single file
- fail-fast: false
- max-parallel: 20
- matrix:
- entry: ${{ fromJSON(needs.list.outputs.entries) }}
-
- steps:
- - name: "Download new vcpkg entries"
- uses: actions/download-artifact@v8
- with:
- pattern: ${{ matrix.entry.artifact }}
- path: incoming
- run-id: ${{ github.event.workflow_run.id }}
- github-token: ${{ secrets.GITHUB_TOKEN }}
-
- - name: "Save vcpkg entry"
- uses: actions/cache/save@v6
- with:
- # The payload is a Windows or macOS archive while the upload runs on Linux, and
- # without this the entry would be stamped as a Linux cache that neither of them
- # is allowed to restore.
- enableCrossOsArchive: true
- key: ${{ matrix.entry.key }}
- path: incoming/${{ matrix.entry.path }}
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
index e69c8e686..732fec7ca 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,6 +4,3 @@
[submodule "doxygen-awesome-css"]
path = doc/doxygen/theme
url = https://github.com/jothepro/doxygen-awesome-css.git
-[submodule "external/qttesting"]
- path = external/qttesting
- url = https://github.com/Kitware/QtTesting.git
diff --git a/.tx/github_integration_config.yml b/.tx/github_integration_config.yml
deleted file mode 100644
index 2586b4800..000000000
--- a/.tx/github_integration_config.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Integration Docs: https://help.transifex.com/en/articles/6265125-github-installation-and-configuration#h_de29ef5cec
-# Config Parameter: https://help.transifex.com/en/articles/6265125-github-installation-and-configuration#h_a734c3e219
-# Supported Files: https://help.transifex.com/en/collections/3519116-file-formats
-
-filters:
- - filter_type: file
- file_format: QT
- source_language: en
- source_file: cockatrice/cockatrice_en@source.ts
- translation_files_expression: cockatrice/translations/cockatrice_.ts
-
- - filter_type: file
- file_format: QT
- source_language: en
- source_file: oracle/oracle_en@source.ts
- translation_files_expression: oracle/translations/oracle_.ts
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 53508fcd1..bee47972c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,8 +20,6 @@ option(WITH_CLIENT "Build Cockatrice client" ON)
option(WITH_ORACLE "Build Cockatrice card database tool (Oracle)" ON)
# Compile Servatrice
option(WITH_SERVER "Build Cockatrice server (Servatrice)" OFF)
-# Compile GUI tests (QtTesting widget-level playback)
-option(WITH_GUI_TEST "Build the QtTesting-based GUI test harness" OFF)
# Compile tests
option(TEST "Build tests" OFF)
# Build with AddressSanitizer and UndefinedBehaviorSanitizer
@@ -31,14 +29,6 @@ option(ENABLE_COVERAGE "Build instrumented for GCC line coverage" OFF)
# Use vcpkg regardless of OS
option(USE_VCPKG "Use vcpkg regardless of OS" OFF)
-# The GUI test harness lives in a submodule; a checkout without it must still configure.
-if(WITH_GUI_TEST AND NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/qttesting/CMakeLists.txt")
- message(WARNING "WITH_GUI_TEST is ON but external/qttesting is missing, so GUI tests are disabled.\n"
- "Run 'git submodule update --init external/qttesting' to enable them."
- )
- set(WITH_GUI_TEST OFF)
-endif()
-
# Default to "Release" build type
# User-provided value for CMAKE_BUILD_TYPE must be checked before the PROJECT() call
if(DEFINED CMAKE_BUILD_TYPE)
@@ -65,10 +55,6 @@ if(USE_CCACHE AND NOT WIN32)
# PCH-aware caching, matching .ci/compile.sh: without this ccache refuses
# to cache any TU that consumes a precompiled header, so every PCH-backed
# target recompiles from scratch on each build.
- # The include_file_mtime/include_file_ctime entries that CI also sets are
- # deliberately not set here: --set-config writes the user's global
- # ccache.conf, and those two would turn off the modified-during-compile
- # check for every project on the machine, not just this one.
execute_process(COMMAND ${CCACHE_PROGRAM} --set-config sloppiness=pch_defines,time_macros)
message(STATUS "Found CCache ${CCACHE_PROGRAM}")
endif()
@@ -85,8 +71,6 @@ if(WIN32 OR USE_VCPKG)
)
# Qt path set by user or env var
if(QTDIR
- OR QTDIR32
- OR QTDIR64
OR DEFINED ENV{QTDIR}
OR DEFINED ENV{QTDIR32}
OR DEFINED ENV{QTDIR64}
@@ -106,7 +90,7 @@ endif()
# A project name is needed for CPack
# Version can be overriden by git tags, see cmake/getversion.cmake
-project("Cockatrice" VERSION 3.2.0)
+project("Cockatrice" VERSION 3.1.2)
# Set release name if not provided via env/cmake var
if(NOT DEFINED GIT_TAG_RELEASENAME)
@@ -236,28 +220,6 @@ endif()
# when ENABLE_SANITIZERS or ENABLE_COVERAGE is on.
include(Sanitizers)
include(Coverage)
-include(CrashReporting)
-
-# Debug information is what makes a cached object large, and a build configuration needs more
-# room than a per-leg cache bound can spare when it carries full DWARF: ccache then fills to
-# its limit and evicts the entries the next build is about to ask for, so it serves a few
-# percent of calls while holding most of a build. CI sets a lower level so one whole
-# configuration fits; level 1 keeps file and line, which is what a failing test or a sanitizer
-# backtrace reports. Empty (the default) leaves local, distro and release builds exactly as
-# they were. This comes after CrashReporting, so the -g it adds for Sentry symbols cannot
-# raise the level again.
-set(COCKATRICE_DEBUG_INFO_LEVEL
- ""
- CACHE STRING "DWARF level for Debug and Release objects (empty keeps the built-in flags)"
-)
-if(NOT COCKATRICE_DEBUG_INFO_LEVEL STREQUAL "")
- if(MSVC)
- message(WARNING "COCKATRICE_DEBUG_INFO_LEVEL is ignored on MSVC")
- else()
- string(APPEND CMAKE_CXX_FLAGS_DEBUG " -g${COCKATRICE_DEBUG_INFO_LEVEL}")
- string(APPEND CMAKE_CXX_FLAGS_RELEASE " -g${COCKATRICE_DEBUG_INFO_LEVEL}")
- endif()
-endif()
# GNU systems need to define the Mersenne exponent for the RNG to compile w/o warning
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
@@ -273,13 +235,13 @@ else()
set(_lib_suffix 32)
endif()
-if(NOT "${QTDIR${_lib_suffix}}" STREQUAL "")
+if(DEFINED QTDIR${_lib_suffix})
list(APPEND CMAKE_PREFIX_PATH "${QTDIR${_lib_suffix}}")
-elseif(NOT "${QTDIR}" STREQUAL "")
+elseif(DEFINED QTDIR)
list(APPEND CMAKE_PREFIX_PATH "${QTDIR}")
-elseif(NOT "$ENV{QTDIR${_lib_suffix}}" STREQUAL "")
+elseif(DEFINED ENV{QTDIR${_lib_suffix}})
list(APPEND CMAKE_PREFIX_PATH "$ENV{QTDIR${_lib_suffix}}")
-elseif(NOT "$ENV{QTDIR}" STREQUAL "")
+elseif(DEFINED ENV{QTDIR})
list(APPEND CMAKE_PREFIX_PATH "$ENV{QTDIR}")
endif()
@@ -287,22 +249,7 @@ message(STATUS "Update Translations: ${UPDATE_TRANSLATIONS}")
include(FindQtRuntime)
-# Reject APIs that were deprecated before the oldest Qt release we support
-# (6.4). Qt 6.5 renamed the switch, so use whichever spelling the found
-# version understands while still compiling everywhere.
-if(Qt6_VERSION VERSION_GREATER_EQUAL 6.5)
- add_compile_definitions(QT_DISABLE_DEPRECATED_UP_TO=0x060400)
-else()
- add_compile_definitions(QT_DISABLE_DEPRECATED_BEFORE=0x060400)
-endif()
-
-# Set once here for every target: these variables are inherited by the
-# subdirectories, so the per-directory copies were redundant. Only the
-# variables are set; qt_standard_project_setup would also change the runtime
-# output directories and the install RPATH.
set(CMAKE_AUTOMOC TRUE)
-set(CMAKE_AUTOUIC TRUE)
-set(CMAKE_AUTORCC TRUE)
# Find other needed libraries
find_package(Protobuf CONFIG)
@@ -380,7 +327,7 @@ if(UNIX)
endif()
elseif(WIN32)
set(CPACK_GENERATOR NSIS ${CPACK_GENERATOR})
- if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+ if("${CMAKE_GENERATOR_PLATFORM}" MATCHES "(x64)")
set(TRICE_IS_64_BIT 1)
else()
set(TRICE_IS_64_BIT 0)
@@ -389,15 +336,6 @@ elseif(WIN32)
# Configure file with custom definitions for NSIS.
configure_file("${COCKATRICE_CMAKE_PATH}/NSIS.definitions.nsh.in" "${PROJECT_BINARY_DIR}/NSIS.definitions.nsh")
- # Installer-log sender for the AbortInstall Sentry hook (values parsed out of COCKATRICE_SENTRY_DSN in
- # cmake/CrashReporting.cmake). Generated only when a DSN was parsed: cmake/NSIS.template.in embeds it under
- # the same CPACK_SENTRY_ENABLED check, so a build without reporting never references or carries the helper.
- if(CPACK_SENTRY_ENABLED)
- configure_file(
- "${COCKATRICE_CMAKE_PATH}/SendInstallerLog.ps1.in" "${PROJECT_BINARY_DIR}/SendInstallerLog.ps1" @ONLY
- )
- endif()
-
# Every runtime DLL a Windows install has to contain, as paths relative to the
# install root. .ci/compile.sh reads this and fails the build when the packaged
# installer is missing one of them, or carries a runtime it does not know about.
@@ -472,9 +410,6 @@ if(WITH_ORACLE OR WITH_CLIENT)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_models ${CMAKE_BINARY_DIR}/libcockatrice_models)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_filters ${CMAKE_BINARY_DIR}/libcockatrice_filters)
endif()
-if(WITH_CLIENT)
- add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_game ${CMAKE_BINARY_DIR}/libcockatrice_game)
-endif()
if(WITH_SERVER)
add_subdirectory(servatrice)
@@ -486,46 +421,11 @@ if(WITH_CLIENT)
set(CPACK_INSTALL_CMAKE_PROJECTS "Cockatrice;Cockatrice;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS})
endif()
-if(WIN32 AND WITH_CLIENT)
- # Runs from the installer's $PLUGINSDIR when a purge fails, and is never installed
- # (see LogProcessesUsingInstallDir in cmake/NSIS.template.in), so it has no install
- # rules and is not part of the client's staged tree.
- add_subdirectory(${CMAKE_SOURCE_DIR}/tools/installer-holder-probe ${CMAKE_BINARY_DIR}/tools/installer-holder-probe)
-endif()
-
if(WITH_ORACLE)
add_subdirectory(oracle)
set(CPACK_INSTALL_CMAKE_PROJECTS "Oracle;Oracle;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS})
endif()
-if(WITH_GUI_TEST)
- set(QtTesting_QT_VERSION "6")
- # QtTesting registers its own unit tests when BUILD_TESTING is on. They target the
- # Qt version the harness was written for and fail on Qt 6.11 (toolbutton
- # long-press recording), and they are not Cockatrice's tests, so suppress them
- # for the duration of the subdirectory instead of painting ctest red.
- if(DEFINED BUILD_TESTING)
- set(_cockatrice_saved_build_testing ${BUILD_TESTING})
- endif()
- set(BUILD_TESTING
- OFF
- CACHE BOOL "Build the testing tree." FORCE
- )
- add_subdirectory(external/qttesting)
- if(DEFINED _cockatrice_saved_build_testing)
- set(BUILD_TESTING
- ${_cockatrice_saved_build_testing}
- CACHE BOOL "Build the testing tree." FORCE
- )
- else()
- unset(BUILD_TESTING CACHE)
- endif()
- # cl.exe has no warnings-as-errors here, and it rejects -Wno-error with D8021.
- if(NOT MSVC)
- target_compile_options(qttesting PRIVATE -Wno-error)
- endif()
-endif()
-
if(TEST)
include(CTest)
add_subdirectory(tests)
@@ -533,5 +433,5 @@ endif()
if(Qt6_FOUND AND Qt6_VERSION_MINOR GREATER_EQUAL 3)
# Qt6.3+ requires project finalization to support translations
- qt_finalize_project()
+ qt6_finalize_project()
endif()
diff --git a/README.md b/README.md
index fa9272c0f..f2a170868 100644
--- a/README.md
+++ b/README.md
@@ -15,10 +15,9 @@
---
-To get started with Cockatrice ⇢ [view our webpage](https://cockatrice.github.io/)
-To get support, or suggest changes to the app ⇢ [file an issue](https://github.com/Cockatrice/Cockatrice/issues) ([How?](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice))
-To learn about the app and its code ⇢ [read our code documentation](https://cockatrice.github.io/docs/)
-To help with development ⇢ learn [how to contribute](#contribute)
+To get started with Cockatrice ⇢ [view our webpage](https://cockatrice.github.io/)
+To get support, or suggest changes to the app ⇢ [file an issue](https://github.com/Cockatrice/Cockatrice/issues) ([How?](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice))
+To help with development ⇢ learn [how to contribute](#contribute-)
@@ -84,7 +83,7 @@ Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other p
### Code
-To commit code to the project, please review our [Contribution Guidelines](https://github.com/Cockatrice/Cockatrice/blob/master/.github/CONTRIBUTING.md) first.
+To contribute code to the project, please review our [guidelines](https://github.com/Cockatrice/Cockatrice/blob/master/.github/CONTRIBUTING.md) first.
We maintain two tags for contributors to easier find issues to potentially work on:
- [](https://github.com/Cockatrice/Cockatrice/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3A%22Good%20first%20issue%22%20)
Issues tagged in this way provide a simple way to get started. They don't require much experience to be worked on.
@@ -96,7 +95,7 @@ We'll happily advice on how best to implement a feature, or we can show you wher
You can also have a look at our `Todo List` in our [Code Documentation](https://cockatrice.github.io/docs) or search the repo for [`\todo` comments](https://github.com/search?q=repo%3ACockatrice%2FCockatrice%20%5Ctodo&type=code).
-### Documentation [](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml?query=event%3Arelease+event%3Aworkflow_dispatch)
+### Documentation [](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml?query=event%3Apush)
There are various places where useful information for different needs are maintained:
- [Official Code Documentation](https://cockatrice.github.io/docs/)
@@ -105,8 +104,7 @@ There are various places where useful information for different needs are mainta
- [Official README](https://github.com/Cockatrice/Cockatrice/blob/master/README.md) `This file`
- [Official CONTRIBUTING](https://github.com/Cockatrice/Cockatrice/blob/master/.github/CONTRIBUTING.md)
-Cockatrice tries to use the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation.
-We encourage everybody to improve the documentation by suggesting edits based on this guide.
+Cockatrice tries to use the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation. We encourage you to improve the documentation by suggesting edits based on this guide.
### Translation [](https://explore.transifex.com/cockatrice/cockatrice/)
diff --git a/Testing/README.md b/Testing/README.md
deleted file mode 100644
index cb990ec29..000000000
--- a/Testing/README.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# GUI tests
-
-Widget-level playback tests for the Cockatrice client, driven by
-[QtTesting](https://github.com/Kitware/QtTesting) (`external/qttesting`, a submodule).
-Each `*_test.xml` file is a list of events that QtTesting replays against the running
-application; a test passes when every event is handled without error.
-
-## Running
-
-```bash
-git submodule update --init external/qttesting # once
-cmake -S . -B build/debug -DCMAKE_BUILD_TYPE=Debug -DWITH_SERVER=0 -DWITH_GUI_TEST=ON
-cmake --build build/debug -j 24
-
-./Testing/run_test.sh --all # headless, offscreen
-./Testing/run_test.sh Testing/smoke_test.xml # one file
-./Testing/run_test.sh --visual Testing/smoke_test.xml # shows the window
-```
-
-`WITH_GUI_TEST` is on by default; a checkout without the submodule configures with a
-warning and no harness. `COCKATRICE_BIN` points the runner at another build, and
-`TEST_TIMEOUT` (seconds, default 180) bounds a single test — a failing event inside a
-modal dialog can otherwise leave the process alive. The client also hard-exits a few
-seconds after playback returns, whatever the result.
-
-The tests are **not** part of `ctest`: they need a card database and a developer's
-settings, so they stay a local loop for now.
-
-## Caveats
-
-- **Not hermetic.** The client reads the local settings and card database, so a test can
- pass on one machine and fail on another. Avoid asserting things that restored
- workspace state or user preferences decide (for example the number of open tabs).
-- **English labels only.** Menu entries are addressed by their (translated) text, so the
- tests expect the source language. Widgets are addressed by objectName paths, which are
- stable across translations.
-- Actions that open a dialog are followed by a `pause` event: playback continues in the
- dialog's own event loop, and the pause gives the UI time to build itself.
-- Widget paths include Qt-internal names where a QTabWidget is involved
- (`qt_tabwidget_stackedwidget`), and the deck views in a game are indexed
- (`tabbedDeckView_0`, `tabbedDeckView_1`).
-
-## Writing a test
-
-A test is a `` list. Widgets are named by their path from the main window, e.g.
-`MainWindow/tabSupervisor/stackedWidget/TabGame/phasesMenu`. Useful commands:
-
-| Command | Arguments | Effect |
-|---------|-----------|--------|
-| `activate` | action or menu name | triggers a QAction (menus are opened first) |
-| `key` | Qt key code | presses that key on the focused widget |
-| `pause` | milliseconds | waits, letting the UI settle |
-| `clickCard` | `card,zone` | double click the card found in that zone (plays it from hand) |
-| `flipCard` | `card,zone` | right-click → *Turn Over* |
-| `cardAction` | `card,zone,label` | right-click → first menu entry whose label contains `label` (submenus included) |
-| `moveCard` | `card,srcZone,dstZone` | drags the card onto the destination zone |
-| `drawArrow` | `srcCard,srcZone,dstCard,dstZone` | right-drag an arrow between two cards |
-| `createToken` | `name,color,pt` | creates a token as the active local player |
-| `selectItem` | `item label` | selects the tree/list entry with that label |
-| `typeText` | `text` | types text one key event at a time |
-| `expectProperty` | `property,value` | fails playback unless the widget property matches |
-
-The card, item and property commands are `pqCockatriceEventPlayer`
-(`cockatrice/src/game_graphics/pq_cockatrice_event_player.h`). They resolve cards by name
-and zone through the `QGraphicsScene` and send real mouse events at the card's current
-viewport position, so a test does not depend on the window size or screen resolution.
diff --git a/Testing/card_actions_test.xml b/Testing/card_actions_test.xml
deleted file mode 100644
index 8d6de4c2b..000000000
--- a/Testing/card_actions_test.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Testing/deck_editor_open_test.xml b/Testing/deck_editor_open_test.xml
deleted file mode 100644
index ab74b1c63..000000000
--- a/Testing/deck_editor_open_test.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/Testing/deck_editor_search_test.xml b/Testing/deck_editor_search_test.xml
deleted file mode 100644
index 6641ac3b2..000000000
--- a/Testing/deck_editor_search_test.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Testing/dialog_lifecycle_test.xml b/Testing/dialog_lifecycle_test.xml
deleted file mode 100644
index 00510b802..000000000
--- a/Testing/dialog_lifecycle_test.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Testing/game_mechanics_test.xml b/Testing/game_mechanics_test.xml
deleted file mode 100644
index efef78707..000000000
--- a/Testing/game_mechanics_test.xml
+++ /dev/null
@@ -1,101 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Testing/integration_test.xml b/Testing/integration_test.xml
deleted file mode 100644
index 33191d1ac..000000000
--- a/Testing/integration_test.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Testing/local_game_lifecycle_test.xml b/Testing/local_game_lifecycle_test.xml
deleted file mode 100644
index bdc964921..000000000
--- a/Testing/local_game_lifecycle_test.xml
+++ /dev/null
@@ -1,178 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Testing/menu_open_close_test.xml b/Testing/menu_open_close_test.xml
deleted file mode 100644
index d894b47b5..000000000
--- a/Testing/menu_open_close_test.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Testing/nonmodal_manage_sets_test.xml b/Testing/nonmodal_manage_sets_test.xml
deleted file mode 100644
index 5fe2bfc0c..000000000
--- a/Testing/nonmodal_manage_sets_test.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/Testing/nonmodal_tip_test.xml b/Testing/nonmodal_tip_test.xml
deleted file mode 100644
index 0f4ffc825..000000000
--- a/Testing/nonmodal_tip_test.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/Testing/nonmodal_view_log_test.xml b/Testing/nonmodal_view_log_test.xml
deleted file mode 100644
index 3369f1fd5..000000000
--- a/Testing/nonmodal_view_log_test.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/Testing/run_test.sh b/Testing/run_test.sh
deleted file mode 100755
index 8ff3fe028..000000000
--- a/Testing/run_test.sh
+++ /dev/null
@@ -1,118 +0,0 @@
-#!/bin/bash
-# Run QtTesting XML test files against the Cockatrice build.
-#
-# Usage:
-# ./Testing/run_test.sh [--visual] [--all | ...]
-#
-# Options:
-# --visual Run with native display (shows the GUI window).
-# Default is headless (QT_QPA_PLATFORM=offscreen).
-# --all Run every *.xml file in Testing/.
-#
-# Environment:
-# COCKATRICE_BIN Path to the client binary (default: build/debug/cockatrice/cockatrice,
-# or the .app bundle binary on macOS).
-# TEST_TIMEOUT Seconds a single test may run before it is killed (default: 180).
-# Uses timeout, or gtimeout from coreutils on macOS.
-#
-# Requires the debug build to exist at build/debug/cockatrice/cockatrice.
-
-set -euo pipefail
-shopt -s nullglob
-
-REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
-if [ -n "${COCKATRICE_BIN:-}" ]; then
- COCKATRICE="$COCKATRICE_BIN"
-elif [ "$(uname -s)" = "Darwin" ]; then
- # A macOS build produces an app bundle, not a bare executable.
- COCKATRICE="$REPO_ROOT/build/debug/cockatrice/cockatrice.app/Contents/MacOS/cockatrice"
-else
- COCKATRICE="$REPO_ROOT/build/debug/cockatrice/cockatrice"
-fi
-TEST_DIR="$REPO_ROOT/Testing"
-TEST_TIMEOUT="${TEST_TIMEOUT:-180}"
-
-# Stock macOS has no `timeout`; coreutils provides `gtimeout`. Without either the
-# runner still works, but a hung test is not killed.
-TIMEOUT=()
-if command -v timeout >/dev/null 2>&1; then
- TIMEOUT=(timeout --foreground "$TEST_TIMEOUT")
-elif command -v gtimeout >/dev/null 2>&1; then
- TIMEOUT=(gtimeout --foreground "$TEST_TIMEOUT")
-else
- echo "Warning: neither timeout nor gtimeout found; tests are not time-limited." >&2
-fi
-
-if [ ! -f "$COCKATRICE" ]; then
- echo "Error: $COCKATRICE not found. Build first:" >&2
- echo " cmake --build build/debug" >&2
- exit 1
-fi
-
-VISUAL=0
-if [ "${1:-}" = "--visual" ]; then
- VISUAL=1
- shift
-fi
-
-if [ "${1:-}" = "--all" ]; then
- shift
- TEST_FILES=("$TEST_DIR"/*.xml)
- if [ ${#TEST_FILES[@]} -eq 0 ]; then
- echo "No *.xml files found in $TEST_DIR" >&2
- exit 1
- fi
-elif [ $# -ge 1 ]; then
- TEST_FILES=("$@")
-else
- echo "Usage: $0 [--visual] [--all | ...]" >&2
- echo " Run a single test: $0 Testing/smoke_test.xml" >&2
- echo " Run all tests: $0 --all" >&2
- echo " Run visually: $0 --visual Testing/smoke_test.xml" >&2
- echo " Run all visually: $0 --visual --all" >&2
- exit 1
-fi
-
-PASS=0
-FAIL=0
-FAILED_TESTS=()
-
-run_one() {
- local file="$1"
-
- local -a cmd=()
- if [ "${#TIMEOUT[@]}" -gt 0 ]; then
- cmd=("${TIMEOUT[@]}")
- fi
- cmd+=("$COCKATRICE" --no-connect --test "$file")
-
- if [ "$VISUAL" -eq 1 ]; then
- "${cmd[@]}"
- else
- env QT_QPA_PLATFORM=offscreen "${cmd[@]}"
- fi
-}
-
-for f in "${TEST_FILES[@]}"; do
- name="$(basename "$f")"
- printf "%-40s " "$name"
- if run_one "$f"; then
- echo "PASS"
- PASS=$((PASS + 1))
- else
- echo "FAIL"
- FAIL=$((FAIL + 1))
- FAILED_TESTS+=("$name")
- fi
-done
-
-TOTAL=$((PASS + FAIL))
-echo ""
-echo "Results: $PASS/$TOTAL passed, $FAIL failed"
-if [ "$FAIL" -gt 0 ]; then
- echo "Failed:"
- for t in "${FAILED_TESTS[@]}"; do
- echo " $t"
- done
- exit 1
-fi
diff --git a/Testing/settings_dialog_test.xml b/Testing/settings_dialog_test.xml
deleted file mode 100644
index 2dd9dda0a..000000000
--- a/Testing/settings_dialog_test.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/Testing/settings_navigation_test.xml b/Testing/settings_navigation_test.xml
deleted file mode 100644
index c112966e0..000000000
--- a/Testing/settings_navigation_test.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Testing/settings_search_test.xml b/Testing/settings_search_test.xml
deleted file mode 100644
index ba60251f0..000000000
--- a/Testing/settings_search_test.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Testing/smoke_test.xml b/Testing/smoke_test.xml
deleted file mode 100644
index a31edbdd4..000000000
--- a/Testing/smoke_test.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/cmake/CrashReporting.cmake b/cmake/CrashReporting.cmake
deleted file mode 100644
index 6f9cf4849..000000000
--- a/cmake/CrashReporting.cmake
+++ /dev/null
@@ -1,212 +0,0 @@
-# Crash reporting via sentry-native (Sentry protocol, MIT).
-# Included once from the root CMakeLists.txt, right after the sanitizer/coverage helpers.
-#
-# Inputs:
-# WITH_CRASH_REPORTING -- build the client with the sentry-native SDK (default OFF, our release
-# CI passes ON; packagers and from-source builds stay SDK-free until this
-# ships something real, so offline package builds never fetch the SDK)
-# COCKATRICE_SENTRY_DSN -- Sentry DSN baked into the build (default empty = SDK init skipped at runtime)
-# COCKATRICE_SENTRY_TAG -- sentry-native release tag to vendor via FetchContent
-# ENABLE_SANITIZERS -- forces the sentry backend to "none" (crashpad vs ASan conflict)
-# Defines: cockatrice_target_enable_crash_reporting()
-
-option(WITH_CRASH_REPORTING "Build with crash reporting via sentry-native" OFF)
-set(COCKATRICE_SENTRY_DSN
- ""
- CACHE STRING "Sentry DSN baked into the client; empty disables crash-report upload at runtime"
-)
-# Pinned stable release; tag verified at https://github.com/getsentry/sentry-native/tags.
-set(COCKATRICE_SENTRY_TAG
- "0.17.3"
- CACHE STRING "sentry-native release tag to vendor via FetchContent"
-)
-
-# Wire the SDK (or the no-op fallback) into a client target. Always callable: with the
-# option OFF the compile definitions below make crash_reporter.cpp compile to no-ops.
-function(cockatrice_target_enable_crash_reporting _target)
- if(WITH_CRASH_REPORTING AND TARGET sentry::sentry)
- target_link_libraries(${_target} PRIVATE sentry::sentry)
- target_compile_definitions(${_target} PRIVATE COCKATRICE_WITH_CRASH_REPORTING=1)
- else()
- target_compile_definitions(${_target} PRIVATE COCKATRICE_WITH_CRASH_REPORTING=0)
- endif()
- # Always baked in (possibly empty): main() prefers the COCKATRICE_SENTRY_DSN env var at runtime.
- target_compile_definitions(${_target} PRIVATE "COCKATRICE_SENTRY_DSN=\"${COCKATRICE_SENTRY_DSN}\"")
- # Sentry release for events from this target: PROJECT_VERSION (getversion.cmake ran before this
- # file was included). Deliberately not PROJECT_VERSION_FRIENDLY / VERSION_STRING — that carries
- # a build date, so every build of the same version would land in a different Sentry release.
- # PROJECT_VERSION matches what the installer reports in its own log and what CPack derives the
- # package version from, so client and installer events group under one release.
- target_compile_definitions(${_target} PRIVATE "COCKATRICE_SENTRY_RELEASE=\"${PROJECT_VERSION}\"")
-endfunction()
-
-if(WITH_CRASH_REPORTING AND WITH_CLIENT)
- include(FetchContent)
-
- if(ENABLE_SANITIZERS)
- # The crash handler cannot run under ASan/UBSan, so there is no backend to capture with.
- set(_sentry_backend "none")
- else()
- # Breakpad on every OS (increment 3 probe, 2026-10-14): the crashpad sources
- # do vendor friction-free (FetchContent inits all submodules recursively by
- # default, verified populated in a local build tree), but the crashpad backend
- # would need its out-of-process crashpad_handler binary installed next to the
- # exe, into the macOS bundle, and through NSIS, plus handler-path wiring and
- # notarization care. Breakpad is in-process, needs no extra binary and no
- # packaging on any OS sentry-native builds, so simplicity wins.
- set(_sentry_backend "breakpad")
- endif()
- # FORCE so toggling ENABLE_SANITIZERS on a reused build dir cannot leave a stale backend behind.
- set(SENTRY_BACKEND
- "${_sentry_backend}"
- CACHE STRING "Crash handler backend for sentry-native" FORCE
- )
- unset(_sentry_backend)
-
- if(NOT MSVC)
- # Symbol upload needs debug info in Release binaries: the top-level flags build GCC
- # Release as -s -O2 (stripped, no DWARF) and Clang/Apple Release as plain -O2 (no DWARF),
- # both of which upload nothing useful. Keep -g and drop the strip flag while the SDK is
- # on; the OFF default leaves local, distro and Debug builds untouched. MSVC already
- # links /DEBUG with PDBs in release (see top-level CMakeLists.txt), so it needs nothing.
- string(REGEX REPLACE "(^| )-s( |$)" "\\1\\2" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
- if(NOT CMAKE_CXX_FLAGS_RELEASE MATCHES "(^| )-g( |$)")
- set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g")
- endif()
- endif()
-
- # No Qt integration: it runs find_package(Qt6) inside the vendored build, which
- # cannot see our thin/aqt Qt layouts (macOS CI) under foreign toolchains, and its
- # breadcrumbs could carry unredacted log lines into native crash events that bypass
- # before_send. Our attached redacted log preview already carries the log context.
- set(SENTRY_INTEGRATION_QT
- OFF
- CACHE BOOL "Qt log-message breadcrumbs for sentry-native (off: discovery + secret surface)" FORCE
- )
- # The transport stays at the SDK default (curl on Apple/Linux, winhttp on Windows).
- set(SENTRY_BUILD_TESTS
- OFF
- CACHE BOOL "Build sentry-native tests" FORCE
- )
- set(SENTRY_BUILD_EXAMPLES
- OFF
- CACHE BOOL "Build sentry-native examples" FORCE
- )
-
- fetchcontent_declare(
- sentry
- GIT_REPOSITORY https://github.com/getsentry/sentry-native.git
- GIT_TAG ${COCKATRICE_SENTRY_TAG}
- GIT_SHALLOW TRUE
- )
- fetchcontent_makeavailable(sentry)
-
- # Vendored sentry-native (and its breakpad/crashpad trees) warn under our -Wall -Wextra
- # (e.g. breakpad's -Wmissing-field-initializers), and WARNING_AS_ERROR would fail the build
- # on code we cannot fix. Those findings are not actionable for us, so opt the whole vendored
- # tree out of warnings-as-errors while keeping the warnings themselves visible; our own
- # targets keep -Werror. Interface libraries compile nothing, so they are skipped outright:
- # an INTERFACE -Wno-error would propagate to our consumers and weaken their build.
- function(_cockatrice_sentry_opt_out_of_werror _dir)
- get_property(
- _sentry_targets
- DIRECTORY "${_dir}"
- PROPERTY BUILDSYSTEM_TARGETS
- )
- foreach(_sentry_target IN LISTS _sentry_targets)
- get_target_property(_sentry_type ${_sentry_target} TYPE)
- if(_sentry_type STREQUAL "INTERFACE_LIBRARY")
- continue()
- elseif(_sentry_type MATCHES "LIBRARY|EXECUTABLE")
- get_target_property(_sentry_imported ${_sentry_target} IMPORTED)
- if(NOT _sentry_imported)
- target_compile_options(${_sentry_target} PRIVATE -Wno-error)
- endif()
- endif()
- endforeach()
- get_property(
- _sentry_subdirs
- DIRECTORY "${_dir}"
- PROPERTY SUBDIRECTORIES
- )
- foreach(_sentry_subdir IN LISTS _sentry_subdirs)
- _cockatrice_sentry_opt_out_of_werror("${_sentry_subdir}")
- endforeach()
- endfunction()
- if(NOT MSVC)
- _cockatrice_sentry_opt_out_of_werror("${sentry_SOURCE_DIR}")
- endif()
-
- message(STATUS "Crash reporting: sentry-native ${COCKATRICE_SENTRY_TAG} (backend: ${SENTRY_BACKEND})")
-else()
- message(STATUS "Crash reporting: SDK not vendored "
- "(WITH_CRASH_REPORTING=${WITH_CRASH_REPORTING}, WITH_CLIENT=${WITH_CLIENT})"
- )
-endif()
-
-# Packaged release binaries must not ship the DWARF that the symbol upload needs: it is
-# 80-90% of an unstripped binary and nobody downloads a 500 MB installer to get it. The
-# build tree keeps -g (CI uploads symbols from there); the *packaged copy* is split at
-# install time by a CPack install script, which extracts the debug info next to the staging
-# dir and strips the binary. Only ELF (Linux/BSD) needs this: Windows links /DEBUG with
-# separate PDBs, and macOS keeps its dSYM extracted from the bundle.
-option(COCKATRICE_SPLIT_DEBUG_SYMBOLS "Strip DWARF out of packaged release binaries" ON)
-set(COCKATRICE_DEBUG_SYMBOL_DIR
- "${CMAKE_BINARY_DIR}/debug-symbols"
- CACHE PATH "Where the packaged binaries' split debug information is written (kept out of the package)"
-)
-if(WITH_CRASH_REPORTING
- AND WITH_CLIENT
- AND COCKATRICE_SPLIT_DEBUG_SYMBOLS
- AND CMAKE_BUILD_TYPE STREQUAL "Release"
- AND UNIX
- AND NOT APPLE
-)
- find_program(COCKATRICE_OBJCOPY NAMES objcopy)
- find_program(COCKATRICE_STRIP_TOOL NAMES strip)
- if(COCKATRICE_OBJCOPY AND COCKATRICE_STRIP_TOOL)
- configure_file(
- "${CMAKE_CURRENT_LIST_DIR}/SplitDebugSymbols.cmake.in" "${CMAKE_BINARY_DIR}/cmake/SplitDebugSymbols.cmake" @ONLY
- )
- # PRE_BUILD runs after CPack stages the install tree and before the package is produced.
- # CPACK_INSTALL_SCRIPTS would run too early (staging is still empty); the script walks
- # CPACK_TEMPORARY_DIRECTORY, never CMAKE_INSTALL_PREFIX, and refuses to run otherwise.
- list(APPEND CPACK_PRE_BUILD_SCRIPTS "${CMAKE_BINARY_DIR}/cmake/SplitDebugSymbols.cmake")
- message(STATUS "Crash reporting: packaged binaries stripped, debug info split to ${COCKATRICE_DEBUG_SYMBOL_DIR}")
- else()
- message(WARNING "COCKATRICE_SPLIT_DEBUG_SYMBOLS is ON but objcopy/strip were not found; "
- "packaged release binaries will keep their debug information"
- )
- endif()
-endif()
-
-# Installer failure reporting (increment 4): parse COCKATRICE_SENTRY_DSN into the parts the Windows installer
-# needs. These are CPACK_* so CPack snapshots them at include(CPack) time and substitutes them into
-# cmake/NSIS.template.in at package time; on non-Windows they are inert (only the NSIS template consumes them).
-# Set from COCKATRICE_SENTRY_DSN on every configure, so a changed or removed DSN can never leave a stale value
-# behind. Plain variables, not CACHE: they only need to be in scope when include(CPack) later snapshots every
-# CPACK_* variable into CPackConfig.cmake.
-set(CPACK_SENTRY_ENABLED "0")
-set(CPACK_SENTRY_KEY "")
-set(CPACK_SENTRY_ENVELOPE_URL "")
-set(CPACK_SENTRY_DSN_FULL "")
-# Deliberately PROJECT_VERSION: that is what the client bakes as its Sentry release and what CI passes to
-# `sentry-cli debug-files upload`, so installer and client events group under one release. It is not the
-# CPACK_PACKAGE_VERSION the installer logs in its own log line — on Development tags PROJECT_VERSION carries a
-# "-