Includes, unity builds, PCH, IWYU
A file can use a header it never included, because unity builds glue many .cpp files together and shared precompiled headers pre-include common stuff. When the grouping changes, the file loses the include it was borrowing and breaks. Prevent it with IWYU (include what you use) and a non-unity build on CI.
New to unity builds? Start with How Unreal builds your code; this page is the interview-question version.
The question
A .cpp uses FMath::Clamp without including its header and compiles fine. Two weeks later an unrelated
change (adding a new .cpp to the module) makes it fail. Why did it compile, why did it break, and how do you prevent it?
Three ways to see a header you didn't include
- Transitive includes. A header you include includes it.
- Precompiled headers.
PCHUsage = UseExplicitOrSharedPCHs(it's in WadOfGore's Build.cs too) lets a module compile against a shared PCH that already containsCoreMinimal.hand friends. - Unity builds. UBT concatenates many .cpp files into one generated file that
#includes them in turn. A header included by an earlier .cpp in the same group is visible to every later one. Your file borrows its neighbour's include.
Why an unrelated change breaks it
- UBT groups files by size and order. Adding or removing a file shifts the boundaries, so you lose the neighbour you were borrowing from.
- Adaptive unity: files you're actively editing (modified in git) get pulled out of the unity blob to compile faster. So "I touched this file and now it won't compile" is the classic symptom.
- Same mechanism: "builds on my machine, fails on CI".
Prevention
- IWYU: every file includes headers for what it uses directly. Epic's convention since UE 4.15;
IWYUSupport = IWYUSupport.Fullin Build.cs (5.2+). - A periodic/CI non-unity build:
-DisableUnity -NoPCH -NoSharedPCH, orbUseUnity = false. It surfaces every hidden dependency at once. - Forward-declare in headers, include in the .cpp. That means fewer transitive includes and faster builds (WadOfGore does this:
class USpringArmComponent;).
"Unity builds and shared PCHs hide missing includes. Unity grouping changes when files are added, or when adaptive unity pulls out a file you're editing, so the error shows up later. I follow IWYU and run a non-unity build on CI to catch it."
Build a project once with -DisableUnity -NoPCH -NoSharedPCH and fix whatever breaks. That gives you real
findings to talk about.
Quiz yourself
What is a unity build?
UBT combines many .cpp files into one generated file that includes them in turn, so the compiler parses shared headers once. That makes builds much faster.
What is adaptive unity, and why does it cause "I edited it and now it won't compile"?
Files you're working on get compiled on their own, outside the blob. They lose the headers they were borrowing from neighbours.
What does IWYU stand for, and what's the rule?
Include What You Use: every file includes the headers for the things it uses directly, instead of relying on something else to pull them in.
Which build flags surface every hidden include problem?
-DisableUnity -NoPCH -NoSharedPCH (or bUseUnity = false in Build.cs).
Why forward-declare in headers?
Fewer includes leak into everyone who includes your header. That gives faster compiles and fewer accidental dependencies.