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
@@ -200,20 +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 [[ ${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")
@@ -228,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,
@@ -278,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")
@@ -311,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"
@@ -336,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
@@ -379,19 +305,10 @@ cmake .. "${flags[@]}"
echo "::endgroup::"
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"
@@ -467,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 64ea6baa7..412ad6a1d 100644
--- a/.ci/docker.sh
+++ b/.ci/docker.sh
@@ -175,12 +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
# shellcheck disable=2086
docker run "${args[@]}" $RUN_ARGS "$IMAGE_NAME" bash "$BUILD_SCRIPT" $RUN_OPTS "$@"
return $?
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 df054ea26..098aabc4f 100644
--- a/.github/workflows/desktop-build.yml
+++ b/.github/workflows/desktop-build.yml
@@ -46,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"
@@ -61,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"
@@ -101,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
@@ -222,7 +184,7 @@ jobs:
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
@@ -263,7 +225,7 @@ 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/
@@ -277,12 +239,6 @@ jobs:
SUFFIX: '-${{ matrix.distro }}${{ matrix.version }}'
package: '${{ matrix.package }}'
server_only: '${{ matrix.server_only }}'
- WITH_CRASH_REPORTING: '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=()
@@ -291,58 +247,9 @@ jobs:
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"
RUN --server --release --package "$package" "${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[@]}"
-
# 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' && steps.restore_ccache.outputs.cache-hit
@@ -360,7 +267,7 @@ jobs:
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
@@ -418,8 +325,6 @@ jobs:
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
xcode: "26.3"
- os: macOS
@@ -436,9 +341,6 @@ jobs:
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
@@ -455,8 +357,6 @@ jobs:
soc: Apple
type: Release
use_ccache: 1
- vcpkg_deployment_target: 14
- vcpkg_scope: 'vcpkg/macos14-arm64-xcode26.6/'
xcode: "26.6"
- os: macOS
@@ -470,33 +370,22 @@ jobs:
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]
+ needs: configure
runs-on: ${{ matrix.runner }}
timeout-minutes: 100
env:
@@ -587,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
@@ -634,11 +490,7 @@ 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 }}
@@ -649,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.
@@ -792,63 +517,6 @@ 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 && github.ref == 'refs/heads/master' && steps.restore_ccache.outputs.cache-hit
@@ -924,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
@@ -935,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
@@ -977,67 +629,3 @@ jobs:
BUILD_PATH: ${{ steps.build.outputs.path }}
GH_TOKEN: ${{ github.token }}
run: gh attestation verify "$BUILD_PATH" --repo Cockatrice/Cockatrice
-
- 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 10Gi 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.
- - name: "Delete unusable cache entries"
- shell: bash
- env:
- GH_TOKEN: ${{ github.token }}
- # Must list every vcpkg_scope of the build-vcpkg matrix 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/'
- 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} "
- deleted=0
- while IFS=$'\t' read -r id ref key accessed; do
- [[ -n $id ]] || continue
- # Only ever touch the key shapes this workflow creates, so that an entry of
- # some other workflow can never be deleted by accident
- case "$key" in
- vcpkg/* | ccache-* | Qt*) ;;
- *) continue ;;
- esac
-
- reason=""
- if [[ $ref != refs/heads/master ]]; then
- reason="scoped to $ref"
- 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/vcpkg-cache-import.yml b/.github/workflows/vcpkg-cache-import.yml
deleted file mode 100644
index 3cc7b2203..000000000
--- a/.github/workflows/vcpkg-cache-import.yml
+++ /dev/null
@@ -1,121 +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 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.
-#
-# 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.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/.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 8a25fe2c6..bee47972c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -90,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)
@@ -220,7 +220,6 @@ endif()
# when ENABLE_SANITIZERS or ENABLE_COVERAGE is on.
include(Sanitizers)
include(Coverage)
-include(CrashReporting)
# GNU systems need to define the Mersenne exponent for the RNG to compile w/o warning
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
@@ -250,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)
@@ -343,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)
@@ -352,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.
@@ -435,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)
@@ -449,13 +421,6 @@ 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})
@@ -468,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/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
-# "-