Tutorial 138: Building Without SDL
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
| Value | Effect |
|---|---|
AUTO (default) | SDL is configured exactly as before. Byte-for-byte the historical behaviour, so no existing build changes. |
ON | The same, stated explicitly. |
OFF | SDL 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:
| Axis | Needs SDL | SDL-free alternatives |
|---|---|---|
CNA_PLATFORM | SDL3 — the only implementation with a window | HEADLESS, TERMINAL (POSIX only) |
CNA_AUDIO_PLATFORM | SDL3 | NULL (silent, everywhere), ALSA (Linux only) |
CNA_GRAPHICS_RENDERER | SDL_RENDERER, SDL_GPU, FNA3D — also when they appear only inside CNA_GRAPHICS_RENDERERS | HEADLESS, 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 friends | Blocked for every project in the build tree | An 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
| Target | Platform | Audio | Renderers | Evidence |
|---|---|---|---|---|
| Linux | HEADLESS or TERMINAL | NULL or ALSA | HEADLESS, 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 TERMINAL | NULL | as above | Selection logic; not built SDL-free by CI |
| Windows (native or mingw-w64 cross) | HEADLESS | NULL only — there is no SDL-free audio on Windows (WASAPI is reserved and refused) | HEADLESS, SOFTWARE, STUB | Selection logic; not built SDL-free by CI |
| Any windowed game, any OS | SDL3 | SDL3, or ALSA on Linux | any | Not 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_SDL | Platform / audio / renderer | Expected |
|---|---|---|
AUTO | SDL3 / SDL3 / HEADLESS | Configure succeeds |
ON | SDL3 / SDL3 / HEADLESS | Configure succeeds |
OFF | HEADLESS / NULL / HEADLESS | Configure succeeds |
OFF | SDL3 / SDL3 / HEADLESS | Must 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
| Symptom | Cause 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 SDL3 | With 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 SDL | Not available: OPENAL and WASAPI are reserved and refused. Keep SDL3 audio there. |
Where to go next
- Tutorial 139: The Terminal Platform — an SDL-free game with a picture
- Tutorial 135: Running on X11 through SDL3 and Tutorial 136: Running on Wayland through SDL3 — the windowed alternative
- Tutorial 137: Native Linux Audio with ALSA
- Tutorial 127: Choose Platform, Renderer, and Audio Independently — every combination rule.
- Native Platforms — Building without SDL