Project-Local Dependencies
This guide owns the reusable contract for libraries, SDKs, frameworks, tools, and runtime payloads added by a game repository that embeds FOnline. It covers dependencies that the game needs but the reusable Engine does not own.
Use ThirdParty Maintenance for source vendored in
Engine/ThirdParty/. Use Native Extensions for the C++
bridge that consumes a project dependency. The embedding project must keep its
exact inventory, product-specific integrations, credentials, providers, and
release policy in its own repository.
Dependency decision
Use this sequence for every project-local dependency:
- Classify the owner first: Engine, embedding project, revisioned companion, project build tool, or operating-system prerequisite. Registration through Engine helpers does not transfer project ownership.
- Select and pin the delivery model, then record version, provenance, integrity, license, supported platforms/toolchains, update path, and rollback pin in the project.
- Create the project target after Engine third-party targets exist and before
BuildCoreLibraries()consumes its revision-pinned library lists. Append it only to the requiredFO_COMMON_LIBS,FO_SERVER_LIBS,FO_CLIENT_LIBS,FO_BAKER_LIBS, orFO_TESTING_LIBSlist. - Distinguish requested, compiled, and initialized-at-runtime states. Keep allocator ownership, exceptions, CRT/toolchain, architecture, generated headers, and C ABI boundaries explicit rather than treating header presence as runtime support.
- Treat a development copy and a release payload separately. Declare runtime files through package declarations, including target paths, notices, runtime file hashes, and signatures or signing ownership. Then start and probe the packaged artifact from an isolated directory on every claimed platform.
A complete release-delivery record names each evidence class separately: package declarations, licenses and notices, runtime-file hashes, signatures or signing ownership, and an isolated start of the packaged artifact. One of these is not shorthand for the others.
Do not stop at a successful include or link. Acceptance must prove the intended requested, compiled, and initialized states; shared-library ABI and allocator ownership; package payload and integrity; and isolated runtime behavior.
Contract Status
The project-facing CMake interface is experimental and revision-pinned. The
current Engine exposes no declared helper for role-scoped project-library
registration. Embedding projects append targets to the current library lists,
which are implementation state and may change with the Engine revision. Pin the
Engine, re-audit the lists, and rebuild dependencies and extensions together
after every pin change.
The Engine owns:
- the
COMMON,SERVER,CLIENT,BAKER, andTESTSlibrary lists; - the core-library targets that consume those lists;
- the core-library graph that consumes each role list;
- package declarations and generic package assembly mechanics;
- the documented allocator, pointer, exception, and native-extension rules.
The embedding project owns:
- dependency selection, version, source, integrity, license, and support term;
- the CMake target, feature gate, role assignment, and unsupported stub;
- generated headers, build tools, platform prerequisites, and ABI compatibility;
- runtime libraries, data files, notices, signing, and package acceptance;
- vulnerability response, update cadence, rollback, tests, and release evidence.
Registration never transfers ownership to the Engine. A library becomes an Engine dependency only through an explicit Engine change that moves the implementation, tests, maintenance record, and supported-platform obligation.
Choose The Owner First
Classify a dependency before adding files or CMake:
| Need | Owner and location | Rule |
|---|---|---|
| Reusable Engine runtime, format, renderer, or tool capability | Engine, normally Engine/ThirdParty/<name>/ |
Follow the Engine vendoring and public-contract review. |
| Game-only native bridge, service client, proprietary SDK, or content runtime | Embedding project, commonly SourceExt/<name>/ or Dependencies/<name>/ |
Keep its implementation, policy, and release evidence project-owned. |
| Reusable but optional integration that should not be Engine core | Revisioned companion repository | Publish an exact Engine compatibility range, its own tests, and a minimal embedding example. |
| Build-time generator or audit not linked into Engine roles | Project tooling tree | Pin its runtime/packages separately and do not add it to an Engine role. |
| Operating-system framework or host library | Project platform configuration | Name the supported hosts and fail configure when a required prerequisite is absent. |
Do not duplicate an Engine dependency in the project merely to reach its headers or target. If a project deliberately needs a different build or version, document symbol isolation, allocator/ABI boundaries, platform scope, and why the Engine copy cannot be reused.
Select A Delivery Model
Use the smallest model that gives deterministic builds and lawful delivery:
- Vendored source is preferred when the project must build the library on all supported hosts, apply a small reviewed patch, or avoid host-version drift. Pin the upstream version and archive hash, preserve required notices, and record pruning.
- Imported SDK target fits a proprietary or prebuilt SDK. Pin the SDK release, architecture, compiler/runtime compatibility, acquisition source, redistributable files, and license terms. Do not commit material that the license forbids distributing.
- System or platform library fits an OS API or a deliberately supported host prerequisite. Keep the use behind explicit platform checks and prove the minimum supported host. A successful developer-machine lookup is not a portable dependency contract.
- Package-manager or fetched source is acceptable only with an immutable version/commit and integrity lock. Release and CI builds must not silently select a newer package or depend on an unreviewed network response.
- Runtime-only payload fits a shared library, helper executable, model, or data file that is not compiled. It still needs a version, provenance, platform/architecture mapping, license decision, package rule, and launch acceptance test.
Do not use an unpinned branch, floating package range, ambient include path, or
unregistered find_package() result as production input.
Keep A Dependency Record
Every project-local dependency should have one authoritative record in the embedding repository. It may be a table, manifest, or dependency-owned README, but it must answer:
| Field | Required content |
|---|---|
| Identity | Upstream name, project target name, owner, and support contact. |
| Version | Exact release/tag/commit and the in-source or package metadata used to verify it. |
| Provenance | Official source URL or private artifact identity plus archive/commit hash. |
| Delivery | Vendored, imported SDK, system, fetched, tool-only, or runtime-only. |
| License | License identifier, retained files, attribution, redistribution and source-offer obligations. |
| Integration | Consuming Engine roles, native bridge, feature flag, generated files, and allocator hook. |
| Support | Platforms, architectures, toolchains, configurations, and unsupported behavior. |
| Package | Runtime files, target paths, signing owner, and acceptance probe. |
| Security | Advisory source, review cadence, secret boundary, and emergency disable/remove path. |
| Update | Local patches, pruning record, compatibility-coupled assets/data, tests, and rollback pin. |
The dependency source is authoritative for the version when it exposes one. Keep human inventories synchronized with that value; do not make a prose-only version string the build’s source of truth.
Integrate At The Project Boundary
Create project dependency targets after the Engine ThirdParty stage has
created reusable Engine targets and before BuildCoreLibraries() consumes the
role lists. Register project source before RegisterEngineSources() as usual:
StartProjectGeneration()
RegisterProjectOptions()
# Register handlers needed by Engine or project third-party CMake before the
# ThirdParty stage installs its find_package() interceptor.
RegisterFindPackageHandler(OptionalBackend NotFoundFindPackage)
AddThirdPartyLibraries()
add_subdirectory(Dependencies/ProjectCodec EXCLUDE_FROM_ALL)
# A project wrapper gives one stable target for upstream target-name changes,
# include classification, compile definitions, and transitive requirements.
add_library(ProjectCodec INTERFACE)
target_link_libraries(ProjectCodec INTERFACE upstream_codec)
target_include_directories(ProjectCodec SYSTEM INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/Dependencies/ProjectCodec/include")
list(APPEND FO_CLIENT_LIBS ProjectCodec)
list(APPEND FO_BAKER_LIBS ProjectCodec)
AddEngineSources(CLIENT SourceExt/ProjectCodecBridge.cpp)
RegisterEngineSources()
SetupCodeGeneration()
BuildCoreLibraries()
The current revision consumes FO_COMMON_LIBS, FO_SERVER_LIBS,
FO_CLIENT_LIBS, and FO_BAKER_LIBS when creating the corresponding core
libraries; FO_TESTING_LIBS feeds native test targets. Append before
BuildCoreLibraries(), avoid duplicates yourself, and fail project configure
for unsupported combinations. There is no dedicated mapper-only library list:
MapperLib consumes ClientLib, so a client dependency reaches Mapper with the
wider client role. A truly mapper-only dependency requires an explicit Engine
interface change instead of an invented FO_MAPPER_LIBS variable.
These lists are revision-pinned integration state, not declared helpers in
BuildTools/cmake/ProjectInterface.json. Re-check State.cmake and
CoreLibs.cmake on every Engine update. A helper’s presence under
BuildTools/cmake would not make it public unless the interface manifest also
declared it.
Route To The Narrowest Role
Dependency roles follow the native source roles:
| Role | Link owner | Typical consumers | Use for |
|---|---|---|---|
COMMON |
CommonLib |
Every enabled runtime/tool role | A genuinely common process/config primitive. Avoid placing a client or server SDK here for convenience. |
SERVER |
ServerLib |
Server and native tests that include it | Authority, persistence, server transport, or backend SDK code. |
CLIENT |
ClientLib |
Client plus current server-controller, Mapper, viewer, Baker, ASCompiler, and test paths | Rendering, input, client transport, or client SDK code. Account for the wider current consumer graph. |
BAKER |
BakerLib |
Baker, Mapper, viewers, ASCompiler, and tests | Resource import, validation, conversion, or authoring support. |
TESTS |
Native test targets | Engine-owned native tests | Focused test-only support; do not rely on it for runtime delivery. |
Split a dependency wrapper when different roles need different headers,
features, or runtime payloads. Do not route a library through COMMON just to
repair a missing symbol.
Control Package Discovery
The ThirdParty stage intercepts find_package() so nested third-party CMake
cannot silently use arbitrary host libraries. Register every expected package
name before AddThirdPartyLibraries():
- map a package to a vendored/imported target in a project handler;
- use
NotFoundFindPackagefor a supported optional backend that must remain disabled; - use
PassThroughFindPackageonly for an intentional host prerequisite whose installation and minimum version are documented; - let an unregistered lookup fail configure and then make the ownership decision explicitly.
Do not disable the interceptor or add a broad fallback. A dependency’s nested optional probes are part of its supply-chain and support surface.
Isolate Headers, Warnings, And Generated Files
Expose dependency headers through its target, preferably with
target_include_directories(... SYSTEM ...), instead of a repository-wide
include path. Keep first-party bridge headers non-system so project warnings
remain errors.
Compile definitions and generated headers belong on the narrow wrapper target
that needs them. Ensure generators run before consuming targets, emit into the
build tree, and participate in clean builds. Do not register a vendored source
tree with AddEngineSources: every registered file enters metadata/codegen
inspection, which is intended for project extension declarations, not arbitrary
third-party code.
If warning-clean upstream source is impractical, keep any warning adjustment on the third-party target. Do not lower warnings globally or suppress diagnostics in the project bridge.
Define The Platform Contract
For every optional or platform-specific dependency:
- expose a project feature option with a deterministic default;
- check platform, architecture, headers, import/static library, and runtime payload at configure time;
- define one availability macro from the final result;
- compile a no-dependency stub when a shared script/native symbol must remain;
- make unsupported runtime use explicit rather than returning false success;
- validate at least one enabled and one disabled build;
- keep the package matrix aligned with the compiled availability.
Separate three states: requested, compiled, and initialized at runtime. The presence of headers does not prove that the matching runtime library loads, credentials are provisioned, or the external service is reachable.
Package Runtime Payloads
Static source dependencies may add no runtime file, but they can still add license obligations. Shared/imported SDKs usually require a platform- and architecture-specific library beside the application. Tools may require helper executables or data packs.
Use the package declarations described in Packaging and Release to include each required file and notice. A development post-build copy helps local launching but is not a release package rule. Package acceptance must start the artifact from an isolated directory and exercise the feature far enough to detect a missing or wrong-architecture payload.
Record runtime file hashes in release provenance. Apply signing/notarization at the release-owned boundary and verify signatures after package assembly. Never place private SDK credentials, service tokens, signing keys, or license-server secrets in source, CMake cache defaults, generated metadata, examples, logs, or documentation.
Respect ABI, Allocation, And Lifetime
Build source dependencies with a compatible compiler, architecture, C/C++ runtime, exception, RTTI, and configuration policy. For a prebuilt SDK, use only the vendor-supported combination and fail configure for unsupported combinations.
Do not transfer ownership of Engine containers, strings, exceptions, or owning pointers across an undocumented shared-library ABI. Convert at the bridge, keep allocator ownership on the side that allocated the object, and expose a small SDK-native or C ABI where possible.
Inspect allocator hooks in the dependency implementation, not only its public declaration. If the library can use Engine allocation, wire a lifecycle-correct hook and test allocate/reallocate/free symmetry, aligned allocation, and shutdown order. Otherwise record that it uses a separate heap and keep its objects out of Engine ownership/statistics assumptions. Follow Essentials and Smart Pointers at the bridge.
Global SDK state must have explicit process-wide semantics. Per-client, per-server, or per-test-instance state belongs to an Engine/project instance; initialize and shut it down through the owning lifecycle path, including failed partial initialization.
Review License And Supply-Chain Risk
Before first use and every update:
- obtain the release from the official upstream or approved private source;
- verify the pinned commit/archive and stored hash;
- review release notes, supported toolchains, license changes, and security advisories;
- preserve licenses, notices, attribution, changelog, and source-offer material required by the distribution model;
- inventory local patches and removed files;
- scan package output for accidental source archives, credentials, debug-only helpers, and unapproved runtime files;
- record an emergency disable, downgrade, or removal route.
The Engine does not determine whether a dependency license is compatible with a game’s commercial or distribution model. That is a project release/legal gate, not a successful-build inference.
Update Workflow
- Record the old/new dependency identity and current Engine/project revisions.
- Stage the candidate outside the authored tree and verify provenance.
- Review license, advisories, API/ABI changes, build requirements, and platform support before replacing files.
- Reapply documented pruning and the smallest possible local patches. Mark project-local edits consistently in the project policy.
- Update the authoritative version record, integrity hash, notices, feature gates, package rules, compatibility-coupled assets/data, and owning docs.
- Reconfigure every affected platform lane so cached discovery cannot hide a missing prerequisite or stale target.
- Build every consuming Engine role and run focused bridge tests.
- Assemble an isolated package, verify runtime payload hashes/signatures, and exercise enabled and disabled behavior.
- Record failures, evidence, and the rollback pin. Keep the previous approved artifact available until acceptance is complete.
If an Engine update and dependency update happen together, audit them as two compatibility ranges. Do not attribute a passing final build to either change without a narrow check or bisectable evidence.
Validation Matrix
At minimum, capture:
| Boundary | Required proof |
|---|---|
| Interface | cmake -P BuildTools/tests/validate_project_interface.cmake and current generated CMake reference. |
| Configure | Clean configure for every affected platform/architecture and for required enabled/disabled feature states. |
| Compile/link | Every core/test list changed by the project; warning-clean first-party bridge and target-scoped dependency policy. |
| Codegen/script | Regenerated metadata plus resource bake when a native declaration or generated header changes. |
| Runtime | Focused success, unavailable, initialization-failure, and shutdown paths. |
| Package | Isolated launch, runtime payload presence/hash/architecture, notices, and secret-free artifact scan. |
| Upgrade | Old/new Engine pin comparison, dependency compatibility review, and no reused native/generated binary from the old pin. |
The Engine-owned minimal project compiles an INTERFACE project dependency by
appending it to FO_SERVER_LIBS; its server extension fails compilation if the
usage requirement is absent. This proves the current revision-pinned link path
without pretending it is a stable helper or making an external SDK part of the
fixture.
Failure Routing
| Symptom | Inspect first |
|---|---|
| Library does not reach its consumer | Selected FO_*_LIBS list, current CoreLibs.cmake, and stage order. |
| Header found locally but not in CI | Wrapper target include scope and accidental ambient include paths. |
Unregistered find_package() failure |
Nested dependency probe and the explicit handler decision. |
| Extension compiles but another role fails to link | Narrow role assignment, transitive target requirements, and actual core-library consumer graph. |
| Runtime library missing or wrong architecture | Package declaration, imported target location, post-build/package distinction, and artifact matrix. |
| Crash during allocation or shutdown | Allocator/free pairing, ABI ownership, global-state lifetime, and partial-init cleanup. |
| Feature says available but cannot initialize | Requested/compiled/runtime state separation and credential/service provisioning. |
| Update passes compile but assets fail | Version-coupled authored data, generator/runtime format, and real runtime validation. |
Source Paths Inspected
BuildTools/Init.cmakeBuildTools/cmake/ProjectInterface.jsonBuildTools/cmake/helpers/Build.cmakeBuildTools/cmake/helpers/State.cmakeBuildTools/cmake/stages/ThirdParty.cmakeBuildTools/cmake/stages/CoreLibs.cmakeBuildTools/cmake/stages/Packages.cmakeExamples/MinimalProject/CMakeLists.txtExamples/MinimalProject/StarterServerExtension.cpp
See Also
- Embedding Project - Engine/game repository ownership.
- Native Extensions - project C++ roles, hooks, metadata, state, and testing.
- ThirdParty Maintenance - Engine-owned vendored source and patch workflow.
- BuildTools Pipeline - public stages and selected helper boundary.
- Packaging and Release - package declarations, payloads, signing, and acceptance.
- Security and Secrets - credentials, redaction, trust, rotation, and incident handling.
- Engine Upgrade Guide - revision update and compatibility reconciliation.