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 135: A Native X11 Build 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 sharp-runtime on their next branches).
SDL3 is CNA's default platform, and an excellent one. But a framework whose abstraction layer cannot be built without the thing it abstracts has not really abstracted it, so this snapshot adds CNA_ENABLE_SDL: a three-way switch that decides whether SDL is part of your build at all. 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.
For an SDL-free build select a native platform, audio and renderer, for example:
-DCNA_PLATFORM=X11 -DCNA_AUDIO_PLATFORM=NULL -DCNA_GRAPHICS_RENDERER=HEADLESS
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, SDL2 | X11, WAYLAND, WIN32, HEADLESS, TERMINAL |
CNA_AUDIO_PLATFORM | SDL3, SDL2 | NULL (silent, everywhere), ALSA (Linux only) |
CNA_GRAPHICS_RENDERER | SDL_RENDERER, SDL_GPU, FNA3D, FREEDIRECT — also when they appear only inside CNA_GRAPHICS_RENDERERS | The GL family (OPENGLES2, OPENGLES3, OPENGL33), OPENGL4, VULKAN, SOFTWARE, PORTABLEGL, HEADLESS, STUB, and the Windows-only DIRECTX9/DIRECTX11/DIRECTX12/DIRECT2D/GDI |
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, the SDL2 counterparts 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 (X11 or Xwayland) | X11 | NULL or ALSA | OPENGL33, VULKAN, SOFTWARE, HEADLESS (and, by the rules, the other non-SDL identities) | CI two jobs, on Xvfb |
| Linux (Wayland) | WAYLAND | NULL or ALSA | as above | Code + local suites, no CI |
| Windows (native or mingw-w64 cross) | WIN32 | NULL only — there is no SDL-free audio on Windows (WASAPI is reserved and refused) | DIRECTX9, DIRECTX11, DIRECTX12, DIRECT2D, GDI, GL family, VULKAN, CPU renderers | Platform harness only (mingw + Wine in CI, MSVC manual); the full engine is not built SDL-free by CI |
| Any POSIX host, no window | HEADLESS or TERMINAL | NULL | CPU renderers (TERMINAL: SOFTWARE, PORTABLEGL, HEADLESS, STUB only) | Selection logic; CI runs these platforms with SDL still configured |
| macOS, iOS, Android, Emscripten | SDL3 only (macOS can also use HEADLESS/TERMINAL without a window) | SDL3 | — | No native backend exists for these; ALSA is Linux-only |
Recipes
Three configurations, in decreasing order of evidence. The first two use the exact flags from CI.
# 1. SDL-free X11, headless renderer, silent audio (CI job "x11-sdl-free")
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug \
-DCNA_ENABLE_SDL=OFF -DCNA_PLATFORM=X11 \
-DCNA_GRAPHICS_RENDERER=HEADLESS -DCNA_AUDIO_PLATFORM=NULL \
-DCNA_ENABLE_NET=OFF
# 2. SDL-free X11 with real renderers and ALSA sound (CI job "x11-sdl-free-gpu")
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCNA_ENABLE_SDL=OFF -DCNA_PLATFORM=X11 \
-DCNA_GRAPHICS_RENDERER=OPENGL33 \
-DCNA_GRAPHICS_RENDERERS="OPENGL33;VULKAN;SOFTWARE;HEADLESS" \
-DCNA_AUDIO_PLATFORM=ALSA -DCNA_ENABLE_NET=OFF
# 3. SDL-free Windows from Linux with mingw-w64 (documented recipe; the full-engine
# build is not run by CI)
cmake -S . -B build-windows -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64.cmake \
-DCNA_ENABLE_SDL=OFF -DCNA_PLATFORM=WIN32 -DCNA_AUDIO_PLATFORM=NULL \
-DCNA_GRAPHICS_RENDERER=DIRECTX11 \
-DCNA_BUILD_TESTS=OFF -DCNA_BUILD_EXAMPLES=OFF
Recipe 1 is what CI builds along with the platform, audio, content and content-pipeline test suites and the cna_content library, 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.
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 '*SDL2*' -o -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: the native backend directories are scanned so that no SDL header can reach them, and a ratchet script (tools/platform/sdl_ratchet.py) denylists them from the exemption the rest of the platform module enjoys. 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 | X11 / 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 renderer that does not link SDL; on Windows, for example, DIRECTX11 or GDI. |
| “CNA_PLATFORM=X11 was requested but this machine cannot build it” | Install the X development packages named in the message. There is no fallback to SDL3. |
| “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. |