Physical module dependency map

CNA snapshot 009d40f5  ·  Development › Repository & Ownership  ·  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. Every edge was read from the CMake files at this snapshot and recomputed by parsing them; no configure or build was run, so this is the declared graph, not the transitive closure of one configuration. The multi-renderer helper-directory caveat is a reading of the CMake conditions, not a tested failure.

CNA's physical modules are not a layering diagram. A maintainer has to keep four things apart: source ownership (which directory a translation unit lives in), compile and include visibility, the static link closure (which archives an executable receives) and runtime ownership (who destroys an object). modules/CMakeLists.txt declares one target per physical module, composes them into the interface umbrella CNA, and enforces a source-partition gate; a handful of private reverse links solve real static-archive cycles. The graph tells you what compiles, links and rebuilds. It does not tell you who owns a runtime object — for that use the ownership map.

There are 23 directories under modules/: 22 framework modules (the set the partition gate calls _cna_framework_modules) plus renderers/, which holds 21 implementation-family directories and the two shared helper directories common/d3d and common/mojoshader. The per-module header, source and test counts are generated on the module index; this page is about the edges between them.

A useful partial graph of the broad runtime closure

Every edge on this page was read from the target_link_libraries calls in modules/CMakeLists.txt, in each module's CMakeLists.txt and in modules/renderers/CMakeLists.txt, and recomputed by parsing those files (no configure was run). It therefore shows what the CMake files declare, not the transitive closure of one particular configuration. Arrows below are the PUBLIC edges; the private ones are in the table, which lists CNA targets and notable third-party libraries and omits the sharp-runtime components each module adds through cna_link_sharp_runtime.

tier 0  cna_core_headers (INTERFACE)   cna_core   cna_diagnostics   cna_build_config (INTERFACE)
            ▲ PUBLIC
tier 1  cna_math    cna_platform    cna_storage    [cna_inspector]    cna_design → cna_math
            ▲ PUBLIC   (graphics_core also → cna_core, cna_diagnostics)
tier 2  cna_graphics_core  ↔  cna_input          graphics_core → input is PRIVATE, input → graphics_core is PUBLIC
            │  PRIVATE, one per compiled identity family
        cna_renderer_<family>  → graphics_core, core, math   (PRIVATE reverse edges)
tier 3  cna_audio  ↔  cna_media               audio → media is PRIVATE, media → audio is PUBLIC
tier 4  cna_content → graphics_core, audio, media, math, core
        cna_content_pipeline → cna_content     (build-time only; linked by cna_content_compiler)
tier 5  cna_runtime → graphics_core, input, content, audio, media, core, diagnostics, math, platform
tier 6  cna_devices, cna_devices_ext, cna_phone → cna_runtime      cna_graphics_ext → cna_graphics_core
        CNA_GamerServices → cna_runtime, cna_storage   ←  CNA_Net (+ enet)      [CNA_ENABLE_NET]
        cna_c_api (shared library) → the modules above, all PRIVATE   [CNA_BUILD_C_API]

CNA (INTERFACE umbrella) = runtime + devices + devices_ext + graphics_ext + cnaext + storage + content
                           + media + audio + input + graphics_core + diagnostics + core + math
                           + platform + build_config + the default renderer target
Target (alias)Created byPUBLIC linksPRIVATE linksPresent when
cna_core (CNA::Core)cna_add_module; a separate INTERFACE target cna_core_headers (CNA::CoreHeaders) carries the core include rootsnonenonealways
cna_diagnostics (CNA::Diagnostics)cna_add_modulenonenonealways; the instrumentation compiles to nothing when CNA_DIAGNOSTICS=OFF
cna_math, cna_platform, cna_storagecna_add_modulecna_core_headersnonealways
cna_design (CNA::Design)cna_add_modulecna_mathnonealways built; not in the umbrella
cna_inspector (CNA::Inspector)cna_add_module, plus the cna-inspector executablecna_core_headers, cna_diagnosticsnoneonly CNA_BUILD_INSPECTOR=ON; a configure error on Emscripten, Android and iOS
cna_graphics_core (CNA::GraphicsCore)cna_add_module; the generated renderer registry is compiled into itcna_core, cna_math, cna_diagnostics, cna_platformcna_input, every target in CNA_RENDERER_TARGETSalways
cna_input (CNA::Input)cna_add_modulecna_graphics_core, cna_math, cna_core, cna_platformnonealways
cna_audio (CNA::Audio)cna_add_modulecna_core, cna_diagnostics, cna_mathcna_platform, cna_media, cna_input, and the selected audio backend's SDL3/SDL3_mixer, SDL2 or dl librariesalways
cna_media (CNA::Media)cna_add_modulecna_audio, cna_graphics_corecna_input; cna_video_ffmpeg when FFmpeg is availablealways; link-complete without FFmpeg
cna_content (CNA::Content)cna_add_module; also compiles tools/gltf_to_cnj/gltf_to_cnj.cpp with CNA_GLTF_TO_CNJ_NO_MAIN=1cna_graphics_core, cna_audio, cna_media, cna_math, cna_corecna_draco when Draco is available; zstd when CNA_CNB_ZSTD finds italways
cna_content_pipeline (CNA::ContentPipelineBuild)cna_add_modulecna_contentFreeType, FFmpeg and zlib when their probes succeedalways built; linked only by cna_content_compiler
cna_runtime (CNA::Runtime)cna_add_modulecna_graphics_core, cna_input, cna_content, cna_audio, cna_media, cna_core, cna_diagnostics, cna_math, cna_platformnonealways
cna_devices, cna_devices_ext (CNA::Devices, CNA::DevicesExt)cna_add_modulecna_runtime, cna_graphics_core, cna_core, cna_math (devices_ext also cna_platform)nonealways built; the extension code inside is compiled under CNA_DEVICES
cna_graphics_ext (CNA::GraphicsExt)cna_add_modulecna_graphics_corenonealways built; the extension code inside is compiled under CNA_CNAEXT
cna_phone (CNA::Phone)cna_add_modulecna_runtime, cna_core_headersnonealways built; not in the umbrella
CNA_GamerServices, CNA_Netplain add_library (no cna_add_module)GamerServices: cna_runtime, cna_storage; Net: CNA_GamerServices and the vendored enetnoneonly CNA_ENABLE_NET=ON (the default)
cna_video_ffmpeg (CNA::VideoFfmpeg)plain add_librarynone of CNA's own (FFmpeg through pkg-config)noneonly when the FFmpeg probe succeeds
cna_c_api (CNA::CApi)plain add_library, SHARED (STATIC under Emscripten)its own include rootcna_core, cna_platform, cna_runtime, cna_graphics_ext, cna_storage, cna_content, cna_media, cna_devices, cna_devices_ext, CNA_Net, CNA_GamerServicesonly CNA_BUILD_C_API=ON, which requires CNA_ENABLE_NET=ON
cna_renderer_<family>cna_add_renderer() (or add_library plus cna_renderer_common_setup() for directx9 and software)cna_renderer_common (INTERFACE, forwards cna_build_config)cna_graphics_core, cna_core, cna_mathonly the families the configuration selected

The usual one-paragraph summary of these edges holds against the source and can be stated more exactly. cna_graphics_core publicly links math, core, diagnostics and platform, and privately links input and every compiled renderer target. cna_input publicly links graphics, math, core and platform: the apparent reverse direction is real (see the cycle section). cna_audio publicly links core, diagnostics and math and privately platform, media and input; cna_media publicly links audio and graphics and privately input. cna_content publicly links graphics, audio, media, math and core because a reader can construct those runtime objects. cna_content_pipeline publicly links content but is not in the game umbrella: cmake/ToolContentPipeline.cmake defines cna_content_compiler (alias CNA::ContentCompiler, a static library over tools/content/content.cpp) as the only consumer, and the cna-content executable is a small main over that library. cna_runtime publicly links the nine targets in the table, which is why a change to a foundational public header or to GraphicsDevice has a much larger rebuild and review radius than a change to one content importer. The exact per-target definitions are in graphics, input, audio, media, content and runtime.

Composition is not source ownership

cna_add_module(<target> <Alias> sources...) creates a STATIC library named cna_<name>, an alias CNA::<Alias>, links cna_build_config PUBLICly and publishes the calling module's own include/ directory. Eighteen of the 22 framework modules use it (inspector only when CNA_BUILD_INSPECTOR is ON); gamer-services, net, video-ffmpeg and c-api define their targets by hand, and core adds the extra header-only target. Renderer families use the analogous cna_add_renderer() helper described in renderer selection internals.

cna_build_config is the shared public surface and is deliberately limited to ABI and header requirements: C++23, XNA5, the default renderer's identity macro, CNA_CNAEXT and CNA_DEVICES when those options are on, CNA_DRACO_AVAILABLE, CNA_VIDEO_AVAILABLE and CNA_FFMPEG_AVAILABLE, CNA_DIAGNOSTICS_LEVEL, and SOUND_ENABLED when the audio platform is SDL3 or ALSA. Compiler policy lives elsewhere: cmake/BuildPerformance.cmake defines cna_project_options and its siblings, and cna_apply_build_support_to_cna_targets() links them PRIVATE to every CNA-owned compiled target (executables and static, shared, module and object libraries) at the end of the root file, so they travel only as link-only dependencies and are not part of a consumer's compile requirements.

The CNA umbrella is an INTERFACE library, not a module. Its part list is exactly: runtime, devices, devices_ext, graphics_ext, the cna_cnaext umbrella, storage, content, media, audio, input, graphics_core, diagnostics, core, math, platform, build_config and the default renderer target. CNA::Design, CNA::Phone, CNA::Inspector, CNA::ContentPipelineBuild, CNA::GamerServices and CNA::Net are not in it and are linked explicitly. CNA::CnaExt (cna_cnaext) composes the real graphics-ext and devices-ext modules; the historical cna_noxna spelling survives only as a comment. Non-default renderer targets reach a static executable through cna_graphics_core's private edges.

Renderer targets follow one naming rule: modules/renderers/<family> builds cna_renderer_<family> with hyphens turned into underscores (canvas, direct2d, directx9, directx11, directx12, easygl, fna3d, freedirect, gdi, headless, html_dom, metal, opengl4, portablegl, sdl_gpu, sdl_renderer, software, stub, svg_dom, vulkan and webgpu). The name is derived from the directory that calls cna_add_renderer(), so no family reads a global target variable, and one family serves several identities (EasyGL serves five). The helper targets are cna_renderer_common (INTERFACE), cna_renderer_d3dcommon (the shared Direct3D 11 and 12 core), cna_renderer_mojoshader_effect (the MojoShader-to-CNA translation shared by every family that runs compiled XNA effects), cna_renderer_d3d9_effect (DirectX 9's effect archive), cna_renderer_metal_headers (always defined, because the Metal policy suites are host-portable) and cna_renderer_software_headers (defined in the GDI configuration).

The declaration order in modules/CMakeLists.txt is readable, not a topological proof: target_link_libraries resolves names at generate time, so forward references and cycles are legal. The same file ends with the physical source-partition gate: every .cpp and .mm under modules/ must sit under a declared module's src, tests, examples or benchmarks tree (renderers under renderers/<family> or renderers/common/<helper>), and a revived root src/ or include/ tree is a configure error. Cross-module internal headers are published under a module's include/CNA/Internal; another module's private src/ is not a shared include root. Three real exceptions are worth knowing:

  • GDI compiles eight software-owned 2D translation units (the list CNA_GDI_SOFTWARE_SOURCES: framebuffer and texture allocation, framebuffer, 2D texture, 2D render target, 2D state, sprite batch and the 2D renderer) into its own archive; the software CMake file publishes the list in the GDI configuration, and physical ownership stays with software. The GDI CMake file's status message hard-codes “3 GDI-owned units”, while modules/renderers/gdi/src holds four .cpp files (a stale count in a message, found by reading).
  • Every renderer target gets modules/platform/src as a PRIVATE include root (cna_renderer_common_setup). The comment ties it to the four allowlisted SDL3-backed families (sdl-renderer, sdl-gpu, fna3d, freedirect, each setting REQUIRES_PLATFORM SDL3), which resolve a window id at one SDL3 implementation edge; the platform tree is not part of any renderer's public include contract.
  • A few targets compile sources from tools/: cna_content takes the glTF converter, and cna_content_compiler is defined in cmake/ rather than in a module. The partition gate scans modules/ only.

Moving a .cpp between modules is therefore an ownership and build change, not a cosmetic rename. Each module also names its sharp-runtime components through cna_link_sharp_runtime(<target> PUBLIC|PRIVATE ...) (SharpRuntimeConsumption.cmake), for example Core.Base for core and IO, Runtime and Threading for storage; against a monolithic sharp-runtime checkout the same helper links the single SHARP_RUNTIME archive.

The declared static-archive cycles

Parsing all link edges and computing strongly connected components finds exactly two groups among the framework targets: {cna_graphics_core, cna_input, the compiled renderer targets and their helper archives} and {cna_audio, cna_media}. Each cycle is declared on both members so CMake repeats the archives on the link line, and each has a stated reason in the CMake comments:

CycleWhy it existsDeclared as
cna_graphics_core ↔ cna_inputGraphicsDevice updates TouchPanel display metrics and the Mouse/TextInputEXT window binding, while Input::MouseCursor builds on Texture2Dgraphics→input PRIVATE, input→graphics PUBLIC; LINK_INTERFACE_MULTIPLICITY 3 on both targets
cna_graphics_core ↔ each selected cna_renderer_<family>the device constructs renderers through descriptors whose create function lives in the family archive, while renderer code calls back into graphics (for example Effect::Apply), core (Logger) and mathgraphics→${CNA_RENDERER_TARGETS} PRIVATE; each family → graphics_core, core, math PRIVATE. cna_renderer_d3dcommon and cna_renderer_mojoshader_effect reach back the same way
cna_audio ↔ cna_mediaFrameworkDispatcher::Update() (in the audio module) pumps dynamic streams, microphone buffers, MediaPlayer and the touch panel, while media plays through the mixeraudio→media PRIVATE, media→audio PUBLIC; default repetition (no multiplicity property)

Two repetitions were enough while the graphics/input cycle was two archives deep. The DirectX families added a third archive (cna_renderer_d3dcommon, PUBLIC for directx11 and directx12) that reaches back into the graphics core, so both modules/graphics/CMakeLists.txt and modules/input/CMakeLists.txt set LINK_INTERFACE_MULTIPLICITY 3; the comment there records a real failing link of a DirectX 12 test executable. These are static archive relationships, not permission to introduce arbitrary source-level cycles, and deleting an edge can pass object compilation and still fail the final link.

Optional modules and backend artifacts

  • FFmpeg video. video-ffmpeg is entered only when CNA_FFMPEG_AVAILABLE is true: CNA_ENABLE_VIDEO is not OFF, the target is not MinGW, Windows, Emscripten, Android or iOS (ON there is a configure error), and the libavcodec, libavformat, libavutil and libswresample pkg-config modules are found. cna_media keeps the stable public API and links the backend privately; without it the media module compiles VideoDecoderUnavailable.cpp and AudioDurationProbeUnavailable.cpp instead.
  • Networking. gamer-services and net are entered only with CNA_ENABLE_NET (default ON). The opt-in c-api adapter links across the native modules and needs that closure for its current surface: CNA_BUILD_C_API=ON with CNA_ENABLE_NET=OFF is a configure error in the root file.
  • Extension code in always-built modules. graphics-ext and devices-ext targets always exist; their sources are guarded by CNA_CNAEXT and CNA_DEVICES, which are default-OFF options published through cna_build_config.
  • Other build-time probes configured in the same file: Draco (CNA_ENABLE_DRACO, default ON except under Emscripten, pinned submodule unless CNA_USE_SYSTEM_DRACO) and optional zstd for .cnb chunks (CNA_CNB_ZSTD, AUTO). Both reach cna_content as compile definitions and private links.
  • Renderer families. Family targets are created after the configure-time identity, platform and dependency checks; the generated registry source is compiled into cna_graphics_core so that static descriptors are reachable. A build can contain several identities but exactly one default identity macro on cna_build_config; every other family's identity macro is private to its own target. Three helper directories are entered from conditions that test the default identity rather than list membership: common/d3d (default DIRECTX11 or DIRECTX12), software for GDI (default GDI) and the Metal family target (default METAL). By reading, a multi-renderer set whose non-default member needs one of them would not have that helper target defined by those lines; none of CNA's multi-renderer CI sets contains such a pair, and no configure was run for this page.
  • The shared runtime. On native ELF with GNU or Clang and CMake 3.27 or newer, CNA_SHARED_LIBRARY defaults to ON. SharedRuntimeLibrary.cmake then defines cna_shared (alias CNA::Shared, output name cna, i.e. libcna.so) from every CNA-owned static library in the umbrella's closure with WHOLE_ARCHIVE, adds every compiled-in renderer target (not only the default) to that closure, and re-points the CNA umbrella at the shared library plus compile-only usage requirements. The walk runs in a deferred call (cmake_language(DEFER)) at the end of the top-level file, when every target has its final link libraries.

These are conditional graph branches. Do not count directories as proof that a piece participated in a particular binary.

How to use this graph during maintenance

  1. For a changed file, locate its physical module and that module's CMakeLists.txt. Decide whether the changed header is public, under CNA/Internal or private src/.
  2. Follow the direct PUBLIC and PRIVATE target links and the umbrella. PRIVATE static links can still appear on a final executable's link line. Ask whether an optional gate (CNA_ENABLE_NET, FFmpeg, CNA_BUILD_C_API, CNA_BUILD_INSPECTOR) excludes the module.
  3. For a linker error, inspect the final link line and the intentional reverse edges before changing C++ includes. A static-archive cycle may require repetition, and deleting an edge can pass object compilation but fail the final link.
  4. For a runtime bug, switch to the ownership map. A link dependency does not imply object ownership.
  5. For tests, inspect the source filtering in cmake/UnitTests.cmake (dozens of list(FILTER ...) exclusions keyed on the configuration) and the configured ctest -N; not every module's tests enter every build.

The CMake architecture page traces configure order, selectors and the generated registry. Read the root CMakeLists.txt for option declarations and the order in which the selection files run, modules/CMakeLists.txt for umbrella and partition policy, and a module's own CMake file for its exact edges. This map is a maintainer's starting model, not a generated transitive adjacency matrix; that depends on configuration, toolchain and optional targets.

The same subsystem is explained at four altitudes. These are the neighbouring pages at each one.

Tests and validation
Test architecture