Skip to content
The CNA BibleCNA 0.1.0-alpha.1 Edition

Chapter 54 Components and CNA Integration

The most consequential sharp-runtime change near CNA’s audited revision did not alter a System:: method. On 2026-08-10, the runtime merged a modular remediation snapshot that replaced the one-archive build shape with named components. The merge touched 3,828 files and landed one day before the CNA revision examined for this book. A description based on the earlier monolith can therefore be internally coherent and still be wrong for the actual sibling pair.

This chapter follows one question through both repositories: when a CNA source includes a runtime header, what target supplies it, and how does that target reach the final program?

CNA source includes a System or SharpRuntime header. Its owning CNA CMake target declares direct namespaced sharp-runtime components. The sharp-runtime registry materializes static, interface, and alias targets and propagates their declared transitive link closure. A normal embedded CNA configure makes all components available, while each CNA target keeps a narrow link interface.
Figure 54.1: How CNA consumes modular sharp-runtime. Component availability and a target’s declared link interface are different questions.

54.1 Directories, components, and targets are different counts

The runtime has 41 module directories, but those directories register 44 components. The registry produces the shape in Table 54.1.

Table 54.1: The audited sharp-runtime component shape.
Kind Count Meaning
STATIC 30 Components with compiled implementation objects.
INTERFACE 13 Header-only surfaces or compatibility umbrellas carrying usage requirements.
ALIAS 1 Xml.XPath aliases Xml, whose implementation boundary is shared.
Total 44 Registered components across 41 physical module directories.

Two extra names explain why a directory count cannot predict the component count. Core and Collections are compatibility umbrellas over narrower components, and Xml.XPath is the alias. The remaining names cover families such as Core.Base, Collections.Core, IO, Text.Json, Threading.Tasks, and Net.WebSockets. Consumers link the namespaced form, for example SharpRuntime::Core.Base, rather than guessing an archive filename.

The module CMake files do not each call add_library directly. They register their kind, dependencies, include directory, and optional test dependencies with a common registry. The registry later materializes targets in a consistent order. Production sources and module tests are globbed within the registered directory, while a source-partition validator and an empty dependency allowlist make accidental ownership leaks visible. The generated component catalogue measures 92 direct production dependency edges at the pin; this edge count is distinct from both the 41 physical modules and 44 selectable/compatibility names.

54.2 Header names predict location, not the link closure

There is no SharpRuntime.hpp. If ported code needs System::IO::MemoryStream, it includes System/IO/MemoryStream.hpp; the IO component owns that public include directory. The component can in turn expose public dependencies needed by the header. Consequently, a consumer names its direct component and lets CMake propagate the remainder.

CNA directly includes 54 distinct sharp-runtime headers: 52 below System/ and two below SharpRuntime/. The latter count must not be misread as the total dependency. SharpRuntime/SharpRuntimeHelper.hpp supplies primitive aliases, while SharpRuntime/Prop.hpp supplies downstream property macros. Both belong to Core.Base; headers such as System/IO/BinaryWriter.hpp and System/Security/Cryptography/SHA256.hpp introduce other component needs.

54.3 Standalone selection and embedded selection differ

When sharp-runtime is the top-level CMake project, it exposes SHARP_RUNTIME_COMPONENTS. A developer may select a subset, or leave the default All. The compatibility SHARP_RUNTIME interface target exists only for that complete selection; a deliberately partial build should use namespaced component targets.

That cache option is created only when the runtime is the top-level project. CNA adds the runtime with add_subdirectory, so the option is not created and selection falls through to All. Every component is therefore available in a normal CNA build. CNA’s narrow component lists reduce each target’s link interface; at this revision they do not prevent unrelated runtime components from being configured and compiled.

This distinction prevents two opposite mistakes:

  • a narrow CNA declaration is meaningful because it documents and enforces the target’s link boundary;

  • it is not yet a build-time pruning mechanism for the embedded sibling.

54.4 CNA’s sibling contract

Sharp-runtime is CNA’s one unconditional sibling dependency. CNA first forces SHARP_RUNTIME_BUILD_TESTS off; otherwise embedding it would add 37 runtime test executables to every CNA build. It then resolves CNA_SHARP_RUNTIME_ROOT, whose default is the adjacent ../sharp-runtime checkout. A missing CMakeLists.txt is a configure-time fatal error with a clone remedy. There is no submodule, vendored copy, FetchContent fallback, or reduced internal substitute.

After add_subdirectory, CNA loads its 59-line SharpRuntimeConsumption.cmake adapter. The adapter recognizes two historical build shapes:

Modular

SharpRuntime::Core.Base exists, so requested component names are translated to namespaced targets. This is the authoritative shape for the audited siblings.

Monolithic

That target does not exist, so CNA links the legacy SHARP_RUNTIME archive. This is a compatibility path for older checkouts, not the architecture of current sharp-runtime.

The detection intentionally tests a target rather than a version string. That makes the seam describe the capability CNA needs and avoids teaching CNA the runtime’s release-numbering history.

54.5 The linking helper makes closures explicit

Every CNA module calls cna_link_sharp_runtime(target visibility [Components...]). In a modular checkout, the helper prefixes each supplied name with SharpRuntime::; in a legacy checkout it falls back to the single archive. Omitting the optional names uses CNA’s default closure:

1 Core.Base
2 IO
3 Collections.Core
4 Collections.ObjectModel
5 Runtime
6 Threading
7 Text
8 Globalization
9 Storage

That list is not every transitive component. Dependencies such as Uri, TimeZone, ComponentModel, and Buffers arrive through the public edges of the named targets. Repeating the transitive closure in CNA would duplicate the runtime registry and make upgrades more fragile.

Eighteen CNA module call sites declare their own closure. Small modules can be genuinely narrow: math and input request only Core.Base. Graphics requests Core.Base, IO, Collections.Core, and Text. Networking adds Runtime and Threading; gamer services additionally needs Globalization. The renderer aggregation target uses the default closure because it spans more of the framework.

1 # Shape-independent CNA module declaration
2 cna_link_sharp_runtime(CNA_Input PUBLIC Core.Base)
3
4 cna_link_sharp_runtime(CNA_Net PUBLIC
5 Core.Base IO Collections.Core Runtime Threading)

The helper is therefore more than syntactic convenience. It lets present-day modules state precise boundaries while retaining a controlled path to old sibling checkouts.

54.6 Tests enforce component ownership

The modular test layout is part of the boundary design. Each component with tests gets a SharpRuntimeTests_<component> executable linked only to its component, gtest_main, and explicitly declared test-only dependencies. GoogleTest discovery adds the component name as a prefix. A test that accidentally reaches into an undeclared component can consequently fail to link; the old monolithic test executable could have masked the same dependency.

Thirty-six components currently contribute such an executable, and a separate integration executable brings the total to 37. Five module directories have no component tests: io-compression-zip, io-isolated-storage, security-cryptography-random, storage, and text-regular-expressions. The last is a 12-header interface component with no implementation and no tests, a concrete coverage gap rather than an implied success.

Outside GoogleTest, Python meta-tests validate boundaries and consumer fixtures exercise both positive and intentionally failing configurations. A negative fixture is successful when a forbidden dependency fails to compile or link; treating every non-zero compiler exit as a CI failure would invert the purpose of that test.

54.7 Upgrade rules for the seam

The safe upgrade procedure follows directly from the architecture:

  1. 1.

    pin and identify both CNA and sharp-runtime revisions;

  2. 2.

    configure through CNA_SHARP_RUNTIME_ROOT rather than moving sibling directories to satisfy a hidden assumption;

  3. 3.

    confirm the shape-detection message and inspect unexpected fallbacks to the monolith;

  4. 4.

    keep each CNA module’s direct component list narrow and let public dependencies close transitively;

  5. 5.

    run runtime boundary fixtures when the registry changes, then run CNA’s normal build and tests with runtime tests still disabled in the embedded build;

  6. 6.

    distinguish “all components were built” from “this CNA target links only its declared closure” in any verification report.

The final rule is the most important. Modularity has two observable layers here: what the parent configures and what an individual target consumes. At the audited revision CNA gets all 44 components from its sibling, but its module targets no longer pretend that they need all 44. That narrower declaration is the durable integration contract on which later build-time pruning can be based.

Type at least three characters. Results are ranked by how often and where the words occur.