Dependency acquisition: submodules, siblings, fetched pins and host packages

CNA snapshot 009d40f5  ·  Deep Dives › Architecture & build  ·  source links pinned to 009d40f5

✓

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. Read from .gitmodules, the cmake/ThirdParty*.cmake files, SharpRuntimeConsumption.cmake, RendererSelection.cmake, modules/CMakeLists.txt, modules/c-api/CMakeLists.txt and CMakePresets.json; nothing was configured, fetched, built or installed.

A successful CNA configure can involve checked-out submodules, sibling repositories, sources and binaries fetched at configure time, and packages and SDKs found on the host. Calling all of that "vendored dependencies" hides the most common reproducibility failures, because each mechanism has a different pin, a different offline story and a different way of failing. This page classifies them at snapshot 009d40f5, explains how they compose, and says what each one does to the build when it is present, absent or different. Pins and override variables are listed on Source ownership: third-party boundaries; the SDL install's manifest mechanics on CMake architecture.

Five ways outside code enters a build

MechanismAt this snapshotPinned byReproducibility boundary
Git submodulesthird_party/SDL, third_party/SDL_image, third_party/SDL_mixer, third_party/draco (Draco 1.5.7) and vendor/googletestthe gitlink commits in CNA's own treeA ZIP or tarball export contains none of them; each has a configure guard that names the directory and the fix
Sources vendored in the treethird_party/enet, third_party/stb, third_party/dr_libs, third_party/cgltfbeing committedAlways present, but compiled only by the module and option that need them (ENet only with networking, dr_libs and stb_vorbis for the ALSA mixer)
Sibling checkouts../sharp-runtime (every build), ../easy-gl with ../meta-gl (the five GL identities), ../free-direct with ../free-api (FREEDIRECT)nothing in CNA's CMake (six CI workflows check out a pinned older sharp-runtime commit)Whatever commit the caller checked out; CNA compares no SHA
Fetched at configure timeFNA3D with MojoShader, PortableGL, SDL_shadercross with SPIRV-Cross, SDL2, and the wgpu-native binary releasecache variables in the cmake/ThirdParty*.cmake filesNetwork access on the first configure that selects them, unless an offline source directory or root is given
Host packages and SDKsFFmpeg, libzstd, FreeType, zlib, Vulkan, OpenGL, the X11 and Wayland development packages, ALSA headers, Threads, the Windows and Apple SDKsnothingCan change which files compile, or whether the configure is admissible at all

There is no in-tree copy of wgpu-native: the native WebGPU renderer uses a downloaded official binary release, and a browser build uses Emscripten's own port. vendor/ holds only GoogleTest.

Mechanisms compose, so a CNA commit is not a build identity

The categories nest. FNA3D is fetched at a pinned tag and brings MojoShader through its own submodules, and CNA then applies its own MojoShader patch series on top. easy-gl is a sibling that itself expects ../meta-gl as a sibling. free-direct is a sibling that adds ../free-api and resolves SDL from targets CNA has already created. The identity of one build is therefore the CNA commit, plus the submodule commits it records, plus sibling commits that CNA records nowhere, plus fetch pins that live in overridable cache variables, plus host package versions that nothing records, plus the manifest stored beside the persistent SDL install. A lock file for CNA's own Git tree alone does not reproduce a build.

Submodules: initialise, do not recurse

The instruction that the CMake gives is git submodule update --init, deliberately not --recursive. ThirdPartySDL.cmake explains why in the comment above its guard: recursion also clones SDL_image's and SDL_mixer's own nested codec submodules (AVIF, JXL, WebP, libpng, GME, libxmp, mpg123, FluidSynth, Opus, Vorbis and more, around nineteen), none of which CNA's sub-build options enable, and it measured six to seven times slower for no compiled input. CNA's own documents are not consistent with that: the description of the tests preset in CMakePresets.json still asks for --recursive and names only four submodules, and the README repeats the recursive form. The CMake is right.

Each missing submodule is caught by its own configure guard, and only when the option that needs it is on: the three SDL trees when SDL is configured at all and CNA_USE_SYSTEM_SDL is OFF; vendor/googletest while CNA_BUILD_TESTS is ON (its message says that a ZIP or tarball export cannot contain submodule content); third_party/draco while CNA_ENABLE_DRACO is ON and no system Draco is requested. The one place where a recursive update is required is outside CNA's tree: an offline FNA3D checkout supplied through FETCHCONTENT_SOURCE_DIR_FNA3D needs its own MojoShader submodule, and the configure says so if MojoShader/mojoshader.h is missing.

For a distributable source snapshot, either ship the exact submodule trees or choose the system-SDL route deliberately and provide GoogleTest another way (or build without tests). Silently building against whatever SDL happens to be installed changes the dependency claim of the result.

The configure-time SDL build and what it implies

With the default vendored route, cmake/ThirdPartySDL.cmake configures, builds and installs SDL3, SDL3_image and SDL3_mixer during CNA's configure step, into a persistent prefix outside every build tree, and then consumes them through their exported CMake packages. The operational consequences:

  • Deleting or cleaning a CNA build directory does not rebuild SDL; a changed vendored source, patch or sub-build argument does, because the install is reused only while its recorded manifest matches.
  • The prefix is keyed by target system and processor (plus Wayland, simulator and deployment-target suffixes where they apply), so a cross-build cannot overwrite the native install or reuse it.
  • A configure failure can be a nested dependency failure rather than a CNA CMake error. Such failures read CNA: SDL3 cmake configure failed (exit code N), ... build failed, ... install failed or ... installed, but <library> is not there; the cause is in the sub-build's own output.
  • If a parent project has already created all three targets SDL3::SDL3, SDL3_image::SDL3_image and SDL3_mixer::SDL3_mixer before adding CNA, the vendored setup returns immediately and uses them. With CNA_USE_SYSTEM_SDL=ON it instead calls find_package(SDL3 REQUIRED), find_package(SDL3_image REQUIRED) and find_package(SDL3_mixer REQUIRED). With CNA_ENABLE_SDL=OFF none of this runs.

The sub-builds prune optional codecs. SDL_mixer is configured with the external codec libraries switched off (SDLMIXER_MP3_MPG123, SDLMIXER_OPUS, SDLMIXER_MIDI_FLUIDSYNTH, SDLMIXER_WAVPACK, SDLMIXER_FLAC_LIBFLAC, SDLMIXER_VORBIS_VORBISFILE, SDLMIXER_VORBIS_TREMOR, SDLMIXER_GME, SDLMIXER_MOD_XMP all OFF), and SDL_image without AVIF, JXL, TIFF, WebP or libpng. CNA's audio and media behaviour on the SDL3 audio route has to be read against that concrete mixer build, not against everything SDL_mixer can do in principle; which formats SDL_mixer still decodes with its built-in decoders in this configuration was not established here. The ALSA route uses CNA's own mixer and its own decoder set instead (see Audio). The small default parallelism of these sub-builds is explained on Configuring CNA per target.

Host packages can change what compiles

"Dependency found" is not merely a link flag. At this snapshot optional host dependencies produce three different outcomes, and it helps to name which one a given package causes.

1. A backend is omitted and the API refuses at run time

FFmpeg is the example. modules/CMakeLists.txt computes CNA_FFMPEG_AVAILABLE from CNA_ENABLE_VIDEO (OFF, AUTO, ON) and four pkg-config modules: libavcodec, libavformat, libavutil and libswresample. libswscale is neither included nor linked; the conversion of decoded YUV frames to RGBA is CNA's own code in the FFmpeg backend. When the flag is off, the video-ffmpeg module is not entered and the media module compiles two small fallback units instead, so Video, VideoPlayer and the XNB video reader still exist and link, and playback throws System::NotSupportedException. The flag is also exported to the root scope, because the unit-test registration keys its real-decoder fixture suites on it; the comment there notes that an unset variable would silently drop those suites on capable hosts.

Cross-builds never probe the build host. On MinGW, Windows, Emscripten, Android and iOS the FFmpeg probe is skipped (AUTO falls back to the no-video backend, ON is a configure error), so a cross-configure cannot import the host's native pkg-config headers and libraries into a target build. The optional zstd support for compressed .cnb chunks follows the same rule more narrowly: it skips pkg-config whenever CMAKE_CROSSCOMPILING is set, because an Android configure once found the host's x86_64 libzstd while compiling ARM64 objects, and falls back to find_path/find_library, which respect the cross toolchain's root.

2. The feature compiles, and a format is refused at run time

Draco is the example. Without it the glTF importer still exists, and a primitive that uses KHR_draco_mesh_compression is refused with a named error at import time. The default is the repository's own gitlink at Draco 1.5.7 rather than a host package, so that conformance does not depend on which distribution version happens to be installed; CNA_USE_SYSTEM_DRACO=ON is an explicit packager's escape hatch. The default is ON everywhere except Emscripten, and modules/CMakeLists.txt records why: the pinned Draco demands an EMSCRIPTEN environment variable that current emsdk no longer exports (CNA now derives it from the toolchain), one Draco source uses std::all_of without including <algorithm>, which libstdc++ tolerates and libc++ does not (CNA force-includes the header into that one Draco target on Clang, leaving the submodule byte-identical), and Draco's own --whole-archive self-link makes wasm-ld report the whole library as duplicate symbols. Opting in on the web is therefore expected to fail at link time until the pin moves.

3. The configuration is refused

Some selections cannot be satisfied without a package and stop the configure with a message: VULKAN calls find_package(Vulkan REQUIRED), OPENGL4 calls find_package(OpenGL REQUIRED), CNA_PLATFORM=X11 or WAYLAND without their development packages names what to install and never falls back to SDL3, CNA_AUDIO_PLATFORM=ALSA needs the ALSA headers, CNA_CNB_ZSTD=ON needs libzstd, CNA_ENABLE_FONT_PIPELINE=ON needs FreeType, and CNA_ENABLE_VIDEO=ON fails on a target outside the FFmpeg integration. These three outcomes (source omitted with a run-time refusal, format rejected at run time, configuration refused) are what a report should distinguish instead of writing "optional".

Sibling repositories are part of the workspace contract

sharp-runtime

sharp-runtime is required by every build. CNA_SHARP_RUNTIME_ROOT defaults to ../sharp-runtime; when the directory has no CMakeLists.txt the root configure stops with a message explaining that it is a separate checkout, not a submodule, and printing a clone command. cmake/SharpRuntimeConsumption.cmake then detects the checkout's shape: if the target SharpRuntime::Core.Base exists the checkout is modular and each CNA module links exactly the components it names through cna_link_sharp_runtime(); otherwise it is treated as the older monolithic shape and every module links the single archive, a fallback kept for old checkouts. CNA's default component closure is Core.Base, IO, Collections.Core, Collections.ObjectModel, Runtime, Threading, Text, Globalization, ComponentModel, Storage, Security.Cryptography, Xml and Resources, plus Xml.Serialization on non-Windows targets (on Windows that component would drag in a sibling source that includes <poll.h>, which neither MinGW nor MSVC provides; sharp-runtime's next branch has since guarded that include in 88c12f15, but CNA's rule at this snapshot still excludes the component on Windows). The root file merges this closure into an existing cache value, so a build directory configured before a component joined the closure picks it up on the next reconfigure instead of failing at link time. A checkout whose branch lacks Resources or Xml.Serialization (sharp-runtime's main and develop) fails inside sharp-runtime's own configure, which rejects the requested component with “Unknown Sharp Runtime component”, which is why the sibling must be sharp-runtime's next branch at this snapshot.

No part of CNA checks which sharp-runtime commit it got. The alpha.1 release verification recorded a sharp-runtime revision as release evidence, not as a lock, and at this snapshot CI obtains the sibling in two different ways: most jobs use scripts/ci/clone_siblings.sh, which picks the first existing branch among the pushed branch, the pull request's target, next and develop, while several workflows pin older commits that predate the components listed above (the list is on Platforms: CI).

easy-gl, meta-gl, free-direct and free-api

The five EasyGL identities need ../easy-gl, and easy-gl in turn expects ../meta-gl. Unlike sharp-runtime, the easy-gl location has no override: cmake/RendererSelection.cmake tests and adds the fixed relative path, once, even when several GL identities share a multi-renderer build. FREEDIRECT expects ../free-direct, whose own CMake adds ../free-api and reuses the SDL targets CNA has already created, so the chain shares CNA's SDL build. A checkout that clones only CNA and sharp-runtime can therefore configure SDL_RENDERER or VULKAN but not the Linux default, OPENGLES3.

Do not paper over a missing sibling by copying its sources into CNA's tree or build directory. A sibling's own commit, tests, public targets and other consumers are part of any claim made about a build that uses it; a copy erases all of that. The roles and revisions of the siblings are on The CNA ecosystem.

Renderer dependencies are deliberately asymmetric

RendererHow its dependency arrivesOffline or override
WEBGPU (native)the official wgpu-native v29.0.1.1 binary release for the target (x86_64 or aarch64 on Linux, macOS and MSVC Windows; x86_64 only for MinGW), downloaded into the build tree's _deps directory and SHA-256-verified before extraction, so unlike the SDL install it is per build treeCNA_WEBGPU_ROOT with CNA_WEBGPU_AUTO_DOWNLOAD=OFF
WEBGPU (Emscripten)Emscripten's emdawnwebgpu port: the browser supplies WebGPU, nothing is downloadednone needed
FNA3D and every compiled-effects optionFNA3D fetched at tag 3240147 with its MojoShader submodule; CNA's MojoShader patch series applied idempotentlyFETCHCONTENT_SOURCE_DIR_FNA3D; the patch script is re-run explicitly on such a checkout, so CNA patches a supplied offline tree in place
PORTABLEGLone header fetched at a pinned commitFETCHCONTENT_SOURCE_DIR_PORTABLEGL
SDL_GPUSDL3 itself, plus SDL_shadercross and SPIRV-Cross when CNA_SDL_GPU_SHADERCROSS is on (default on Windows and Apple)the CNA_SDL_SHADERCROSS_GIT_* and CNA_SPIRV_CROSS_GIT_* variables
VULKAN, OPENGL4host SDK or system library, found with find_package(... REQUIRED)the host's package manager
Direct3D, Direct2D, GDI, METALthe platform SDK of the target toolchainthe toolchain
The five GL identities; FREEDIRECTsibling checkouts (above)none for the path

Accordingly, "every identity CMake accepts" is not "every renderer dependency is available on this host". A name can pass the identity check and still be refused a few lines later by its SDK gate, its platform gate or a missing sibling; how the selection file orders those checks is on Renderer selection internals.

How CNA itself is consumed

The C++ framework is a build-tree product. It has no install(), export() or CPack rules; a game adds the checkout with add_subdirectory (with the same siblings next to it), usually with CNA_BUILD_TESTS and CNA_BUILD_EXAMPLES off, and links CNA or narrower aliases. Several details exist so that this works: the legacy-root guard, the generated CNA/Version.hpp and the graphics module's stb include root are anchored on CNA_SOURCE_DIR/CNA_BINARY_DIR rather than CMAKE_SOURCE_DIR/CMAKE_BINARY_DIR, which in a subproject name the consumer's root; a parent may supply the three SDL targets itself; and the packaged Android demo adds the CNA root this way and reuses CNA's own SDL install. CNA is a CMake target and CNA::Math and the others are aliases; neither implies a file called libCNA. On native ELF builds with CNA_SHARED_LIBRARY the runtime file is libcna.so (target cna_shared, alias CNA::Shared); elsewhere the modules are static archives composed into the final executable.

The optional C API is the only install surface, and it is deliberately narrower than the C++ framework. Its rules install the shared CNA::CApi library (and a static CNA::CApiStatic archive on non-Apple Unix builds when Python 3 is available), the C headers, CNAConfig.cmake, CNACTargets.cmake and a package-version file, together with the vendored SDL shared libraries it needs at run time (not SDL's own CMake or pkg-config files, and not FFmpeg, which stays a system dependency). The package version is read from CNA_ABI_VERSION_* in abi.h, so it is the C ABI version, not the product version, and its compatibility rule is SameMajorVersion. An external consumer test (CApi_InstalledConsumer, building hello_cna.c through find_package(CNA CONFIG)) exists in the source; no workflow builds the C API library, so its status is described on C API: intended build. The package does not turn the C++ module archives into an installed C++ SDK and does not remove the sibling contract for source builds.

Evidence and limits

Read at 009d40f5 from .gitmodules, cmake/ThirdPartySDL.cmake, the other cmake/ThirdParty*.cmake files, cmake/SharpRuntimeConsumption.cmake, cmake/RendererSelection.cmake, modules/CMakeLists.txt, modules/c-api/CMakeLists.txt and CMakePresets.json. Nothing was configured, fetched, built or installed. The effect of a sharp-runtime branch without the required components is read from the target resolution, not reproduced; the six-to-sevenfold recursion cost is CNA's own recorded measurement.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.