Skip to content

Build: build-graph-integrate the OloHeaderTool codegen (add_custom_command + DEPFILE + CODEGEN) #758

Description

@drsnuggles8

Problem

GenerateBindings (tools/OloHeaderTool/CMakeLists.txt) is an add_custom_target. That means it declares no outputs and tracks no inputs, with three consequences:

  1. It re-runs the full recursive scan of OloEngine/src on every single build. OloEngine add_dependencies on it, so the scan is on the critical path of every incremental build, not just the ones that changed a component.
  2. It is the mechanical cause of the documented cross-tree hazard. CLAUDE.md already warns never to build the build/ (msvc) and build-clang/ (clangcl) trees concurrently, because "both run GenerateBindings, which writes the same generated .inl files into the shared source tree" — file-lock custom-build failures, half-written generated code, LNK2038. An untracked target that writes into the source tree cannot be made safe by ordering alone; the build graph has to know about the outputs.
  3. A failed or empty scan surfaces as a confusing test failure, not a build error. The coverage tests (ComponentTupleCoverageTest, ComponentSerializerCoverageTest, SaveGameComponentSerializerCoverageTest, McpFieldRegistryTest) parse the generated files as text — so if the tool never ran, or scanned nothing, you get a parse-count assertion failure rather than "the codegen tool is missing".

Proposal

Convert to add_custom_command(OUTPUT ...) over the generated files, following the pattern in Bret Brown's Until Reflection: Pragmatic Code Generation with CMake (C++Now 2026), which is a checklist for exactly this shape of tool:

  • OUTPUT — list the generated artefacts (Scene/Generated/*.inl, SaveGame/Generated/*.inl, Scripting/C#/Generated/*, OloEditor/src/MCP/Generated/*.inl, the ScriptCore .cs) so the build graph knows what this rule produces and can skip it when nothing changed.
  • DEPFILE — have main.cpp emit a depfile listing every header it opened. This is the only honest way to track a directory scan as a dependency; a configure-time file(GLOB) goes stale on a new file, which is the failure mode you least want here.
  • CODEGEN — registers the rule with CMake's built-in codegen target, so cmake --build build --target codegen regenerates the bindings without a full engine build. Useful for the "the generated .inl look stale" workflow CLAUDE.md describes.
  • Keep VERBATIM (already present).

Optional follow-on, worth deciding explicitly rather than by default: the talk argues commit inputs, not outputs. This repo commits the generated files, which is defensible today because it keeps clangd/IDE navigation working without a configure step — but that justification is largely a consequence of the generation not being build-graph integrated. Once it is, re-evaluate whether the generated tree still needs to be tracked.

Also worth adopting from the same talk: find_program for the tool so a missing/failed generator fails fast with a clear message instead of producing an empty scan.

Acceptance

  • A no-op incremental build of build/ does not re-run OloHeaderTool.
  • Touching one component header does re-run it (proves the depfile works).
  • cmake --build build --target codegen regenerates without building the engine.
  • Both trees can be configured without the generated-file collision being a lock failure (it becomes an ordinary stale-output question).
  • Coverage tests still pass; regenerated files re-staged per the existing convention.

Notes

  • CMake 4.2 is the local toolchain, so CODEGEN (3.31+) and DEPFILE are both available. The root cmake_minimum_required is 3.25 and would need raising, or the feature guarding behind a version check.
  • Verify under both generators — CODEGEN/DEPFILE support differs between Ninja and the Visual Studio generator, and build/ uses VS 18 2026.

Source: C++Now 2026 review — see the reflection-status counterpart in #688.

Score

capability: 3
craft: 1
stability: 5
decay: 3
effort: 3
confidence: 0.8
learning: 3
fun: 3
kano: table-stakes
blocked_by: []
blocks: []

Rated per issue-scoring · score = confidence × (capability + craft + stability + decay) / effort, derived by the picker.

Metadata

Metadata

Assignees

No one assigned

    Labels

    architectureEngine architecture / cross-cutting designcleanupDead code, smells, config hygiene, non-feature maintenancefeatureNew feature or requestrobustnessProduction hardening / shipping robustnesstoolingMCP / dev-tooling / codegen — exempt from feature freeze

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions