Precompiled Headers in Cross Builds: Invalidate Them When Target Options Change
Treat a precompiled header as compiler state tied to a specific compilation context. Reuse requires compatible language, compiler binary, relevant macros and options. A successful compile does not prove the PCH was used, because GCC can reject it and parse the ordinary header instead.
This guide pins GCC 14.2.0 and CMake 3.31.12 documentation. Executed x86-64 host fixtures used G++ 14.2.0 to distinguish accepted PCH use from a macro-mismatch fallback. CMake reuse, another compiler binary and cross-target option changes were not executed.
Define the reuse key before sharing the artifact
The GCC 14.2 PCH guide requires the same language and compiler binary. A C PCH cannot simply be reused for C++. Macros defined before the PCH is included must agree when they affect its contents; unrelated macros need not invalidate it.
| Key category | Evidence to preserve |
|---|---|
| Compiler identity | Actual compiler binary and toolchain release, not only a command alias. |
| Language context | C or C++, selected language mode and relevant language options. |
| Header inputs | Wrapper, transitive headers, include order and resolved sysroot paths. |
| Preprocessor context | Definitions and undefinitions affecting the precompiled content. |
| Target and code generation | Architecture/ABI options, exception policy and other relevant compiler flags. |
| Debug configuration | Whether the PCH was generated with the debugging form needed by consumers. |
GCC generally requires matching -m options and explicitly requires matching -fexceptions. Its documentation recommends identical relevant option sets rather than assuming every optimization or code-generation difference is harmless. Use the target-specific documentation for any permitted relaxation.

Do not confuse artifact presence with artifact use
For an include of pch.hh, GCC searches for a corresponding pch.hh.gch before falling back to the ordinary header. If the precompiled form cannot be used, normal compilation may still succeed. This is useful behavior, but it can hide an incorrectly shared cache and invalidate a claimed build-time improvement.
Enable -Winvalid-pch during qualification and retain diagnostics. The fixture also used -H to expose header/PCH processing. A warning is evidence that the intended reuse did not happen; it is not proof that the fallback result is semantically wrong.
If a build requires verified reuse, make invalid-PCH diagnostics fatal in that qualification job. Keep that policy distinct from ordinary correctness testing, where parsing the original header may be an acceptable fallback.
Read the executed macro control
The owned fixture header defined a constant from FEATURE_LEVEL. The PCH producer command selected level one:
g++ -std=c++17 -DFEATURE_LEVEL=1 -x c++-header pch.hh -o pch.hh.gch
The consumer included pch.hh before other code and printed the selected value. A matching compile used -DFEATURE_LEVEL=1, -H and -Winvalid-pch. Its trace marked the PCH as used, and the resulting executable printed one.
A second compile changed only FEATURE_LEVEL to two. GCC reported that the PCH was not used, the trace marked it invalid and ordinary-header fallback produced an executable printing two. With -Werror=invalid-pch, the same mismatch failed compilation instead.
| Executed case | Compiler result | Program result |
|---|---|---|
| Matching macro | PCH accepted and used. | 1 |
| Relevant macro changed | PCH rejected; ordinary header parsed. | 2 |
| Mismatch with fatal diagnostic | Compilation rejected. | No program executed. |
This proves one relevant-macro boundary on one compiler binary and architecture. It does not prove that every target-flag mismatch produces the same warning or that a different toolchain’s PCH format is compatible.
Keep CMake reuse explicit
The CMake 3.31.12 command reference distinguishes creating a target’s PCH from reusing another target’s artifact. The proposed reuse form is:
target_precompile_headers(consumer REUSE_FROM producer)
This sets the reuse relationship and adds a dependency on the producer. It requires the same compiler options, flags and definitions for both targets. CMake also rejects this form if the consumer already has its own PRECOMPILE_HEADERS property set.
A dependency edge establishes ordering; it does not prove that two differently configured targets are compatible. Compare effective compile commands after generator expressions and transitive usage requirements have been applied. A target name or shared include directory is not an adequate reuse key.
CMake generates and force-includes a wrapper header for PCH use. Keep language-specific header selections explicit for mixed C/C++ targets. Avoid exporting PCH requirements casually as PUBLIC or INTERFACE usage requirements; downstream consumers should normally control their own acceleration strategy.
Invalidate deliberately in a cross-build matrix
Start with a matched producer/consumer pair for the intended target. Then change one relevant macro, language mode, compiler binary or target ABI option at a time in disposable build directories. Record whether the compiler accepts the PCH, rejects it with fallback or fails the build.
Rebuild the PCH under the changed context and require both intended reuse and the unchanged application test expectations. When the application contract intentionally changes, update that expectation through a separate review rather than treating a cache rebuild as permission to change behavior.
Do not alter host security settings merely to make PCH files byte-identical. This procedure validates compatibility and actual reuse, not deterministic PCH bytes. PCH hashing alone is not a complete correctness test, and two independently generated compatible PCH files need not be identical.
Release the evidence with the build rule
Retain the reuse-key ledger, producer and consumer commands, relevant header identities, accepted-use trace and mismatch control. Ensure header changes regenerate the PCH and dependent objects. Reusing an artifact from another SDK directory should require an explicit compatibility decision.
The useful result is a build rule that knows when reuse is permitted and makes fallback visible. Here the matched, fallback and fatal-mismatch host cases passed. Any cross-target reuse claim remains pending the corresponding target toolchain tests.