I need to change build configuration
Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit c1c316b9; unless a sentence says otherwise, nothing here was built or executed. Read at c1c316b9 from CMake files, presets, workflows and docs/build-performance.md; nothing was configured, built or run, and the named tests and CI cells are registrations, not results.
Use this recipe to add or change a CMake option, a default, a value of one of the three selectors, a renderer combination rule, a preset, a CI configuration or a pinned third-party source. CNA's configure step is also its validation step: a wrong combination is meant to fail at configure time with a message that names the option, and the decision code is written so that the refusal can be tested in milliseconds. The trace of the whole configure is on CMake architecture; this page is the procedure and the rules a change has to keep. Everything was read at c1c316b9 from CMake, presets and workflows; nothing was configured or run.
Find the owner
| Change | Owner file | What checks it |
|---|---|---|
| A root feature option (tests, diagnostics, video, net, CNAEXT, devices) | CMakeLists.txt | Configure itself; presets; CI cells that set it |
A value of CNA_PLATFORM | PlatformSelection.cmake | CnaPlatformSelection_Retired, platform-ci.yml |
A value of CNA_AUDIO_PLATFORM | AudioPlatformSelection.cmake | CnaAudioPlatformSelection_* (script mode) |
| A renderer identity, default or set | RendererIdentities.cmake, RendererDefaultSelection.cmake, RendererSelection.cmake, RendererRegistry.cmake | RendererIdentityRegistry, CnaRendererDefaultSelection_*, CnaRendererRetired_*, RuntimeRendererDiscipline, RendererTargetDiscipline |
| A pairwise renderer restriction | RendererCombinations.cmake | RendererCombinationRegistry (rule versus runtime-renderer-selection.md) |
| Whether SDL is configured at all | SdlAvailability.cmake, ThirdPartySDL.cmake | CnaSdlOffFindsNoSdlPackage, the sdl-enable-matrix job |
| A pinned dependency, patch or prebuilt | cmake/ThirdParty*.cmake, cmake/patches, SdlPrebuiltFingerprint.cmake | CnaSdlPrebuiltFingerprint |
| A preset | CMakePresets.json | tools/build/check_build_performance_policy.py for the focused presets |
| Compiler, linker, sanitizer, debug-info, IPO, PCH, unity policy | BuildPerformance.cmake | The same policy checker; build-performance.md is the record |
Read first
CMakeLists.txtfrom the top throughadd_subdirectory(modules): the order is functional (selection precedes the SDL sub-build; the private compile policy is attached last to every CNA-owned target).- The one selection file you are changing, and the
cmake -Pcase that tests it (cmake/Tests). UnitTests.cmakearound theCnaAudioPlatformSelection_*loop andModuleProbes.cmakearoundCnaRendererDefaultSelection_*: the registration patterns.platform-ci.yml: the tuple matrix and thesdl-enable-matrixjob that asserts a configure fails.- Then the CMake option index, selection axes and the user-facing Building CNA: CMake options.
Rules an option must follow
| Concern | Rule at TARGET | Example |
|---|---|---|
| Name | Project options are CNA_<AREA>_<NAME>; a per-identity switch is CNA_RENDERER_<IDENTITY>; a per-renderer feature is CNA_<IDENTITY>_<FEATURE>. The chosen selector value becomes a compile definition CNA_PLATFORM_<NAME>, CNA_AUDIO_PLATFORM_<NAME> or CNA_RENDERER_<NAME> | CNA_VULKAN_COMPILED_EFFECTS, CNA_SDL_GPU_SHADERCROSS |
| Type | A boolean uses option(). Anything with more than two states is a CACHE STRING with set_property(CACHE ... STRINGS ...), upper-cased, validated against a regex and written back with FORCE so the cache holds the normal form | CNA_DIAGNOSTICS (OFF, STATS, FULL), CNA_ENABLE_VIDEO, CNA_ENABLE_SDL (AUTO, ON, OFF) |
| Default | The default reproduces the historical build byte for byte. AUTO means “as before”; a new capability is OFF or AUTO until it is proven, and defaults change only with the same evidence | The comment header of SdlAvailability.cmake |
| Refusal | Refuse an invalid value or combination with message(FATAL_ERROR) that names the option, the value received, the valid set and why nothing is substituted. Never fall back to another identity | Reserved identifiers (SDL12, EMSCRIPTEN, OPENAL, WASAPI) are recognised so that they fail loudly instead of reading as typos |
| Testability | Put the decision in its own file that works under cmake -P (cmake_policy(SET CMP0057 NEW), include_guard, guard directory-scoped commands with CMAKE_SCRIPT_MODE_FILE), then test it without configuring a project | AudioPlatformSelection.cmake, RendererDefaultSelection.cmake, RendererIdentities.cmake |
| Message wording in tests | Assert a short, stable phrase: CMake wraps long diagnostics at terminal width, so a long expected string is never found | "is a reserved identifier", "is not a member of" |
| Dependent options | State a required pairing at configure time, with the option to change, rather than as a later missing include | CNA_BUILD_C_API=ON requires CNA_ENABLE_NET=ON |
| Documentation | No gate reads every option. Two are held mechanically: renderer combination rules against docs/runtime-renderer-selection.md, and the documented renderer count against the identity table. Update the option's docs/*.md, the presets and the site's option table yourself | check_renderer_combinations.py |
The shape of a tri-state option, as CNA_DIAGNOSTICS and CNA_ENABLE_VIDEO write it (a template for a new option, not an existing one):
set(CNA_ENABLE_FOO "AUTO" CACHE STRING "Enable Foo (OFF, AUTO, or ON)")
set_property(CACHE CNA_ENABLE_FOO PROPERTY STRINGS OFF AUTO ON)
string(TOUPPER "${CNA_ENABLE_FOO}" _cna_enable_foo_normalized)
if(NOT _cna_enable_foo_normalized MATCHES "^(OFF|AUTO|ON)$")
message(FATAL_ERROR
"CNA_ENABLE_FOO must be OFF, AUTO, or ON (received '${CNA_ENABLE_FOO}').")
endif()
set(CNA_ENABLE_FOO "${_cna_enable_foo_normalized}" CACHE STRING
"Enable Foo (OFF, AUTO, or ON)" FORCE)
unset(_cna_enable_foo_normalized)
The three selectors and their refusals
| Selector | Values at TARGET | Refused at configure time | Refusal test |
|---|---|---|---|
CNA_PLATFORM (default SDL3) | SDL3 and HEADLESS always; TERMINAL on non-Windows targets | Reserved SDL12 and EMSCRIPTEN (and TERMINAL on Windows); the retired WIN32, X11 and WAYLAND, with a message that points to SDL3; unknown values; TERMINAL with a GPU renderer (checked in RendererSelection.cmake) | CnaPlatformSelection_Retired (script mode) |
CNA_AUDIO_PLATFORM (default SDL3) | SDL3, NULL, ALSA | Reserved OPENAL and WASAPI; unknown values; ALSA off Linux | Eight cmake -P cases: CnaAudioPlatformSelection_DEFAULT, _SDL3, _NULL, _ALSA, _OPENAL, _WASAPI and two unknown names, among them _BOGUS |
CNA_GRAPHICS_RENDERER and optional CNA_GRAPHICS_RENDERERS | The 14 public identities; the default is Emscripten WEBGL2, Linux OPENGLES3, otherwise SDL_RENDERER | Any name outside the 14 given to the cache selector or as a set member, and any retired identity on all three routes (cache selector, set member, CNA_RENDERER_<X>=ON; a removed identity gets its own message that says it was removed); a CNA_RENDERER_<X>=ON switch for a name that is neither public nor retired is not read and is not refused; a default that is not a member of the set; unbuildable pairs; per-identity host and dependency gates | CnaRendererDefaultSelection_*, CnaRendererRetired_* (generated from the retired list), RendererIdentityRegistry, RendererCombinationRegistry |
CNA_ENABLE_SDL (AUTO, ON, OFF) | A switch layered over all three | OFF while any selection genuinely needs SDL (the SDL3 platform or SDL3 audio, or SDL_RENDERER, SDL_GPU, FNA3D), listing the reasons | CnaSdlOffFindsNoSdlPackage, four cells of sdl-enable-matrix |
The selectors are independent by design. The couplings that do exist are deliberate and refused explicitly: TERMINAL has no native graphical window and accepts only SOFTWARE, HEADLESS or STUB (checked in RendererSelection.cmake before any renderer dependency is probed), and CNA_ENABLE_SDL=OFF refuses every SDL-backed value of the other selectors. A new coupling between selectors is a design change and needs an entry in the selection axes index.
Renderer combination rules and the registry
- Combination rules are real, demonstrated conflicts with a stated reason. At TARGET there is one: identities cannot span the Windows-only (
DIRECTX9,DIRECTX11), Emscripten-only (WEBGL2) and Apple-only (METAL) partitions, because one toolchain targets one of them at a time. The three EasyGL identities can coexist since GL profile became a runtime value, although a variable and a comment inRendererCombinations.cmakestill describe the removed rule. The rejection function joins its reason fromARGN: write the reason as adjacent literals with no;, which CMake would split. - The registry is generated.
cna_generate_renderer_registrywritesgenerated/CnaRendererRegistry.generated.cppinto the build tree from the identity map inRendererRegistry.cmake; an identity with no row is a configure error naming the three places to register it. The explicit table exists because a static-initialiser self-registration would be discarded by the linker inside an archive. The first identity is the build's default. - Identity macros. An arm of
cna_configure_renderer_identityappends its macro to_cna_identity_defines; it must never calladd_compile_definitions, which is directory-scoped and would define that identity's macro for every renderer in the set.scripts/check_runtime_renderer_discipline.py(RuntimeRendererDiscipline) fails on a new one. - Target gates versus resource gates. A gate that decides whether a target exists and runs against the default renderer must test equality with
CNA_GRAPHICS_RENDERER; a gate that decides whether a resource is available may test membership in the set (graphics examples). In a single-renderer build they coincide, which is why the mistake is easy to make. - Adding a whole renderer family is the worked example on CMake architecture; do not copy a neighbour's family CMake without knowing which special relationship applies.
Prove a selector change
- A script-mode case for the decision. Follow the audio loop in
UnitTests.cmake:add_test(NAME CnaAudioPlatformSelection_<CASE> COMMAND cmake -DCNA_AUDIO_SELECTION_FILE=... -DCNA_AUDIO_SELECTION_CASE=<CASE> -DCNA_AUDIO_SELECTION_EXPECTED=<phrase> -P cmake/Tests/AudioPlatformSelectionCase.cmake)with theaudio;platformlabels. Cover the default, every implemented value, every reserved value and an unknown value. The renderer default cases carry an outcome (ACCEPTorREJECT) and, for refusal, expected text. Keep the control cases (an accepted case, an unknown name still unknown): a refusal that refuses everything would pass every reject case. - A nested configure only where detection or a real project is needed (
CnaSdlOffFindsNoSdlPackageconfigures a work directory with the generator under test). - A CI cell for the expected outcome. Copy the
sdl-enable-matrixpattern: a matrix row withexpect: configure-succeeds|configure-fails, configure withset +e, then assert the status and, for a failure,grepthe diagnostic. It proves the refusal, and builds nothing. - Single, multi and default. Configure a single-identity tree, a multi-renderer set with a different default, and the unchanged default; inspect the generated registry and the final link line.
scripts/check_renderer_configure_sweep.shconfigures every identity (skipping ones whose SDK is absent) without building. - Run the identity, combination and discipline checks (Python) after any list edit;
check_renderer_identities.pyfollows each identity through to its descriptor accessor and checks documented counts.
Retired names. A name outside the 14 is refused by name; the refusal cases are generated from the retired list so retiring an identity adds its test automatically, and a retired C ABI value is never reused. Do not write a removed identity into documentation or tests as a live example.
Presets, CI and documentation
- Presets.
CMakePresets.jsonhas 17 visible configure presets and a hiddenbase-ninjaparent (Ninja,CNA_EXPORT_COMPILE_COMMANDS,CNA_USE_CCACHE), and its own list of 17 build presets; macos, ios, ios-simulator and multi-renderer have no build preset. Binary directories are${sourceDir}/cmake-build-<name>. Thedev,unitandrelease-modulesclosures deliberately omit tests, demos, the C API, networking, FFmpeg and Draco; PCH, unity and IPO stay opt-in presets.tools/build/check_build_performance_policy.py(run ingeneral-tests-ci.yml) resolves preset inheritance and fails if those closures regain the omitted features or if routine policy is written into globalCMAKE_CXX_FLAGS, linker flags oradd_compile_options. - Workflows. The selection matrix lives in
platform-ci.yml(SDL3 + OPENGLES3, SDL3 + VULKAN, SDL3 + SOFTWARE, whose SDL3 cells also run SDL’s x11 and wayland drivers, HEADLESS with NULL audio, TERMINAL + SOFTWARE with NULL audio, an SDL-free HEADLESS job with ALSA audio, the SDL-switch job and a manual native-MSVC SDL3-on-Windows job).multi-renderer-ci.ymlbuildsHEADLESS;SOFTWARE;STUBand a single-renderer control, because the property that matters is that single-renderer builds did not change. Path filters exclude Markdown anddocs/. Sibling checkouts useclone_siblings.sh(candidate branches, thennext, thendevelop). Eighteen workflow files exist; the C API has five build-free gate workflows and no build workflow (Update the C API). - Documentation. A user-visible option needs the site's options table, the presets and, for renderers, the renderer guide; the generated option index is regenerated from the source, not edited.
Third-party sources, patches and siblings
| Dependency | Where and how pinned | Offline or override |
|---|---|---|
| SDL3, SDL3_image, SDL3_mixer | Submodules, built at configure time into a persistent prebuilt root outside every build tree | CNA_USE_SYSTEM_SDL=ON; CNA_SDL_PREBUILT_ROOT; CNA_MAX_VENDORED_BUILD_JOBS (default 2, separate from Ninja parallelism) |
| FNA3D and MojoShader | CNA_FNA3D_GIT_TAG; a series of local patches against one MojoShader revision applied as a whole, idempotently, with a stamp | -DFETCHCONTENT_SOURCE_DIR_FNA3D=<path> |
| wgpu-native, SDL_shadercross and SPIRV-Cross | Pinned versions or commits in their own ThirdParty*.cmake; wgpu-native downloads verify a SHA-256 and fail closed | CNA_WEBGPU_ROOT with auto-download off; FETCHCONTENT_SOURCE_DIR_* |
| sharp-runtime, easy-gl, meta-gl | Sibling checkouts, not submodules; sharp-runtime must be its apple/m4-stabilization branch; CNA requests a fixed component closure; easy-gl (built for the EasyGL identities) expects its own ../meta-gl sibling | CNA_SHARP_RUNTIME_ROOT, CNA_EASYGL_ROOT |
Recently added mechanisms at this snapshot: the persistent SDL prebuilt is now reused only while a build manifest still matches (SdlPrebuiltFingerprint.cmake: SHA-256 over the vendored source tree without .git, every patch in order with its hash, and every sub-build argument; ccache launcher excluded), so a moved source, an edited patch or changed arguments rebuild it once and an interrupted build is retried; two SDL_gpu Vulkan barrier patches are staged from a copy so the vendored tree stays pristine (cmake/patches); with CNA_ENABLE_SDL=OFF MojoShader now builds on the C library without its SDL_gpu adapter (FNA3D still needs SDL and is refused by name); and the EasyGL test filter lists only the EasyGL identities. The Vulkan validation output gate turns a printed message into a test failure through FAIL_REGULAR_EXPRESSION, for problems that appear at teardown beyond any in-process assertion.
To change a pin: change the tag or commit, refresh any patch series against exactly that revision (a patch that does not apply stops the configure and names itself), and rely on the manifest to rebuild the SDL prebuilt; to force it, delete the prebuilt root that configure prints. The C API package is the only installable target (modules/c-api/CMakeLists.txt): the C++ framework has no install() rules.
Shared library and build performance
CNA_SHARED_LIBRARYdefaults ON on native ELF with GNU or Clang and CMake 3.27 or newer; the decision precedes every target because archives insidelibcna.soneed position-independent code, and it is a configure error elsewhere. The C API links module archives directly and stays self-contained.CNA_ENABLE_IPOis refused with sanitizers and withCNA_C_API_BUILD_STATIC=ON.- ccache.
CNA_USE_CCACHE(ON) wraps compilers with a launcher that exportsCCACHE_BASEDIRfromCNA_CCACHE_BASEDIR(the environment's value wins when set; otherwise the parent of the source directory), so the same translation unit hashes identically across build trees;cmake --build <dir> --target cna_ccache_statsshows the cache. CNA does not change global ccache sloppiness, and it documents an apparent 0% direct-hit rate that turned out to be an unwritable statistics directory. Read counters as deltas, not lifetime rates. - Focused targets and linkers. Module test executables share object libraries with
CnaTests;CNA_LINKERselects AUTO (Mold, then LLD), DEFAULT, MOLD or LLD;CNA_DEBUG_INFO=LINE_TABLESand the PCH and unity pilots are opt-in and measured indocs/build-performance.md. Compare a change only against the same compiler, renderer, cache state and target.
Traps
- An unread cache entry. Forgetting a removed identity would make
-DCNA_RENDERER_X=ONsilently configure the default; that is why each retired name is checked on that route too, and why a value not consumed anywhere is worse than a refusal. The check does not reach a misspelled or never-existing name:-DCNA_RENDERER_D3D9=ONis still an unread cache entry at this snapshot, so read theCNA: Using <X> graphics rendererline after such a configure. - A default that moves. The per-host default lives in one file used by two consumers so the SDL-free decision cannot drift from the renderer default; do not restate it.
- Stale comments in selection files.
ModuleProbes.cmakestill speaks of 42 identities;RendererCombinations.cmakedescribes a rule P11 removed. Trust the code and the identity table. - Presets that hide a failure. A preset is a convenience, not a matrix:
devis not evidence for a renderer, C API, network, media or Draco change (What to test after changing X). - Directory-scoped commands in selection code.
add_compile_definitionsand friends leak across the set and do not run in script mode. - A nested sub-build inherits nothing. SDL is configured by separate CMake invocations; settle Apple sysroot and architecture before launching them.
Review checklist
- The option's name, type, default and normal form follow the table; the default build is unchanged.
- Every invalid value or combination fails at configure time by name; nothing is substituted.
- A script-mode or nested test asserts the refusal with a short phrase, plus a control that accepts; a CI cell asserts the expected outcome where a real configure is needed.
- Presets, workflow cells, the site option table and, for renderers, the combination document agree; single, multi and default configurations were considered.
- Pins, patches and offline overrides are recorded; anything not configured or run is stated as such.
Related: Blast radius and readiness, Test architecture, Fix a renderer bug and Make a release.
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.
- Dependency acquisition: submodules, siblings, fetched pins and host packages — The five ways outside code enters a CNA build, how they compose, what the configure-time SDL build implies, how host packages change what compiles, the sibling contract and how CNA itself is consumed.
- easy-gl and meta-gl: the two-library GL stack beneath the EasyGL family — How meta-gl and easy-gl split loading, typed calls, ownership and failure beneath CNA's three GL identities: revisions CNA needs, feature gating, per-thread state, context loss, tests and build inheritance.
- Module boundaries: cycles, umbrellas and the gates that enforce them — Why CNA keeps three static-archive cycles, what the CNA, CnaExt and BuildConfig compositions carry, what each boundary gate and the fifteen module probes check, when they run and what a violation looks like.
- sharp-runtime components and how CNA consumes them — How sharp-runtime is cut into CMake components, how an include maps to a component and link closure, and how CNA c1c316b9 selects, links and instruments the components it needs.
- The CNA ecosystem: siblings, references and consumers — Which repositories CNA builds against, audits against and is consumed by, when CMake needs each sibling, where each sibling draws its boundary, and which revision each statement refers to.
Known issues in this area
Current defects, gaps and limitations at this snapshot that touch this subject.
- CNA-VGAP-011: No test exercises PlatformSelection.cmake's reserved-identifier refusal paths — PlatformSelection.cmake fails configure for SDL12, EMSCRIPTEN and TERMINAL on Windows, but no test asserts those refusals; the one platform-selection test, CnaPlatformSelection_Retired, covers only the retired and unknow
Related pages
The same subsystem is explained at four altitudes. These are the neighbouring pages at each one.
- User guide
- Building CNA: CMake options · Building CNA: presets · Platforms: the four axes · Runtime renderer selection
- Architecture
- CMake architecture
- Internals
- Renderer selection internals · Platform backends
- Maintainer workflow
- Fix a renderer bug · What to test after changing X · Blast radius and readiness
- Tests and validation
- Test architecture
- Reference
- CMake option index · Selection axes index