Lesson 27 · Quality & debugging
Debugging CMake
When configure fails or a target links wrong. message(), --trace, cmake_diagnostic(), and verbose builds. A practical troubleshooting workflow.
CMake fails. Everyone’s CMake fails. The difference between frustrated and fast is knowing the three tools in this lesson. Printing, tracing, and seeing the actual commands.
1. Printing, message()
message() is your print statement. Levels matter. They are the difference between hidden info and a build-stopping error:
message(STATUS "Building calc with backend: ${CALC_BACKEND}") # normal info
message(WARNING "Deprecated option used") # warning
message(FATAL_ERROR "GLFW not found. Install it or set CMAKE_PREFIX_PATH") # stops configure
Tip
Use
message(STATUS ...)for “what is happening” and reserveFATAL_ERRORfor genuine show-stoppers with a hint about the fix, like the example above.message(FATAL_ERROR "foo not found")without a hint is how beginners produce forum posts.
Note
CMake 4.4 added a dedicated
cmake_diagnostic()command for diagnostics, warnings and errors with full control. It is the replacement for the deprecatedCMAKE_WARN_DEPRECATEDandCMAKE_ERROR_DEPRECATEDvariables, see lesson 30. For everyday debugging,message()is still the tool.
2. Tracing, —trace
When you need to know what ran, in what order, where:
cmake --trace -S . -B build # every command, every line
cmake --trace-expand -S . -B build # with variables expanded
cmake --trace-source=CMakeLists.txt -S . -B build # just one file
Perfect for “why is this set() being overridden?”. You see every call site, in order.
3. Seeing the actual commands
The configure succeeded, but the compile is wrong. See the real command lines:
cmake --build build -v # verbose. Print every command (Ninja and Make)
# older make-only spelling. make VERBOSE=1
ninja -C build -t commands app # print the exact commands for target "app"
This is how you check include dirs (-I...), definitions (-D...), and flags. The ground truth, not what you think CMake generated.
A working troubleshooting flow
- Reproduce cleanly.
rm -rf build && cmake -S . -B build. Half of “CMake bugs” are stale-cache ghosts (lesson 16). - Read the error. CMake’s errors say exactly what it looked for and where. Read the last ten lines before anything else.
- Add
message(STATUS ...)probes around the suspicious block. Print your variables, printCMAKE_CXX_COMPILER, print the paths. - Trace if needed.
cmake --trace-expandshows the exact order of execution. - For link and compile problems. Verbose build, then read the flags,
-I,-L,-l. Ninety percent of the time the answer is “the include dir you think is there is not” (lesson 9) or “the target you linked is not the one you think” (lesson 10). - For policy warnings. Lesson 6. Bump
cmake_minimum_requiredor set the policy.
The rename you will see in new CMake, 4.4
Three debugging flags got new spellings in CMake 4.4. The old spellings still work but are deprecated, so you will see the new names in docs and the old ones in blog posts:
| Old (deprecated) | New (4.4) |
|---|---|
-Wdev / -Wno-dev | -Wauthor / -Wno-author |
--warn-uninitialized | -Wuninitialized |
--no-warn-unused-cli | -Wno-unused-cli |
Pitfall
“It worked before I changed nothing”. You almost always did change something. A cache variable persisted (lesson 16), or the build directory mixed two generators (lesson 21). The first fix to try is always a fresh
build/directory. It is cheap, safe, and eliminates the biggest class of confusion.
Tip
Build these habits early. Never debug a stale build directory. Always read the full error. Use
-vbuilds to verify rather than guess. Those three habits solve more CMake problems than any flag.
Read the docs