Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,24 @@ on:
push:
branches: [master]
pull_request:
workflow_dispatch:

jobs:
linux:
name: linux (${{ matrix.image }})
runs-on: ubuntu-latest
container: ${{ matrix.image }}
env:
CCACHE: 1
CCACHE_DIR: /ccache
# Hash the compiler binary instead of trusting its mtime, so a g++ point
# release landing inside the base image cannot reuse the old one's
# entries. CCACHE_SLOPPINESS is deliberately left unset: include_file_mtime
# and include_file_ctime would make ccache trust timestamps over content,
# and time_macros would let it cache the __DATE__/__TIME__ build banner in
# GlobalContainerArgs.cpp.
CCACHE_COMPILERCHECK: content
CCACHE_MAXSIZE: 1G
strategy:
fail-fast: false
matrix:
Expand All @@ -25,7 +37,7 @@ jobs:
run: |
apt-get update -qq
apt-get install -y --no-install-recommends \
ca-certificates git g++ python3 scons pkg-config \
ca-certificates git g++ python3 scons pkg-config ccache \
libsdl2-dev libsdl2-image-dev libsdl2-net-dev libsdl2-ttf-dev \
libvorbis-dev libogg-dev libspeex-dev \
libboost-date-time-dev libboost-thread-dev libboost-system-dev \
Expand All @@ -35,6 +47,16 @@ jobs:

- uses: actions/checkout@v4

- name: Restore the compiler cache
uses: actions/cache/restore@v4
with:
path: /ccache
key: ccache-${{ matrix.image }}-${{ github.run_id }}
restore-keys: ccache-${{ matrix.image }}-

- name: Reset the cache statistics
run: ccache -z

- name: Build glob2
run: scons -j$(nproc) release=1

Expand Down Expand Up @@ -96,6 +118,24 @@ jobs:
scons -j$(nproc) release=1 server=0 speed-tests
xvfb-run -a python3 test/run-game-speed-tests.py

# A run that touched a widely included header and still shows near-total
# direct hits means the cache is not seeing the change.
- name: Report the cache statistics
if: always()
run: ccache -s -v

# Only master writes. PR branches cannot read each other's caches anyway,
# and letting every PR save would churn the repository's cache quota and
# evict the entries PRs restore from. Saved even when a step failed: the
# entries are content-addressed, so objects compiled during a run whose
# tests failed are still the right objects.
- name: Save the compiler cache
if: ${{ !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: actions/cache/save@v4
with:
path: /ccache
key: ccache-${{ matrix.image }}-${{ github.run_id }}

windows:
name: windows (mingw-w64)
runs-on: windows-latest
Expand Down
9 changes: 9 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ Options are cached in `options_cache.py`, so `release=1` and `server=1` stick un

**`DET_INIT` — hunting uninitialized-read nondeterminism.** Valgrind/MSan don't work on modern macOS. Instead build twice, `DET_INIT=zero scons` and `DET_INIT=pattern scons`, and hammer one seed serially per build (add `MallocPreScribble=1 MallocScribble=1` to scribble the heap). If each build is internally stable but the two disagree, an uninitialized stack read is confirmed; if a scribbled build is still unstable run-to-run, the cause is not uninitialized memory. The env var is invisible to scons's dependency tracking, so rebuild affected objects when toggling.

**`CCACHE=1`** wraps the C/C++ compilers in ccache (`scons/ccache.py`, used by
both this SConstruct and `test/SConstruct`). Opt-in via the environment rather
than a scons option so it never sticks in `options_cache.py`. Leave it unset
when regenerating `compile_commands.json`, which would otherwise record the
wrapped command line. Do not set `CCACHE_SLOPPINESS`: `include_file_mtime` and
`include_file_ctime` make ccache trust timestamps over content, and
`time_macros` lets it cache the `__DATE__`/`__TIME__` banner in
`GlobalContainerArgs.cpp`.

**Dependencies:** SDL2, SDL2_net, SDL2_ttf, SDL2_image, libvorbis, libogg, speex, OpenGL, GLU, libepoxy, Boost date_time, zlib, fribidi, pcre. Optional: portaudio. See `vcpkg.json`.

## Tests
Expand Down
7 changes: 7 additions & 0 deletions SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import os
import glob
sys.path.append( os.path.abspath("scons") )
import bundle
import ccache
import dmg
import nsis

Expand Down Expand Up @@ -271,6 +272,12 @@ def main():
env['CXX'] = 'x86_64-w64-mingw32-g++'
env['AR'] = 'x86_64-w64-mingw32-ar'
env['RANLIB'] = 'x86_64-w64-mingw32-ranlib'

# Compiler cache. Done here so it wraps whichever compiler the mingw
# branches above settled on, and before configure() runs its CheckCC /
# CheckCXX probes against the same command line.
if ccache.enabled():
ccache.enable(env)



Expand Down
35 changes: 35 additions & 0 deletions scons/ccache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Optional ccache wrapper for the C/C++ compilers.

Enabled by setting CCACHE=1 in the environment. An env var rather than a scons
option so it never lands in options_cache.py and silently sticks across later
builds. Note that compile_commands.json records the wrapped command line, so
leave CCACHE unset when regenerating it for tools/remove-unused-includes.py.
"""

import os
import shutil

# ccache settings forwarded into the environment scons scrubs for build
# commands. Deliberately absent: CCACHE_SLOPPINESS. Marking include_file_mtime
# or include_file_ctime sloppy makes ccache trust timestamps over content, and
# time_macros would let it cache the __DATE__/__TIME__ build banner in
# GlobalContainerArgs.cpp. Both trade correctness for hit rate.
_FORWARDED = ('CCACHE_DIR', 'CCACHE_COMPILERCHECK', 'CCACHE_MAXSIZE',
'CCACHE_DISABLE', 'CCACHE_LOGFILE', 'HOME')


def enabled():
return bool(os.environ.get('CCACHE'))


def enable(env):
"""Prefix env's compilers with ccache. Call once CC and CXX are final."""
binary = shutil.which('ccache')
if not binary:
raise SystemExit("CCACHE is set but ccache was not found on PATH")
for var in ('CC', 'CXX'):
if env.get(var) and not env[var].startswith(binary):
env[var] = binary + ' ' + env[var]
for var in _FORWARDED:
if var in os.environ:
env['ENV'][var] = os.environ[var]
9 changes: 9 additions & 0 deletions test/SConstruct
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import os
import sys

sys.path.append( os.path.abspath("../scons") )
import ccache

env = Environment()

# Compiler cache, shared with the top-level build. Set before the Clone()s
# below so every harness environment inherits it.
if ccache.enabled():
ccache.enable(env)

# Shared include paths so tests can compile against game headers.
# echo/Echo.h transitively pulls in Map.h / Player.h / Team.h / etc.,
# which need libgag and SDL2 headers visible at parse time even when the
Expand Down
Loading