Build Workflow
This document explains how to approach FOnline builds without hard-coding assumptions from one project into another.
Source paths inspected
../CMakeLists.txt../BuildTools/README.md../BuildTools/Init.cmake../BuildTools/validate.sh../BuildTools/validate.cmd../BuildTools/buildtools.py../BuildTools/cmake/stages/Init.cmake../BuildTools/cmake/stages/ProjectOptions.cmake../BuildTools/cmake/stages/EngineSources.cmake../BuildTools/cmake/stages/Codegen.cmake../BuildTools/cmake/stages/ScriptsAndBaking.cmake../BuildTools/cmake/stages/Applications.cmake../BuildTools/cmake/stages/Packages.cmake../BuildTools/cmake/stages/Finalize.cmake../BuildTools/cmake/helpers/*.cmake../Source/Applications/TestingApp.cpp../Source/Tests/README.md
Use the embedding project as the build root
FOnline is normally built through a game repository that embeds the engine as Engine/. Configure and build from the game root unless a focused engine-only command explicitly says otherwise.
Reasons:
- Target names are project-defined.
.fomaincontrols game-specific configuration.- Generated scripting APIs are project-dependent.
- Package names, signing, resources, and deployment settings belong to the product.
- Platform presets usually live in the embedding project’s
CMakePresets.json.
Typical workflow
- Open the game repository root.
- Inspect available presets with CMake or the IDE integration used by the project.
- Configure the smallest preset that covers your change.
- Build the narrowest relevant target.
- Run the corresponding test, package, or launch target.
- Update documentation if the workflow or behavior changed.
Prerequisites
The exact list depends on host OS and target platform, but common tools include:
- Git
- CMake
- Python 3
- A C++20-capable compiler/toolchain
- Platform SDKs for the targets you build
- Visual Studio or Build Tools on Windows-oriented workflows
- Emscripten and Node.js for Web builds
- JDK and Android NDK for Android builds
Prefer the embedding project’s documented setup because it may pin specific SDK/tool versions.
Linux managed builds compile position-independent objects and link executable targets as PIE.
On x64, Mono’s JIT requires low-address executable mappings; a large native brk heap in a
non-PIE host can occupy that address range before the first managed call and make a small JIT
allocation fail with ENOMEM despite available memory. Shared libraries retain PIC objects,
including in MemorySanitizer configurations; the executable-only -pie option must not be
applied to shared or module targets.
Fetching through a mirror of your own
prepare-workspace downloads the toolset, the Android SDK/NDK, the MSVC SDK and the LLVM sources from
whoever publishes them. Each of those is a machine you do not run, and a dropped connection costs the
job that is waiting on it. An embedding project may put a host of its own in front of them; the engine
only needs to be told where it is, so nothing about that host is compiled in and everything travels in
the environment:
| variable | what it configures |
|---|---|
FO_DOWNLOAD_MIRROR |
Base URL of a pull-through mirror. https://host/path is fetched as <mirror>/host/path instead. |
FO_WORKSPACE_CACHE |
Base URL for prepared workspaces. The Emscripten SDK is keyed by version, host OS, and architecture; the MSVC SDK tree is keyed by xwin version and contained architectures; the published managed runtime tree (setup-mono) is keyed by everything that shapes it, see BuildToolsPipeline.md. Each complete tree is built once and downloaded whole afterwards. |
FO_CI_TOKEN |
Bearer token for the two addresses above. It is sent only to their own scheme and host, never to an upstream one. |
FO_CI_CA |
Extra trust anchors, added to the system store rather than replacing it, for a machine whose root store cannot be repaired. |
Unset, every one of them leaves the download path exactly as it was.
Two behaviours are deliberate. A download is checked against the upstream Content-Length, because a
dropped connection ends the read instead of raising and an archive cut in half unpacks into a failure
far from its cause. And a workspace cache that is empty, unreachable or refusing is only a miss:
it exists to make the build faster and independent of other people’s servers, not to become another
way for it to fail.
emsdk and xwin fetch their own packages, so mirroring the engine’s direct downloads does not cover
them. Their complete prepared results are therefore what the workspace cache holds. A corrupt or incomplete
Emscripten cache object is discarded and rebuilt locally; cache creation and upload remain best-effort. Cache
fills use gzip’s fast level because they run on the producing job’s critical path; the modestly larger object is
amortized by every later restore and does not change the tar.gz format or cache identity.
Cached trees are extracted through the standard data-only tar filter after a path-boundary check. Extraction
lands in a temporary sibling first, and only the named complete SDK directory is promoted, so an archive
cannot overwrite another prepared workspace tree. The existing xwin cache follows the same restore rule.
Where build logic lives
- ../BuildTools/README.md — BuildTools overview.
- BuildToolsPipeline.md — staged CMake pipeline and change routing.
../BuildTools/cmake/— reusable CMake modules and staged generation/build/package logic.../CMakeLists.txt— engine-level CMake entry points.- Embedding project root — product-level presets, configuration, and target selection.
Validation by change type
- Runtime C++: build and run the project unit-test target; use Testing.md to choose focused suites and understand generated test targets.
- CMake/BuildTools: reconfigure from a clean or relevant build directory and run the affected build/package target; use BuildToolsPipeline.md for stage ownership.
- Generated API: rebuild generation targets, verify scripts compile, and consult GeneratedApiAndMetadata.md.
- Resource baking: run the relevant normal/forced bake path and consult BakingPipeline.md.
- Updater: follow ClientUpdater.md.
- Web: follow WebDebugging.md.
- Android: follow AndroidDebugging.md.
- Mapper/tooling: follow Tools.md and MapperTools.md.
- Nullability/script boundary: follow Scripting.md, ScriptMethodsMap.md, and Nullability.md.
- Configuration/resources: follow ConfigurationAndDataSources.md and BakingPipeline.md.
- Essentials/low-level utilities: follow Essentials.md and run the matching essentials tests from Testing.md.
Keep build docs maintainable
Do not copy a full preset list into engine docs. Presets change per game and per branch. Instead, explain ownership and link to the concrete project document that owns exact commands.
Validation checklist
- Confirm the command or preset belongs to the embedding project before documenting exact names in engine docs.
- For BuildTools changes, reconfigure the smallest affected preset and run the generated target that exercises the changed stage.
- For runtime changes, run focused tests first and then the project
RunUnitTeststarget when practical. - For package/platform changes, validate the owning package/debug doc in the same change.
- Update BuildToolsPipeline.md, Testing.md, or platform docs when the build workflow itself changes.