Skip to content
CppCooperProgramming Portfolio

Article

Keeping optional modules optional in Cheryl’s installed SDK

ArchitectureGames

An optional module needs to remain optional when another application consumes the engine. Its target must carry the headers, definitions and libraries that its public API requires. Its implementation dependencies need an owner. An installed package then has to preserve that arrangement after the producer’s checkout and build directory disappear.

That is the connection between the module layout and development SDK in Cheryl Engine 4. Source composition and installed consumption use the same public target names. Their contract determines what is selected, what is exported, and what the application still owns.

Keep one Engine and select its integrations

Cheryl::Engine owns the neutral runtime, input and rendering contracts alongside core services. Concrete platform, graphics, UI and audio integrations live in separate libraries. The root build conditionally adds them and selects the configured assembly. Engine has no reverse dependency on those integrations.

Each selected integration has a specific dependency owner. Native GLFW owns its GLFW/Gainput integration. OpenGL uses the native platform integration. TGUI and RmlUi consume Engine contracts and expose their toolkit authoring APIs. Miniaudio supplies an independent audio implementation. The input-routing path is one example of a neutral contract with a selected native producer and separate UI consumers.

Standalone source modules reuse a supplied Cheryl::Engine. If none exists, the module helper bootstraps one checkout with unrelated selections disabled in a local variable scope. It avoids changing the host’s cached choices or compiling a private Engine copy inside each integration. A separate module earns its place through a real selection or isolation benefit; directory size alone supplies little reason for another binary.

An aggregate target also selects headers

Cheryl::Cheryl is an interface target. It forwards Engine and the configured components without creating a combined binary. An application can instead link granular targets such as Cheryl::Engine or Cheryl::UI::RmlUi.

Module registration adds a public feature definition such as CHERYL_HAS_UI_RMLUI=1. Selection adds the module to an owned aggregate. <cheryl/cheryl.h> includes Engine plus the module/support umbrellas, which use those feature definitions to expose the selected APIs. Registration and selection are separate operations, and including a header does not link its implementation.

A supplied SDK’s aggregate keeps the selection chosen by its producer. Adding a source module alongside that package leaves the imported aggregate unchanged; the application explicitly links the new module too. This prevents a helper from quietly rewriting a dependency supplied by its host.

Consume the installed package from another project

The repository contains a tiny consumer that can be copied outside the checkout. Its CMake entry point begins exactly as follows:

cmake_minimum_required(VERSION 3.28)
project(cheryl_standalone_sdk_consumer LANGUAGES CXX)
find_package(Cheryl 4 CONFIG REQUIRED)
enable_testing()
add_executable(sdk-standalone main.cpp)
target_link_libraries(sdk-standalone PRIVATE Cheryl::Cheryl)
add_test(NAME sdk-standalone COMMAND sdk-standalone)

The project’s main.cpp includes the public umbrella, exercises selected API references and rejects leaked private implementation headers. It contains no Engine sources or hand-written dependency list. Public C++23 requirements, include roots and compiled logging policy arrive through the imported targets.

For a compatible Linux Release SDK, the documented configure/build commands select its extracted prefix:

cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_PREFIX_PATH=/absolute/path/to/extracted-sdk
cmake --build build --parallel

This example selects an already produced SDK. Changing source-build options in the consumer cannot recompile the package. The installed headers and binaries must agree on the producer’s configuration. The SDK manifest records its platform, compiler, configuration, enabled features and component versions; matching a major package version alone does not establish C++ ABI compatibility.

Export the dependency closure deliberately

Module registration also does not install a component. SDK declarations explicitly map owned targets to public export names. Separate installation rules collect the selected public-header roots, package files and dependency material. New modules therefore need target export, headers and feature metadata as well as source-build selection.

The dependency split follows the API. Public templates such as GLM and CTTI remain consumer requirements; Startup exposes CLI11, and UI integrations expose their native authoring headers. Gainput, GLAD and miniaudio remain private implementations behind their owning modules. The build’s dependency declarations reject assigning the same compiled private dependency to two Cheryl owners.

Static Cheryl still needs a complete final link. Static SDKs export the embedded archive closure and retain required platform discoveries, including selected OpenGL/X11 or libudev requirements. Both SDK linkages import packaged private native libraries, such as FreeType and HarfBuzz, without rediscovering their development packages. Shared SDKs omit the additional platform discoveries. The package configuration makes that distinction; it also discovers Threads for either linkage.

Installation still leaves runtime responsibilities

A static Engine does not imply every native dependency is static. The package exposes Cheryl_RUNTIME_DIRECTORIES, which can contain runtime libraries for either Cheryl linkage. Linux libraries use an origin-relative search path for packaged neighbors. Windows consumers must make the selected DLLs available; importing the SDK does not automatically copy them beside an application executable. Graphics drivers and compatible host runtimes remain external requirements.

Library lifetime matters too. Retained handles carry their release contexts, and their owning libraries must remain loaded while those handles or deleters are in use. This SDK does not define arbitrary plugin hot unloading or mixed module versions. The same ownership requirement appears inside the runtime in resource lifetimes.

The SDK verification driver relocates an extracted package, checks import files for producer paths, then copies the standalone consumer outside the repository with private package discovery disabled. The copied consumer covers selected API linkage and offline calls. A live desktop session adds graphics, display and physical-device requirements beyond that probe.

Cheryl Engine 4 reading path

  1. Runtime ownership and lifecycle
  2. Prepared frames and rendering handoff
  3. Retained resources and cleanup
  4. Input routing and editable menus
  5. Unicode layout and retained text
  6. Optional modules and the installed SDK (this article)