Tutorial 138: Building Without SDL

CNA Tutorials  ·  CNA snapshot 009d40f5

ℹ

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

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.
  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:

AxisNeeds SDLSDL-free alternatives
CNA_PLATFORMSDL3, SDL2X11, WAYLAND, WIN32, HEADLESS, TERMINAL
CNA_AUDIO_PLATFORMSDL3, SDL2NULL (silent, everywhere), ALSA (Linux only)
CNA_GRAPHICS_RENDERERSDL_RENDERER, SDL_GPU, FNA3D, FREEDIRECT — also when they appear only inside CNA_GRAPHICS_RENDERERSThe 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 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, 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

TargetPlatformAudioRenderersEvidence
Linux (X11 or Xwayland)X11NULL or ALSAOPENGL33, VULKAN, SOFTWARE, HEADLESS (and, by the rules, the other non-SDL identities)CI two jobs, on Xvfb
Linux (Wayland)WAYLANDNULL or ALSAas aboveCode + local suites, no CI
Windows (native or mingw-w64 cross)WIN32NULL only — there is no SDL-free audio on Windows (WASAPI is reserved and refused)DIRECTX9, DIRECTX11, DIRECTX12, DIRECT2D, GDI, GL family, VULKAN, CPU renderersPlatform harness only (mingw + Wine in CI, MSVC manual); the full engine is not built SDL-free by CI
Any POSIX host, no windowHEADLESS or TERMINALNULLCPU renderers (TERMINAL: SOFTWARE, PORTABLEGL, HEADLESS, STUB only)Selection logic; CI runs these platforms with SDL still configured
macOS, iOS, Android, EmscriptenSDL3 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_SDLPlatform / audio / rendererExpected
AUTOSDL3 / SDL3 / HEADLESSConfigure succeeds
ONSDL3 / SDL3 / HEADLESSConfigure succeeds
OFFX11 / 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 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 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