Problem
GenerateBindings (tools/OloHeaderTool/CMakeLists.txt) is an add_custom_target. That means it declares no outputs and tracks no inputs, with three consequences:
- 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.
- 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.
- 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.
Problem
GenerateBindings(tools/OloHeaderTool/CMakeLists.txt) is anadd_custom_target. That means it declares no outputs and tracks no inputs, with three consequences:OloEngine/srcon every single build.OloEngineadd_dependencieson it, so the scan is on the critical path of every incremental build, not just the ones that changed a component.build/(msvc) andbuild-clang/(clangcl) trees concurrently, because "both runGenerateBindings, which writes the same generated.inlfiles 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.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— havemain.cppemit a depfile listing every header it opened. This is the only honest way to track a directory scan as a dependency; a configure-timefile(GLOB)goes stale on a new file, which is the failure mode you least want here.CODEGEN— registers the rule with CMake's built-incodegentarget, socmake --build build --target codegenregenerates the bindings without a full engine build. Useful for the "the generated.inllook stale" workflow CLAUDE.md describes.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_programfor the tool so a missing/failed generator fails fast with a clear message instead of producing an empty scan.Acceptance
build/does not re-run OloHeaderTool.cmake --build build --target codegenregenerates without building the engine.Notes
CODEGEN(3.31+) andDEPFILEare both available. The rootcmake_minimum_requiredis 3.25 and would need raising, or the feature guarding behind a version check.CODEGEN/DEPFILEsupport differs between Ninja and the Visual Studio generator, andbuild/uses VS 18 2026.Source: C++Now 2026 review — see the reflection-status counterpart in #688.
Score
Rated per issue-scoring · score = confidence × (capability + craft + stability + decay) / effort, derived by the picker.