Tutorial 138: Building Without SDL

CNA Tutorials  ·  CNA snapshot c1c316b9

ℹ

What you’ll learn: what CNA_ENABLE_SDL does, which platform, audio and renderer selections need SDL, how to read the refusal message, the SDL-free recipes CI builds (and the ones it does not), and how to verify a binary is SDL-free.

ℹ

Before you start — Tutorial 137: Native Linux Audio with ALSA builds one SDL-free configuration end to end. This tutorial explains the rule that makes it possible, shows the refusals you will meet, and lays out what an SDL-free build looks like on each operating system. You need the workspace from Tutorial 02 (CNA and its siblings on the documented branch).

SDL3 is CNA's windowing platform: every window on every operating system is an SDL3 window. But not every CNA program needs a window — a game server, a test runner, a batch renderer or a terminal game does not — and a framework whose abstraction layer cannot be built without the thing it abstracts has not really abstracted it. CNA_ENABLE_SDL is a three-way switch that decides whether SDL is part of your build at all; switching it off gives a windowless build. Before it existed, even a configuration that referenced no SDL symbol anywhere still had to configure — and compile — SDL3, SDL3_image and SDL3_mixer.

The switch

ValueEffect
AUTO (default)SDL is configured exactly as before. Byte-for-byte the historical behaviour, so no existing build changes.
ONThe same, stated explicitly.
OFFSDL is not fetched, not built, not found and not linked. Any selection that genuinely requires SDL is refused at configure time, naming which one. Nothing is silently substituted.

The value is case-insensitive and also accepts TRUE/1 and FALSE/0/NO; anything else is an error. Note that AUTO does not mean “skip SDL if nothing needs it” — only OFF removes SDL.

Try it wrong first

The quickest way to learn the rule is to break it. From the cna directory:

cmake -S . -B build-nosdl -DCNA_ENABLE_SDL=OFF

Configuration stops early, before any SDL sub-build, with a message like this:

CMake Error at cmake/SdlAvailability.cmake:...
  CNA: CNA_ENABLE_SDL=OFF, but this configuration genuinely requires SDL: CNA_PLATFORM=SDL3, CNA_AUDIO_PLATFORM=SDL3.
  Nothing is substituted for it, deliberately -- silently swapping in another backend would build something other than what you asked for.
  An SDL-free build has no window: select a windowless platform, audio and renderer, for example:
    -DCNA_PLATFORM=HEADLESS -DCNA_AUDIO_PLATFORM=NULL -DCNA_GRAPHICS_RENDERER=HEADLESS
  (TERMINAL, SOFTWARE and Linux ALSA audio are SDL-free too). A windowed build -- on Windows, X11 or Wayland alike -- needs CNA_PLATFORM=SDL3.

The defaults for platform and audio are both SDL3, so switching SDL off requires you to choose both explicitly. Four kinds of selection need SDL:

AxisNeeds SDLSDL-free alternatives
CNA_PLATFORMSDL3 — the only implementation with a windowHEADLESS, TERMINAL (POSIX only)
CNA_AUDIO_PLATFORMSDL3NULL (silent, everywhere), ALSA (Linux only)
CNA_GRAPHICS_RENDERERSDL_RENDERER, SDL_GPU, FNA3D — also when they appear only inside CNA_GRAPHICS_RENDERERSHEADLESS, SOFTWARE, STUB; the GPU renderers do not link SDL themselves but need a window, which an SDL-free build cannot open
Any dependency asking for find_package(SDL3) and friendsBlocked for every project in the build treeAn optional lookup finds nothing; a REQUIRED one fails configure, naming the package

The last row exists because a sibling project's example directory once ran find_package(SDL3 QUIET), picked up an SDL3 installed in /usr/local, and quietly added an SDL-linked executable to an “SDL-free” build. With SDL off, SDL3, SDL3_image, SDL3_mixer, SDL3_ttf, SDL3_net and SDL itself are all marked not-findable, and CNA has a test (CnaSdlOffFindsNoSdlPackage) that keeps it so.

What an SDL-free build looks like on each OS

TargetPlatformAudioRenderersEvidence
LinuxHEADLESS or TERMINALNULL or ALSAHEADLESS, SOFTWARE, STUB (TERMINAL displays SOFTWARE’s frames)CI Headless + Headless + ALSA, with the platform, audio, content and content-pipeline suites and a 2D demo smoke run
Other POSIX hosts (macOS, BSD)HEADLESS or TERMINALNULLas aboveSelection logic; not built SDL-free by CI
Windows (native or mingw-w64 cross)HEADLESSNULL only — there is no SDL-free audio on Windows (WASAPI is reserved and refused)HEADLESS, SOFTWARE, STUBSelection logic; not built SDL-free by CI
Any windowed game, any OSSDL3SDL3, or ALSA on LinuxanyNot SDL-free by definition: SDL3 opens the window

Recipes

Two configurations; the first is the exact one CI builds.

# 1. SDL-free Headless with ALSA sound through CNA's own mixer (CI job "sdl-free")
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug \
  -DCNA_ENABLE_SDL=OFF -DCNA_PLATFORM=HEADLESS \
  -DCNA_GRAPHICS_RENDERER=HEADLESS -DCNA_AUDIO_PLATFORM=ALSA \
  -DCNA_BUILD_TESTS=ON -DCNA_ENABLE_NET=OFF

# 2. An SDL-free terminal game: CPU pixels shown as character cells (POSIX only)
cmake -S . -B build-terminal -G Ninja \
  -DCNA_ENABLE_SDL=OFF -DCNA_PLATFORM=TERMINAL \
  -DCNA_GRAPHICS_RENDERER=SOFTWARE -DCNA_AUDIO_PLATFORM=NULL

Recipe 1 is what CI builds along with the platform, audio, content and content-pipeline test suites, the cna_content library and the 2D demo, so the whole content side of CNA is SDL-free too. WAV decoding for content is CNA's own code (DecodeWavToPcm16) and is compiled whatever the audio platform, which is exactly what lets cna_content link without SDL. Recipe 2 is the configuration of Tutorial 139; CNA’s recorded validation built it SDL-free and ran the platform suite and the terminal demo integration test, while CI runs the Terminal platform with SDL still configured.

The configure log confirms the switch:

-- CNA: SDL is NOT configured (CNA_ENABLE_SDL=OFF). No SDL source is fetched, built, found or linked by this configuration.

Contrast -DCNA_ENABLE_SDL=ON (or AUTO): the same line is absent, and the first configure spends its time building the vendored SDL3 family into .sdl-prebuilt-*.

Prove it

cmake --build build --target cna_demo_2d --parallel

ldd build/cna_demo_2d | grep -i sdl                        # nothing
readelf -d build/cna_demo_2d | grep -i 'NEEDED.*sdl'      # nothing
nm -D --undefined-only build/cna_demo_2d | grep ' SDL_'   # nothing
find build -iname '*SDL3*'                                # no SDL artifact in the build tree
grep -E '^SDL[23]_DIR:PATH=' build/CMakeCache.txt | grep -v NOTFOUND   # no package was found

CI applies these same checks (and asserts libasound is not NEEDED for the ALSA build). CNA also enforces the boundary in source: inventory and classification gates (tools/platform/sdl_inventory.py, sdl_classify.py, and the ratchet sdl_ratchet.py) keep SDL confined to the SDL3 platform, the SDL renderers and an explicit budget elsewhere. If you have tests on, ctest -R CnaSdlOffFindsNoSdlPackage checks the find-package blocking.

The four-cell matrix CI runs

The workflow job sdl-enable-matrix configures (without building) four selections to pin the switch's contract:

CNA_ENABLE_SDLPlatform / audio / rendererExpected
AUTOSDL3 / SDL3 / HEADLESSConfigure succeeds
ONSDL3 / SDL3 / HEADLESSConfigure succeeds
OFFHEADLESS / NULL / HEADLESSConfigure succeeds
OFFSDL3 / SDL3 / HEADLESSMust fail with “genuinely requires SDL”

Using it from your own project

The switch is an ordinary cache variable, so a consumer project passes it on the command line (or sets it, together with the platform and audio values, before add_subdirectorying CNA). Your game code does not change: the platform is a build-time choice, and there is no CNA_PLATFORM environment variable. Only SDL-dependent your own code (for example a direct #include <SDL3/SDL.h>) would stop working, and with SDL off find_package(SDL3) in your own CMake will find nothing either.

Troubleshooting

SymptomCause and fix
“genuinely requires SDL: CNA_AUDIO_PLATFORM=SDL3”You left audio at its default. Pass -DCNA_AUDIO_PLATFORM=NULL or ALSA.
“... CNA_GRAPHICS_RENDERER=SDL_RENDERER” (on Windows, macOS, Android, iOS the default renderer is SDL_RENDERER)Choose a windowless renderer: HEADLESS, SOFTWARE or STUB.
“genuinely requires SDL: CNA_PLATFORM=SDL3”You need a window; an SDL-free build cannot open one. Leave CNA_ENABLE_SDL at AUTO, or choose HEADLESS or TERMINAL.
“CNA_AUDIO_PLATFORM=ALSA needs Linux”ALSA is Linux-only. Use NULL elsewhere.
A sibling project fails to configure, naming SDL3With the switch off, find_package for SDL packages is blocked everywhere. Build that project's SDL-free configuration or leave CNA_ENABLE_SDL at AUTO.
You want sound on Windows or macOS without SDLNot available: OPENAL and WASAPI are reserved and refused. Keep SDL3 audio there.

Where to go next