mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -08:00
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / Prune caches (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [CI] Key the vcpkg binary cache per ABI and let pull requests heal it The cache had no prefix, and TAServers/vcpkg-cache restores every entry it finds under the prefix rather than only the hashes vcpkg asks for. With all legs sharing the single vcpkg/ namespace, each of the five of them downloads all 66 archives of all five on every run. Each restore also refreshes the entry's last access date, so a dead ABI generation stays fresh forever and GitHub's least recently used eviction can never reach it. Name the scope in the key after the platform, target and toolchain it belongs to, so a leg restores only its own archives. macOS 14, 15 and 26 on the same Xcode build identical binaries when they share the lowest deployment target, so point all three at 14 and let them share one scope instead of three. A pull request cannot write the shared cache either. Its entries are scoped to refs/pull/<n>/merge, so nothing it saves is ever readable again, and letting it write anyway only burns the repository's quota, which is what the previous attempt at this did. Keep the build read-only and hand the archives it had to build itself to a new workflow running in the default branch scope, which saves them where master can restore them. Forks stay read-only, they are the one case whose archives must not be trusted. Because the prefix changes every key, add a job that deletes the entries of retired scopes, which is the one-time clean up of the current flat namespace, and the per-run ones pull requests leave behind, which age based eviction also never reaches. Also pin the Linux ccache cache to its own subdirectory, since the directory is shared with the docker build context, and drop the clang-format install from the build images, which format nothing at runtime. * [CI] Create the vcpkg cache directory when a scope is still empty * [CI] Let a pull request write the vcpkg archives it builds * [CI] Upload the vcpkg handover as one archive instead of loose files * [CI] Hand the vcpkg cache over from one leg per scope * [CI] Gate vcpkg cache writes to master pushes --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
793 lines
33 KiB
YAML
793 lines
33 KiB
YAML
name: Build Desktop
|
|
|
|
permissions:
|
|
actions: write # needed to delete entries in GHA cache (update ccache)
|
|
attestations: write # needed to persist the attestation.
|
|
contents: write # needed for e.g. vcpkg dependency graph updates
|
|
id-token: write # needed for signing certificate in attestation
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- master
|
|
paths:
|
|
- '*/**' # matches all files not in root
|
|
- '!**.md'
|
|
- '!.github/**'
|
|
- '!.tx/**'
|
|
- '!doc/**'
|
|
- '.github/workflows/desktop-build.yml'
|
|
- 'CMakeLists.txt'
|
|
- 'vcpkg.json'
|
|
- 'vcpkg' # needed to match submodule bumps (gitlink)
|
|
tags:
|
|
- '*'
|
|
pull_request:
|
|
paths:
|
|
- '*/**' # matches all files not in root
|
|
- '!**.md'
|
|
- '!.github/**'
|
|
- '!.tx/**'
|
|
- '!doc/**'
|
|
- '.github/workflows/desktop-build.yml'
|
|
- 'CMakeLists.txt'
|
|
- 'vcpkg.json'
|
|
- 'vcpkg' # needed to match submodule bumps (gitlink)
|
|
|
|
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on tag --> release)
|
|
concurrency:
|
|
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
|
|
cancel-in-progress: ${{ github.ref_type != 'tag' }}
|
|
|
|
jobs:
|
|
configure:
|
|
name: Configure
|
|
runs-on: ubuntu-slim # https://github.com/actions/runner-images/blob/main/images/ubuntu-slim/ubuntu-slim-Readme.md
|
|
outputs:
|
|
tag: ${{ steps.configure.outputs.tag }}
|
|
sha: ${{ steps.configure.outputs.sha }}
|
|
|
|
steps:
|
|
- name: "Configure"
|
|
env:
|
|
RESOLVED_SHA: ${{ case(github.event_name == 'pull_request', github.event.pull_request.head.sha, github.sha) }}
|
|
id: configure
|
|
shell: bash
|
|
run: |
|
|
if [[ "$GITHUB_REF_TYPE" == 'tag' ]]; then # release
|
|
echo "tag=$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
echo "sha=$RESOLVED_SHA" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: "Checkout"
|
|
if: steps.configure.outputs.tag != null
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 0 # fetch all history for all branches and tags
|
|
|
|
- name: "Prepare release parameters"
|
|
id: prepare
|
|
if: steps.configure.outputs.tag != null
|
|
shell: bash
|
|
env:
|
|
TAG: ${{ steps.configure.outputs.tag }}
|
|
run: .ci/prep_release.sh
|
|
|
|
- name: "Create release"
|
|
if: steps.configure.outputs.tag != null
|
|
id: create_release
|
|
shell: bash
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
tag_name: ${{ steps.configure.outputs.tag }}
|
|
target: ${{ steps.configure.outputs.sha }}
|
|
release_name: ${{ steps.prepare.outputs.title }}
|
|
body_path: ${{ steps.prepare.outputs.body_path }}
|
|
prerelease: ${{ steps.prepare.outputs.is_beta }}
|
|
run: |
|
|
args=()
|
|
[[ $prerelease == yes ]] && args+=(--prerelease)
|
|
|
|
gh release create "$tag_name" --verify-tag --draft "${args[@]}" \
|
|
--target "$target" \
|
|
--title "$release_name" \
|
|
--notes-file "$body_path"
|
|
|
|
build-linux:
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
# The files in ".ci/$distro$version" correspond to the values given here
|
|
include:
|
|
- distro: Arch
|
|
|
|
allow-failure: yes
|
|
package: skip # We are packaged in Arch already
|
|
|
|
- distro: Servatrice_Debian
|
|
version: 12
|
|
|
|
package: DEB
|
|
server_only: yes
|
|
test: skip
|
|
|
|
- distro: Debian
|
|
version: 12
|
|
|
|
package: DEB
|
|
test: skip # Running tests on all distros is superfluous
|
|
|
|
- distro: Debian
|
|
version: 13
|
|
|
|
package: DEB
|
|
|
|
- distro: Fedora
|
|
version: 43
|
|
|
|
package: RPM
|
|
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
|
|
|
|
# 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: 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
|
|
|
|
name: ${{ matrix.distro }} ${{ matrix.version }}${{ matrix.variant && format(' {0}', matrix.variant) || '' }}
|
|
needs: configure
|
|
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
|
|
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 }}
|
|
|
|
steps:
|
|
- name: "Checkout"
|
|
uses: actions/checkout@v7
|
|
|
|
- name: "Restore compiler cache (ccache)"
|
|
id: restore_ccache
|
|
uses: actions/cache/restore@v6
|
|
with:
|
|
key: ccache-${{ matrix.distro }}${{ matrix.version }}${{ matrix.variant }}
|
|
path: ${{ env.CACHE }}/.ccache
|
|
|
|
- name: "Build ${{ matrix.distro }} ${{ matrix.version }} Docker image"
|
|
shell: bash
|
|
run: source .ci/docker.sh --build
|
|
|
|
- name: "Build debug and test"
|
|
if: matrix.test != 'skip'
|
|
shell: bash
|
|
env:
|
|
sanitize: '${{ matrix.sanitize }}'
|
|
coverage: '${{ matrix.coverage }}'
|
|
run: |
|
|
source .ci/docker.sh
|
|
args=()
|
|
[[ $GITHUB_REF == "refs/heads/master" ]] && args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
|
|
args+=(--ccache "$CCACHE_SIZE")
|
|
args+=(--cmake-generator "$CMAKE_GENERATOR")
|
|
|
|
extra=()
|
|
[[ $sanitize == yes ]] && extra+=(--sanitize)
|
|
[[ $coverage == yes ]] && extra+=(--coverage)
|
|
|
|
RUN --server --debug --test "${extra[@]}" "${args[@]}"
|
|
|
|
# RUN bind-mounts the workspace into the container, so the report the build wrote into
|
|
# build/ is already on the host and no second container is needed to collect it.
|
|
- name: "Collect coverage report"
|
|
if: matrix.coverage == 'yes'
|
|
shell: bash
|
|
run: |
|
|
mkdir -p coverage
|
|
# --html-details writes one page per source file plus a stylesheet and script, and the
|
|
# index links to all of them by relative name. Copying the index alone would publish a
|
|
# report where every link 404s, so the whole family has to travel together.
|
|
cp build/coverage-details* coverage/
|
|
cp build/coverage.lcov build/coverage.xml build/coverage-summary.json coverage/
|
|
ls -la coverage/
|
|
|
|
- name: "Upload coverage artifact"
|
|
if: matrix.coverage == 'yes'
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: coverage-report
|
|
path: coverage/
|
|
if-no-files-found: warn
|
|
|
|
- name: "Build release package"
|
|
id: build
|
|
if: matrix.package != 'skip'
|
|
shell: bash
|
|
env:
|
|
SUFFIX: '-${{ matrix.distro }}${{ matrix.version }}'
|
|
package: '${{ matrix.package }}'
|
|
server_only: '${{ matrix.server_only }}'
|
|
run: |
|
|
source .ci/docker.sh
|
|
args=()
|
|
[[ $server_only == yes ]] && args+=(--no-client)
|
|
[[ $GITHUB_REF == "refs/heads/master" ]] && args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
|
|
args+=(--ccache "$CCACHE_SIZE")
|
|
args+=(--cmake-generator "$CMAKE_GENERATOR")
|
|
args+=(--suffix "$SUFFIX")
|
|
|
|
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' && steps.restore_ccache.outputs.cache-hit
|
|
continue-on-error: true
|
|
env:
|
|
CACHE_PRIMARY_KEY: ${{ steps.restore_ccache.outputs.cache-primary-key }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
if gh cache delete --repo "$GITHUB_REPOSITORY" "$CACHE_PRIMARY_KEY"; then
|
|
echo "Cache deleted successfully"
|
|
fi
|
|
|
|
- name: "Save updated compiler cache (ccache)"
|
|
if: github.ref == 'refs/heads/master'
|
|
uses: actions/cache/save@v6
|
|
with:
|
|
key: ${{ steps.restore_ccache.outputs.cache-primary-key }}
|
|
path: ${{ env.CACHE }}/.ccache
|
|
|
|
- name: "Upload artifact"
|
|
id: upload_artifact
|
|
if: matrix.package != 'skip'
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
archive: false
|
|
if-no-files-found: error
|
|
path: ${{ steps.build.outputs.path }}
|
|
|
|
- name: "Upload to release"
|
|
id: upload_release
|
|
if: matrix.package != 'skip' && needs.configure.outputs.tag != null
|
|
shell: bash
|
|
env:
|
|
asset_name: ${{ steps.build.outputs.fullname }}
|
|
asset_path: ${{ steps.build.outputs.path }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
tag_name: ${{ needs.configure.outputs.tag }}
|
|
run: gh release upload "$tag_name" "$asset_path#$asset_name"
|
|
|
|
- name: "Attest binary provenance"
|
|
id: attestation
|
|
if: steps.upload_release.outcome == 'success'
|
|
uses: actions/attest@v4
|
|
with:
|
|
show-summary: false
|
|
subject-path: ${{ steps.build.outputs.path }}
|
|
|
|
- name: "Verify binary attestation"
|
|
if: steps.attestation.outcome == 'success'
|
|
shell: bash
|
|
env:
|
|
BUILD_PATH: ${{ steps.build.outputs.path }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: gh attestation verify "$BUILD_PATH" --repo Cockatrice/Cockatrice
|
|
|
|
build-vcpkg:
|
|
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
|
|
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"
|
|
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
|
|
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"
|
|
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: 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
|
|
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
|
|
|
|
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
|
|
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
|
|
runs-on: ${{ matrix.runner }}
|
|
timeout-minutes: 100
|
|
env:
|
|
CCACHE_DIR: ${{ github.workspace }}/.cache/
|
|
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
|
|
|
|
steps:
|
|
- name: "Checkout"
|
|
uses: actions/checkout@v7
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: "[Windows] Add msbuild to PATH"
|
|
if: matrix.os == 'Windows'
|
|
uses: microsoft/setup-msbuild@v3
|
|
with:
|
|
msbuild-architecture: x64
|
|
|
|
- name: "[macOS] Setup ccache"
|
|
if: matrix.os == 'macOS' && matrix.use_ccache == 1
|
|
run: brew install ccache
|
|
|
|
- name: "[macOS] Restore compiler cache (ccache)"
|
|
if: matrix.os == 'macOS' && matrix.use_ccache == 1
|
|
id: restore_ccache
|
|
uses: actions/cache/restore@v6
|
|
with:
|
|
key: ccache-${{ matrix.runner }}_${{ matrix.override_target }}-Xcode${{ matrix.xcode }}
|
|
path: ${{ env.CCACHE_DIR }}
|
|
|
|
- name: "[macOS] Install aqtinstall"
|
|
if: matrix.os == 'macOS'
|
|
run: pipx install aqtinstall
|
|
|
|
# Resolve given wildcard versions (e.g. Qt 6.6.*) to latest version via aqtinstall to avoid stale caches on new releases
|
|
- name: "[macOS] Resolve latest Qt from ${{ matrix.qt_version }} input"
|
|
if: matrix.os == 'macOS'
|
|
env:
|
|
QT_VERSION: ${{ matrix.qt_version }}
|
|
id: resolve_qt_version
|
|
shell: bash
|
|
run: .ci/resolve_latest_aqt_qt_version.sh "$QT_VERSION"
|
|
|
|
- name: "[macOS] Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }}"
|
|
if: matrix.os == 'macOS'
|
|
id: restore_qt
|
|
uses: actions/cache/restore@v6
|
|
with:
|
|
key: Qt-${{ steps.resolve_qt_version.outputs.version }}-macOS-${{ matrix.soc }}-${{ matrix.qt_modules }}-thin
|
|
path: ${{ github.workspace }}/Qt
|
|
|
|
# Using jurplel/install-qt-action to install Qt without using brew
|
|
# Qt build using vcpkg either just fails or takes too long to build
|
|
- name: "[macOS] Install fat Qt ${{ matrix.qt_version }}"
|
|
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
|
|
uses: jurplel/install-qt-action@v4
|
|
with:
|
|
cache: false
|
|
# cache-key-prefix: Qt
|
|
dir: ${{ github.workspace }} # thinning script depends on this location
|
|
modules: ${{ matrix.qt_modules }}
|
|
version: ${{ matrix.qt_version }}
|
|
|
|
- name: "[macOS] Create thin Qt libraries"
|
|
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
|
|
run: .ci/thin_macos_qtlib.sh
|
|
|
|
- name: "[macOS] Cache thin Qt libraries"
|
|
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true' && github.ref == 'refs/heads/master'
|
|
uses: actions/cache/save@v6
|
|
with:
|
|
key: ${{ steps.restore_qt.outputs.cache-primary-key }}
|
|
path: ${{ github.workspace }}/Qt
|
|
|
|
- name: "[Windows] Install Qt ${{ matrix.qt_version }}"
|
|
if: matrix.os == 'Windows'
|
|
uses: jurplel/install-qt-action@v4
|
|
with:
|
|
# Qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released
|
|
aqtsource: git+https://github.com/miurahr/aqtinstall.git
|
|
cache: ${{ github.ref == 'refs/heads/master' }}
|
|
cache-key-prefix: Qt
|
|
modules: ${{ matrix.qt_modules }}
|
|
version: ${{ matrix.qt_version }}
|
|
|
|
- name: "[Windows] Install NSIS"
|
|
if: matrix.os == 'Windows'
|
|
shell: bash
|
|
run: choco install nsis
|
|
|
|
- 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.
|
|
- name: "Create vcpkg cache directory"
|
|
shell: bash
|
|
env:
|
|
CACHE_DIR: '${{ steps.vcpkg-cache.outputs.path }}'
|
|
run: mkdir -p "$CACHE_DIR"
|
|
|
|
# Uses environment variables, see compile.sh for more details
|
|
- name: "Build Cockatrice"
|
|
id: build
|
|
shell: bash
|
|
env:
|
|
BUILDTYPE: '${{ matrix.type }}'
|
|
CCACHE_EVICTION_AGE: ${{ matrix.ccache_eviction_age }}
|
|
CMAKE_GENERATOR: ${{ matrix.cmake_generator }}
|
|
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 }}
|
|
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
|
|
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
|
|
MAKE_PACKAGE: '${{ matrix.make_package }}'
|
|
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 }}
|
|
run: .ci/compile.sh --server --test --vcpkg
|
|
|
|
# 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.
|
|
# Dump it, or a broken installer script is undiagnosable from CI alone.
|
|
# shell: bash matters here - the default pwsh resolves `find` to Windows'
|
|
# text-search find.exe, which errors instead of searching.
|
|
- name: "Dump NSIS compile log"
|
|
if: failure() && matrix.os == 'Windows'
|
|
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/<n>/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
|
|
continue-on-error: true
|
|
env:
|
|
CACHE_PRIMARY_KEY: ${{ steps.restore_ccache.outputs.cache-primary-key }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
if gh cache delete --repo "$GITHUB_REPOSITORY" "$CACHE_PRIMARY_KEY"; then
|
|
echo "Cache deleted successfully"
|
|
fi
|
|
|
|
- name: "[macOS] Cache updated compiler cache (ccache)"
|
|
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 }}
|
|
path: ${{ env.CCACHE_DIR }}
|
|
|
|
- name: "[macOS] Sign app bundle"
|
|
if: matrix.os == 'macOS' && matrix.make_package && needs.configure.outputs.tag != null
|
|
id: sign_macos
|
|
env:
|
|
BUILD_PATH: ${{ steps.build.outputs.path }}
|
|
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
|
|
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
|
|
run: |
|
|
if [[ -n "$MACOS_CERTIFICATE_NAME" ]]
|
|
then
|
|
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
|
|
/usr/bin/codesign --sign="$MACOS_CERTIFICATE_NAME" --entitlements=".ci/macos.entitlements" --options=runtime --force --deep --timestamp --verbose "$BUILD_PATH"
|
|
fi
|
|
|
|
- name: "[macOS] Notarize app bundle"
|
|
if: matrix.os == 'macOS' && steps.sign_macos.outcome == 'success'
|
|
env:
|
|
BUILD_PATH: ${{ steps.build.outputs.path }}
|
|
MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
|
|
MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
|
|
MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
|
|
run: |
|
|
if [[ -n "$MACOS_NOTARIZATION_APPLE_ID" ]]
|
|
then
|
|
# Store the notarization credentials so that we can prevent a UI password dialog from blocking the CI
|
|
echo "Create keychain profile"
|
|
xcrun notarytool store-credentials "notarytool-profile" --apple-id "$MACOS_NOTARIZATION_APPLE_ID" --team-id "$MACOS_NOTARIZATION_TEAM_ID" --password "$MACOS_NOTARIZATION_PWD"
|
|
|
|
# We can't notarize an app bundle directly, but we need to compress it as an archive.
|
|
# Therefore, we create a zip file containing our app bundle, so that we can send it to the
|
|
# notarization service
|
|
echo "Creating temp notarization archive"
|
|
ditto -c -k --keepParent "$BUILD_PATH" "notarization.zip"
|
|
|
|
# Here we send the notarization request to the Apple's Notarization service, waiting for the result.
|
|
# This typically takes a few seconds inside a CI environment, but it might take more depending on the App
|
|
# characteristics. Visit the Notarization docs for more information and strategies on how to optimize it if
|
|
# you're curious
|
|
echo "Notarize app"
|
|
xcrun notarytool submit "notarization.zip" --keychain-profile "notarytool-profile" --wait
|
|
|
|
# Finally, we need to "attach the staple" to our executable, which will allow our app to be
|
|
# validated by macOS even when an internet connection is not available.
|
|
echo "Attach staple"
|
|
xcrun stapler staple "$BUILD_PATH"
|
|
fi
|
|
|
|
- name: "Upload artifact"
|
|
if: matrix.make_package
|
|
id: upload_artifact
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
archive: false
|
|
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
|
|
shell: bash
|
|
env:
|
|
asset_name: ${{ steps.build.outputs.fullname }}
|
|
asset_path: ${{ steps.build.outputs.path }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
tag_name: ${{ needs.configure.outputs.tag }}
|
|
run: gh release upload "$tag_name" "$asset_path#$asset_name"
|
|
|
|
- name: "Attest binary provenance"
|
|
if: steps.upload_release.outcome == 'success'
|
|
id: attestation
|
|
uses: actions/attest@v4
|
|
with:
|
|
show-summary: false
|
|
subject-path: ${{ steps.build.outputs.path }}
|
|
|
|
- name: "Verify binary attestation"
|
|
if: steps.attestation.outcome == 'success'
|
|
shell: bash
|
|
env:
|
|
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
|
|
# "<scope>/<abi hash>", 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"
|