Build system
Evidence basis: source-verified at the pinned commit. Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Preset, target and option names were read from CMakePresets.json and the CMake files; nothing was configured, built or run, so 'builds CnaCoreTests' means the preset names that target.
The root CMake project composes physical module targets and one implementation from each backend axis. Presets capture the developer configurations the repository supports; target names, not the CNA umbrella, drive focused builds. This page is the command entry point for a maintainer: what a configure needs and refuses without, which presets and build presets exist, how to select backends by hand, what to rebuild after a given kind of change, how to read the configure log, and where generated artefacts come from. The configure-to-link trace is on CMake architecture; install steps for a first build are in Building CNA.
Evidence for this page: the preset and target names below were read from CMakePresets.json and the CMake files at snapshot 009d40f5. Nothing was configured, built or run for the page, so a sentence such as "builds CnaCoreTests" means the preset names that target, not that the build passed. Run cmake --list-presets=all against your own checkout before scripting a preset name.
Prerequisites
The build has a small hard floor and many conditional dependencies. Backend libraries are needed only for configurations that select them, and several optional features are three-state switches (OFF, AUTO, ON) whose AUTO default silently degrades the configuration instead of failing. Inspect the configure output (reading the configure log) and never mistake an unavailable optional backend for a runtime failure.
- Toolchain. CMake 3.20 or newer (
cmake_minimum_requiredinCMakeLists.txt) and a C++23 compiler with extensions off (CMAKE_CXX_STANDARD 23,CMAKE_CXX_EXTENSIONS OFF). Enabling the C API adds C17. The linked-oncelibcna.solayout needs CMake 3.27 and a native ELF GNU or Clang toolchain; CMake does not check compiler versions. The presets that inheritbase-ninjause Ninja and turn ccache on; presets such aswebanddevices-*declare no generator. - Python 3. Several configure-time gates and CTest entries run Python scripts: the platform ratchet, the non-production SDL audit, the hot-path lint, the XNA pipeline parity gates and the module link-closure tests. Without an interpreter they are skipped with a status line rather than failing, so a configure that "passed" on a machine without Python did not run them.
- git. Needed for
git submodule update --initand, at configure time, to apply the vendored-SDL patch series when the SDL prebuilt is (re)built (the SDL prebuilt); a missinggitthere is a configure error. - Submodules.
third_party/SDL,SDL_image,SDL_mixer,third_party/dracoandvendor/googletest. Each missing one stops the configure with a message that names the directory and tells you to rungit submodule update --init(non-recursive: the recursive form only fetches nested codec submodules CNA disables). WithCNA_ENABLE_SDL=OFFthe SDL ones are never read;CNA_ENABLE_DRACO=OFFandCNA_BUILD_TESTS=OFFremove the others. - Sibling repositories.
../sharp-runtimeon itsnextbranch for every build (missing: configure error namingCNA_SHARP_RUNTIME_ROOT);../easy-glwith../meta-glwhen any of the five GL identities is selected, including the Linux default;../free-directonly forFREEDIRECT. They are separate checkouts, not submodules. The first-build clone commands are in Building CNA, step 2. - Optional three-state features.
CNA_ENABLE_VIDEO(FFmpeg, found through four pkg-config modules),CNA_CNB_ZSTD(libzstd for compressed.cnbchunks),CNA_ENABLE_FONT_PIPELINE(FreeType for the.spritefontroute) andCNA_ENABLE_MEDIA_PIPELINE(FFmpeg-based build-time importers); zlib for binary FBX arrays is detected without a switch. Draco defaults ON and is off by default only under Emscripten. - Backend packages.
find_package(Vulkan REQUIRED)forVULKAN,find_package(OpenGL REQUIRED)forOPENGL4, the X or Wayland development packages forCNA_PLATFORM=X11orWAYLAND(refused with the package names, never replaced by SDL3), and the SDL submodules unlessCNA_ENABLE_SDL=OFF.
How CMake is structured
CMake architecture follows the configure order, the target graph, the generated renderer registry, the static-archive cycles, the shared-library mode, the SDL prebuilt and a worked renderer addition. In short:
- Root policy and options in
CMakeLists.txtestablish language and toolchain behaviour, then the version identity, the linker, ccache and the test-display policy. - Selection files resolve the platform, audio and renderer identities and decide whether SDL is needed at all, before any dependency is configured.
- Each physical module declares its own static target, include root and dependencies through
cna_add_module; the modules are entered frommodules/CMakeLists.txt. - Backend implementation targets attach only for the selected or requested identities; a generated registry lists them and a descriptor gate compiles the ones you did not select.
CNAis an INTERFACE umbrella over the runtime modules plus the selected renderer. It has no sources, so it is not a buildable target: build a real target by name, or build everything.- Test, harness and probe registration comes after module composition, so those files can name the final targets.
Useful configurations
The repository ships 17 visible configure presets (plus a hidden Ninja base) and 17 build presets. Each configure preset writes its tree to ${sourceDir}/cmake-build-<preset> (the multi-renderer tree is cmake-build-multi and the iOS simulator tree cmake-build-ios-sim). Read both halves of the JSON: a configure preset existing does not imply a build preset of the same name.
| Goal | Configure preset | Build preset (target) | What the configure preset sets |
|---|---|---|---|
Fast core feedback (cmake --preset unit) | unit | unit-core (CnaCoreTests), unit-math (CnaMathTests), unit-content (CnaContentTests), unit-graphics (CnaGraphicsTests) | STUB renderer, Debug, tests on; examples, networking, video, Draco and the C API off. The focused binaries land in cmake-build-unit. |
| All unit tests the configuration registers | unit | unit (CnaTests) | Then ctest --test-dir cmake-build-unit -N to list what this tree actually registered. |
| Broad repository tests | tests | tests (CnaTests only) | Ninja, Debug, OPENGLES3, tests and examples on. The build preset does not build the example executables; see the warning below. |
| Runtime renderer set, CPU only | multi-renderer | none | Default HEADLESS with CNA_GRAPHICS_RENDERERS=HEADLESS;SOFTWARE;STUB; tests and examples keep their default ON. Build with cmake --build cmake-build-multi. |
| Extension modules | cnaext | cnaext (CnaTests) | Debug, OPENGLES3, CNA_CNAEXT=ON, tests on, examples off. |
| Memory, thread and undefined-behaviour investigation | devices-asan, devices-tsan, devices-ubsan | the same names (CnaTests) | Debug, OPENGLES3, CNA_DEVICES=ON, tests on, with CNA_SANITIZE set to address (optimisation O0), thread (O1) or undefined (O1). Only devices-ubsan is used by a workflow. |
| Content tool only | dev, dev-fast-debug | the same names (cna_tool_cnb_info) | STUB, Debug, tests and examples off; dev-fast-debug adds CNA_DEBUG_INFO=LINE_TABLES. |
| Compile-time pilots | unit-pch, unit-unity | unit-content-pch, unit-core-math-unity | The unit configuration plus CNA_ENABLE_PCH=ON or CNA_ENABLE_UNITY_BUILD=ON. |
| Release measurements | release-modules, release-ipo | the same names (cna_tool_cnb_info) | STUB, Release, tests and examples off; release-ipo adds CNA_ENABLE_IPO=ON. |
| Web | web | web (cna_house3d_demo) | Release, WEBGL2, examples on, tests off. No toolchain file: run emcmake cmake --preset web. |
| Apple | macos, ios, ios-simulator | none | Release with SDL_RENDERER. macos has tests and examples on; the iOS presets use cmake/toolchains/ios.cmake with tests, examples and networking off. |
Build presets that name a single target build only that target. cmake --build --preset tests builds CnaTests, but many CTest entries in that configuration are separate example or smoke executables, so an unfiltered ctest afterwards reports missing binaries. Run a full cmake --build cmake-build-tests first, or select entries with -L or -R, as described in the testing handbook.
No preset selects VULKAN, OPENGL4, SDL_GPU, WEBGPU, X11 or WAYLAND, the C API, or a Windows target: configure those by hand, as in the next section. The multi-renderer set contains only CPU renderers, so it needs no GPU; the private display wrapper is for tests in a hand-configured GPU tree (GPU and window tests).
Manual selection
cmake -S . -B build/dev \
-DCMAKE_BUILD_TYPE=Debug \
-DCNA_PLATFORM=HEADLESS \
-DCNA_GRAPHICS_RENDERER=HEADLESS \
-DCNA_AUDIO_PLATFORM=NULL
cmake --build build/dev --parallel
These identities exist at this snapshot, and combinations are still constrained by target logic and test needs; prefer a repository preset when one expresses the task. Two things surprise people. With the default CNA_ENABLE_SDL=AUTO the vendored SDL is configured and built even for the display-free selection above, because AUTO means "exactly as before"; only -DCNA_ENABLE_SDL=OFF gives an SDL-free tree. And the HEADLESS platform, HEADLESS renderer and NULL audio choice is three independent decisions, not one "headless" switch.
# SDL-free native X11 with several renderers compiled in (the shape of the CI x11-sdl-free-gpu cell;
# OPENGL33 needs the easy-gl and meta-gl siblings, VULKAN the Vulkan headers)
cmake -S . -B build/x11 -G Ninja \
-DCNA_ENABLE_SDL=OFF -DCNA_PLATFORM=X11 -DCNA_AUDIO_PLATFORM=ALSA \
-DCNA_GRAPHICS_RENDERER=OPENGL33 \
-DCNA_GRAPHICS_RENDERERS="OPENGL33;VULKAN;SOFTWARE;HEADLESS"
# One GPU renderer, everything else default
cmake -S . -B build/vk -DCNA_GRAPHICS_RENDERER=VULKAN
What configure refuses, and where
Invalid combinations fail at configure time with a named reason and never fall back silently. The table lists the refusals a hand-written selection can hit and the file that raises each; the first three are the ones people meet first.
| Selection | Refused because | Raised in |
|---|---|---|
A renderer name outside the 25, through CNA_GRAPHICS_RENDERER or a member of CNA_GRAPHICS_RENDERERS; a retired name through a CNA_RENDERER_<NAME>=ON switch | Unknown names are refused by name on the selector and set routes, and retired names on all three routes, before any dependency is configured; a name that used to exist gets a separate "retired" message. A CNA_RENDERER_<X>=ON switch whose name is neither public nor retired (for example D3D9) is never read, so it is not refused and the platform default renderer is configured | cmake/RendererIdentities.cmake |
Two CNA_RENDERER_<NAME> switches ON; a default that is not a member of the plural list | Exactly one switch may be ON; the singular default names the default within the set | cmake/RendererSelection.cmake, cmake/RendererDefaultSelection.cmake |
PORTABLEGL with a real-GL family, GDI with SOFTWARE, identities from different operating-system partitions | Duplicate gl* symbols, the same sources compiled twice with different definitions, one toolchain targeting two platforms | cmake/RendererCombinations.cmake |
CNA_PLATFORM=TERMINAL with anything but SOFTWARE, PORTABLEGL, HEADLESS, STUB | No graphical window; checked before renderer dependencies are probed | cmake/RendererSelection.cmake |
| Windows-only, macOS-only or browser-only renderers off their target; native GL profiles under Emscripten | Windows SDK, Metal or browser APIs are unavailable | cmake/RendererSelection.cmake |
CNA_PLATFORM=WIN32 off Windows, TERMINAL on Windows, X11 or WAYLAND without their packages; SDL12 and EMSCRIPTEN | Host-conditional or reserved platforms are refused with what to install, never replaced by SDL3 | cmake/PlatformSelection.cmake |
CNA_AUDIO_PLATFORM=ALSA off Linux; OPENAL and WASAPI | Linux-only; reserved | cmake/AudioPlatformSelection.cmake |
| SDL2 and SDL3 in one process (platform SDL2 with default audio, platform SDL3 with SDL2 audio, SDL2 for both with an SDL3-linking renderer) | Both libraries export identically named entry points, so neither backend would be the one running | cmake/Sdl2OnlyConfiguration.cmake |
CNA_ENABLE_SDL=OFF with an SDL platform, an SDL audio value, or SDL_RENDERER, SDL_GPU, FNA3D, FREEDIRECT | The configuration genuinely needs SDL; the message lists which selection does | cmake/SdlAvailability.cmake |
CNA_BUILD_C_API=ON with CNA_ENABLE_NET=OFF | The C API adapts GamerServices, which is built only with networking | CMakeLists.txt |
I changed this — what must I rebuild?
| Change | Minimum expected rebuild and validation |
|---|---|
.cpp in one module | That module's static library plus every target linking it. With the default shared layout on native ELF, libcna.so is relinked but test and example executables are not; with static linking every dependent executable relinks. |
| A new or deleted source or test file under a module | Module sources and test sources are collected with file(GLOB … CONFIGURE_DEPENDS) (the C API lists its sources by hand, and a renderer family globs only its src/*.cpp), so the next build re-runs CMake by itself. A new test directory needs a focused-target name registered in cmake/UnitTests.cmake, or configure stops. |
Public or Internal header | Every target whose dependency scan includes it; validate the downstream module tests. |
| CMake, options, presets or target sources | Reconfigure, then rebuild the affected targets. A changed cache value (a preset, a -D) reconfigures the same tree. |
Renderer contract (IGraphicsRenderer, the descriptor) | Every selected family must compile, not only the default; the descriptor gate compiles the others. Then graphics tests and every affected renderer's conformance slice. |
| Platform contract | Platform, input and runtime tests, the SDL containment audits, and native-host CI. |
| C API header | The C API shared, static and WebAssembly paths, the ABI baseline and compatibility gates, and external binding compatibility. |
| Content schema or reader | The pipeline tool, golden assets and loaders, and runtime content tests. |
| Vendored SDL source, an SDL patch or the SDL configure arguments | The next configure rebuilds that SDL install instead of reusing it (the build manifest no longer matches); the install directory is shared by every build tree of the checkout. |
Toggling a CNA_*_COMPILED_EFFECTS option | Fetches FNA3D with MojoShader at configure time (offline: -DFETCHCONTENT_SOURCE_DIR_FNA3D=<path>), then rebuilds that renderer and common/mojoshader. |
A shader .glsl under a renderer or engine layer | Regenerate the checked-in header with its generator (compile_shaders.py beside the Vulkan and SDL_gpu shaders, or tools/shader_package/generate_shader_package.py; both load libshaderc), rebuild the affected targets, and run the matching *ShaderPackageReproducibility gate. |
Fixture or reference assets under tests/ | No rebuild: tests read them at run time from repository-root-relative paths. |
Reading the configure log
The configure prints one status line per decision that matters, and most build surprises are explained by one of them. Search the log for these prefixes before debugging a "missing feature".
| Line (prefix) | What it tells you |
|---|---|
CNA: Using <X> platform implementation, CNA: Using <X> audio platform implementation | The resolved platform and audio values. |
CNA: renderer set -- <list> (default: <X>), CNA: renderer registry -- <n> family/families | The compiled-in identities, default first, and the families the generated registry names. |
CNA: renderer descriptor gate -- <n> compiled here, <m> by their own family target | How many non-selected descriptors the gate compiles. |
CNA: video enabled (…) or CNA: video disabled (…) | Whether the FFmpeg backend was found; disabled means video playback reports NotSupportedException. |
CNA: SDL is NOT configured | The SDL-free configuration is active; no SDL package can be found by any project in the tree. |
CNA: <name> prebuilt is current (build manifest …) or CNA: (re)building <name> because … | Whether the persistent SDL install was reused or rebuilt, and why. |
CNA: vendored SDL3 video drivers: … | The video drivers the prebuilt SDL actually has (for example whether wayland is present). |
CNA: focused unit-test targets: … | Which per-module test executables this configuration can build. |
CNA: runtime linked as libcna.so — <n> module archives inside | Shared-library mode is active. |
CNA: compiler cache available (…; base directory: …) or CNA: ccache not found | Whether compile output is cached and against which base directory. |
CNA: <n> GPU/window tests inherit the caller's DISPLAY | The test-display policy is active (CMake 3.28 or newer); GPU tests are not pointed at a fixed display. |
CNA: MojoShader pinned with FNA3D … or MojoShader is built on the C standard library | A compiled-effects option fetched MojoShader; the second form is the SDL-free variant without its SDL_GPU adapter. |
Build speed and disk use
The build-speed controls are collected in cmake/BuildPerformance.cmake and documented in docs/build-performance.md; public ABI facts stay in cna_build_config, and everything below is target-scoped private policy.
- ccache.
CNA_USE_CCACHEdefaults to ON. Whenccacheis found, both compiler launchers becomecmake -E env CCACHE_BASEDIR=<dir> ccache. The base directory isCNA_CCACHE_BASEDIR: the exportedCCACHE_BASEDIRif set, otherwise the directory above the source tree, so one translation unit hashes the same from every build tree.cmake --build <dir> --target cna_ccache_statsprints the statistics. - One shared library.
CNA_SHARED_LIBRARY(default ON on native ELF GNU or Clang with CMake 3.27) links the engine once intolibcna.so. A configuration that previously carried a full static copy in every test executable (about 104 MB per Debug executable across 798 executables, by the measurement recorded in the rootCMakeLists.txt) now carries it once; a renderer.cppedit no longer relinks every test. - Linker and debug information.
CNA_LINKER(AUTOtriesmoldthenlldon native ELF),CNA_DEBUG_INFO(FULL,LINE_TABLES,SPLIT; Debug builds only, and not combinable with sanitizers). - Sanitizers.
CNA_SANITIZE=address,undefinedwithCNA_SANITIZE_OPTIMIZATION; ASan with TSan and TSan with MSan are refused, and MSVC and Emscripten are unsupported. The instrumentation is attached to CNA-owned targets and sharp-runtime, not to vendored SDL or ENet. - Pilots.
CNA_ENABLE_PCH(content test objects; that target bypasses ccache),CNA_ENABLE_UNITY_BUILD(the core and math modules; refused for Emscripten and cross builds) andCNA_ENABLE_IPO. - Vendored SDL jobs.
CNA_MAX_VENDORED_BUILD_JOBS(default 2) bounds the parallelism of the configure-time SDL sub-builds. - Configure audits. The slow configure-time Python audits cache their success by a content fingerprint of their inputs (
CNA_CONFIGURE_AUDIT_CACHE, default ON), so an unchanged tree does not re-run them.
Generated artifacts
Build directories, compiled content and generated code belong to their configured output trees unless a generation rule explicitly checks them in. Some generated files live in the build tree (the renderer registry, CNA/Version.hpp, the Wayland protocol bindings) and are never committed; others are committed and guarded by a gate that fails when the file no longer matches what its generator writes (the SPIR-V and shader-package headers, the C API baselines and coverage inventories, the XNA pipeline parity report, the glTF goldens). Before committing a generated change, locate the generating target or script and reproduce the file from clean input. The full table is in the repository map; the source-side entry points are CMakeLists.txt and CMakePresets.json, and the generated CMake option index lists the declared options.
Deep dives on this topic
Long-form pages that explain the exact semantics, invariants and evidence behind this subject.
- Configuring CNA per target: routes, toolchains and what a green build proves — How the Linux, Windows (MSVC and MinGW-w64 with Wine), Android, Emscripten and Apple routes configure CNA, run its tests, and what a successful configure, build or test run on each one proves.
Known issues in this area
Current defects, gaps and limitations at this snapshot that touch this subject.
- CNA-BUG-233: CLAUDE.md's Platform Boundary lists WIN32 as a reserved identifier that fails configuration, but cmake/PlatformSelection.cmake implements CNA_PLATFORM=WIN32 on Windows — CNA's contributor instructions name SDL3, SDL2, X11, WAYLAND, HEADLESS and TERMINAL as the platforms and SDL12/WIN32/EMSCRIPTEN as reserved; the build offers WIN32 on Windows and reserves TERMINAL there.
Related pages
The same subsystem is explained at four altitudes. These are the neighbouring pages at each one.
- Architecture
- CMake architecture · Architecture: the build axes
- Internals
- Renderer selection internals · Platform backends
- Maintainer workflow
- I need to change build configuration
- Tests and validation
- Testing handbook
- Reference
- CMake option index · Selection axes index