Reading CMake Errors by Phase: Compiler Detection, find_package, Missing Targets, Link Failures and Stale Caches
Key takeaways
CMake fails in one of three phases — configure, generate, build — and each phase has its own class of errors. This guide shows real messages from each phase, what they actually mean, and the fix.
CMake error output is long, and the first instinct is to scroll to the bottom and search for the last line. That usually lands on -- Configuring incomplete, errors occurred! or mingw32-make: *** [all] Error 2, neither of which says anything useful. The single most helpful thing I’ve learned about CMake errors is to ask first which phase failed, because each phase fails for different reasons:
- Configure — CMake runs your
CMakeLists.txt: detects compilers, runsfind_package, evaluates variables. Errors end withConfiguring incomplete, errors occurred!. - Generate — CMake turns targets into Makefiles, Ninja files or a Visual Studio solution. Errors here mention targets and source files and end with
CMake Generate step failed. - Build — the compiler and linker run. Errors come from
g++,cl.exeorld, not from CMake, and CMake can only be blamed for passing the wrong flags.
Every message below was reproduced with CMake 4.3 and MinGW g++ 10.3 unless noted, so the wording is real rather than paraphrased. For syntax-level problems (unbalanced parentheses, typos in command names, cmake_minimum_required too old), see 10 common CMake error messages; this article is about the errors that remain once the file parses.
Configure phase: the compiler
”No CMAKE_CXX_COMPILER could be found”
CMake Error at CMakeLists.txt:2 (project):
No CMAKE_CXX_COMPILER could be found.
Tell CMake where to find the compiler by setting either the environment
variable "CXX" or the CMake cache entry CMAKE_CXX_COMPILER to the full path
to the compiler, or to the compiler name if it is in the PATH.
The error points at project(), because that’s where languages are enabled. CMake looked on the PATH of the shell that launched it and found nothing matching the generator. The usual causes:
- Windows with Visual Studio generators or Ninja + MSVC:
cl.exeis only onPATHinside a Developer Command Prompt (or after runningvcvars64.bat). A plain PowerShell or a CI step without the VS environment gives exactly this error. The Visual Studio generators find MSVC themselves;-G Ninjadoes not. - MinGW / MSYS2: the
bindirectory isn’t onPATH, or you installed the MSYS2 package for one environment (UCRT64) and are running a shell for another. - Linux containers:
build-essential/gcc-c++isn’t installed in the image.
The fix is to make the compiler visible, then configure into a fresh build directory:
cmake -S . -B build -DCMAKE_CXX_COMPILER=/usr/bin/g++-13
# or
CXX=clang++ cmake -S . -B build-clang
The retry trap: “is not a full path to an existing compiler tool”
The failed detection is written into CMakeCache.txt. If you then point CMake at a different compiler in the same build directory, it has to throw the cache away first, and tells you so:
You have changed variables that require your cache to be deleted.
Configure will be re-run and you may have to reset some variables.
The following variables have changed:
CMAKE_CXX_COMPILER= C:/TDM-GCC-64/bin/clang++.exe
If the path is wrong (here, there is no clang++ in that directory), the next error is:
CMake Error at CMakeLists.txt:2 (project):
The CMAKE_CXX_COMPILER:
C:/TDM-GCC-64/bin/clang++.exe
is not a full path to an existing compiler tool.
I treat the compiler as a property of the build directory, not something to change inside it. One directory per toolchain (build-gcc, build-clang, build-msvc) avoids an entire category of “it worked yesterday” problems — and CMake presets make that pattern easy to keep.
”The C++ compiler does not support C++20” style problems
If you set CMAKE_CXX_STANDARD 20 with CMAKE_CXX_STANDARD_REQUIRED ON and the compiler is too old, CMake reports that the target requires a language dialect the compiler doesn’t support. Prefer expressing the requirement per target rather than hand-written version checks:
target_compile_features(app PRIVATE cxx_std_20)
Configure phase: find_package
”Could not find a package configuration file provided by …”
CMake Error at CMakeLists.txt:5 (find_package):
By not providing "Findfmt.cmake" in CMAKE_MODULE_PATH this project has
asked CMake to find a package configuration file provided by "fmt", but
CMake did not find one.
Could not find a package configuration file provided by "fmt" with any of
the following names:
fmt.cps
fmtConfig.cmake
fmt-config.cmake
Add the installation prefix of "fmt" to CMAKE_PREFIX_PATH or set "fmt_DIR"
to a directory containing one of the above files. If "fmt" provides a
separate development package or SDK, be sure it has been installed.
Read it literally. find_package(fmt) first looked for a Findfmt.cmake module (there isn’t one — CMake ships modules only for some libraries), then for a config file that fmt’s own installation provides. Neither was found. (The .cps names appear only in recent CMake versions; older ones list the two .cmake names.) The causes, in the order I check them:
- Only the runtime package is installed. On Debian/Ubuntu the config files come with
libfmt-dev, notlibfmt9. That’s what the “separate development package” sentence is about. - It’s installed in a non-default prefix (
/opt/...,$HOME/.local, a vcpkg or Conan tree). Pass-DCMAKE_PREFIX_PATH=/opt/fmt— the prefix, not thelib/cmake/fmtdirectory — or-Dfmt_DIR=/opt/fmt/lib/cmake/fmt. - vcpkg or Conan wasn’t wired in. vcpkg needs
-DCMAKE_TOOLCHAIN_FILE=<vcpkg>/scripts/buildsystems/vcpkg.cmakeon the first configure; Conan 2 generatesconan_toolchain.cmakethat must be passed the same way. - Case and name. The first argument must match the config file name:
find_package(OpenCV)findsOpenCVConfig.cmake;find_package(opencv)looks foropencvConfig.cmake/opencv-config.cmake.
The legacy variant — Could NOT find Boost (missing: Boost_INCLUDE_DIR) — comes from a Find module rather than a config file. For Boost specifically, CMake 3.30 removed FindBoost.cmake (policy CMP0167); the details are in Boost in modern C++. For deeper find_package debugging, see CMake “Could NOT find”.
When the search itself is the mystery, ask CMake to show it:
cmake -S . -B build --debug-find-pkg=fmt # CMake 3.23+: every path tried for fmt
Generate phase: targets and sources
”Cannot find source file”
CMake Error at CMakeLists.txt:4 (add_executable):
Cannot find source file:
src/util.cpp
CMake Error at CMakeLists.txt:4 (add_executable):
No SOURCES given to target: app
CMake Generate step failed. Build files cannot be regenerated correctly.
Notice this happens at generate, after configure reported success. The file list is checked when build files are written. Relative source paths are resolved against the directory of the CMakeLists.txt that contains the command (CMAKE_CURRENT_SOURCE_DIR), not the top-level project — a frequent surprise after moving an add_library call into a subdirectory. The second error is just a consequence of the first. Case matters too: Util.cpp on a Windows checkout works and breaks on Linux CI.
If you build file lists with file(GLOB ...), a newly added file isn’t seen until CMake reconfigures, and a deleted file produces this error until then. CONFIGURE_DEPENDS makes the glob re-check on every build, at some cost; listing files explicitly avoids the question.
”Target … links to … but the target was not found”
CMake Error at CMakeLists.txt:4 (target_link_libraries):
Target "app" links to:
fmt::fmt
but the target was not found. Possible reasons include:
* There is a typo in the target name.
* A find_package call is missing for an IMPORTED target.
* An ALIAS target is missing.
Names containing :: are always treated as targets, so CMake can report this at generate time. The usual cause is find_package missing, placed in a different directory scope, or placed after it’s needed in a subdirectory that was already processed. Imported targets are directory-scoped unless created GLOBAL, so a find_package inside src/CMakeLists.txt is not visible to a sibling tests/CMakeLists.txt.
Plain names are not checked. target_link_libraries(app PRIVATE zlibx) with no target called zlibx sails through configure and generate and fails at link time:
ld.exe: cannot find -lzlibx
That’s the argument for always linking namespaced targets (ZLIB::ZLIB, fmt::fmt) instead of bare library names: typos become CMake errors with a line number instead of linker errors.
”Cannot specify link libraries for target … which is not built by this project”
CMake Error at CMakeLists.txt:5 (target_link_libraries):
Cannot specify link libraries for target "helper" which is not built by
this project.
The first argument of target_link_libraries must be a target that already exists. Either it’s misspelled, or the add_library/add_executable for it comes later in the file or in a subdirectory added later.
Build phase: usage requirements and the link line
Most “CMake” build failures are really usage-requirement mistakes: a target is missing something that another target should have propagated.
Header not found in the consumer: a PRIVATE that should be PUBLIC
# mylib/CMakeLists.txt
add_library(mylib src/api.cpp)
target_include_directories(mylib PRIVATE include) # wrong for a public header
# CMakeLists.txt
target_link_libraries(app PRIVATE mylib)
mylib builds fine — its own sources see include/. app fails:
src\main.cpp:1:10: fatal error: mylib/api.h: No such file or directory
PRIVATE means “for building this target only.” Because app includes mylib/api.h, the include directory is part of mylib’s interface and must be PUBLIC. The three keywords answer one question — who needs this?
| Keyword | Used when building the target | Propagated to consumers | Typical use |
|---|---|---|---|
PRIVATE | Yes | No | Dependencies used only in .cpp files |
PUBLIC | Yes | Yes | Dependencies that appear in the target’s public headers |
INTERFACE | No | Yes | Header-only libraries; flags only consumers need |
The opposite mistake is quieter: marking everything PUBLIC “to be safe” makes every downstream target inherit include paths, definitions and link dependencies it doesn’t need, which slows builds and occasionally causes macro or header collisions far away from the cause.
Undefined reference: headers without the library
A common “fix” for the error above is to add the include path manually to app:
target_include_directories(app PRIVATE mylib/include) # compiles, but...
Now compilation passes and linking fails:
ld.exe: CMakeFiles\app.dir/objects.a(main.cpp.obj):main.cpp:(.text+0xe): undefined reference to `mylib::answer()'
collect2.exe: error: ld returned 1 exit status
Include directories make declarations visible; only target_link_libraries puts the library on the link line. I’ve seen this pattern spread through a codebase because each manual include “fixed” one compile error — and then every new executable needed both the include hack and a separate link fix. The real fix is one line in the library (PUBLIC include) and one target_link_libraries(app PRIVATE mylib) per consumer; everything else flows from there.
Other undefined-reference causes to rule out, in rough order of frequency:
- The
.cppwith the definition isn’t in any target’s source list. - C code linked from C++ without
extern "C"— the C++ side looks for a mangled name. - Static library order. With bare
-lflags and GNU ld, a library must come after the objects that use it. CMake orders targets correctly when dependencies are declared on the targets themselves (target_link_libraries(mylib PUBLIC dep)), which is another reason to avoid hand-written link lines. - ABI mismatches: mixing Debug and Release runtimes (
/MDdvs/MD) on MSVC, or libraries built against a different_GLIBCXX_USE_CXX11_ABIsetting on Linux — the symbol exists, but with a different mangled name.
CMake link errors goes deeper on LNK2019 and ODR-related failures.
Runtime: the program builds but won’t start
./app: error while loading shared libraries: libmylib.so.1: cannot open shared object file: No such file or directory
This is the dynamic loader, not CMake. Executables in the build tree get an RPATH pointing at the build tree, so they run there; after cmake --install the installed copy has no RPATH by default. Set one relative to the executable:
set_target_properties(app PROPERTIES INSTALL_RPATH "$ORIGIN/../lib") # Linux
On Windows there is no RPATH; the DLL has to be next to the .exe or on PATH. Since CMake 3.21, $<TARGET_RUNTIME_DLLS:app> lists the DLLs of linked imported targets so a post-build step can copy them:
add_custom_command(TARGET app POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_RUNTIME_DLLS:app> $<TARGET_FILE_DIR:app>
COMMAND_EXPAND_LISTS)
The cache: when CMake remembers too much
CMakeCache.txt makes reconfiguration fast by remembering every detected compiler, every find_* result and every option(). It also means CMake keeps using answers you thought you had changed.
Generator mismatch
CMake Error: Error: generator : Unix Makefiles
Does not match the generator used previously: MinGW Makefiles
Either remove the CMakeCache.txt file and CMakeFiles directory or choose a different binary directory.
A build directory belongs to one generator forever. IDEs trigger this constantly: VS Code’s CMake Tools configures with Ninja, then someone runs cmake -G "Visual Studio 17 2022" in the same directory from a terminal. Use a separate directory, or cmake --fresh (CMake 3.24+), which discards the cache and reconfigures from scratch.
Copied or moved build directories
CMake Error: The current CMakeCache.txt directory .../p2copy/build/CMakeCache.txt is different than the directory .../p2/build where CMakeCache.txt was created. This may result in binaries being created in the wrong place.
Build directories contain absolute paths and can’t be moved or copied, including by caching them in CI between runners with different workspace paths. Cache the dependencies (vcpkg’s binary cache, ccache), not the CMake build directory.
Found the wrong thing, and keeps finding it
find_library, find_path and find_package store what they found. If the first configure picked up /usr/lib/libssl.so (the system OpenSSL) and you later add -DCMAKE_PREFIX_PATH=/opt/openssl-3, nothing changes — the cached result is already set, so CMake doesn’t search again. Only results that were -NOTFOUND are retried. The same goes for option(ENABLE_X "..." OFF): changing the default in CMakeLists.txt doesn’t affect an existing build directory, because the cached value wins.
The fixes, from narrowest to broadest:
cmake -S . -B build -U 'OPENSSL_*' -DOPENSSL_ROOT_DIR=/opt/openssl-3 # drop matching entries, search again
cmake -S . -B build -Dfmt_DIR=/opt/fmt/lib/cmake/fmt # overwrite one entry directly
cmake --fresh -S . -B build # rebuild the whole cache (3.24+)
Editing CMakeLists.txt never requires deleting the cache — CMake re-runs automatically when it changes. Deleting build directories by reflex hides the real problem and costs a full rebuild each time.
Debugging tools worth knowing
cmake -S . -B build --log-level=DEBUG # show message(DEBUG ...) output
cmake -S . -B build --debug-find # trace every find_* search (3.17+)
cmake -S . -B build --trace-expand --trace-source=CMakeLists.txt # each command with variables expanded
cmake --build build --verbose # the real compiler and linker command lines
cmake --build build --config Release # multi-config generators ignore CMAKE_BUILD_TYPE
Two notes from experience. First, --verbose is the answer to most build-phase mysteries: once you see the actual g++ ... -o app line, a missing -I or a library in the wrong position is obvious. Second, with Visual Studio and Ninja Multi-Config generators, -DCMAKE_BUILD_TYPE=Release at configure time does nothing — the configuration is chosen at build time with --config. A surprising number of “Release builds are slow” reports are Debug builds.
To see what a target actually carries after propagation, print its properties from CMake:
get_target_property(inc app INCLUDE_DIRECTORIES)
get_target_property(libs app LINK_LIBRARIES)
message(STATUS "app includes: ${inc}")
message(STATUS "app links: ${libs}")
INCLUDE_DIRECTORIES here shows only what was set directly on app; include paths inherited from linked targets are added at generate time, which is why --verbose is the more reliable check.
Where it failed and what to check first
| Where it failed | Typical message | First thing to check |
|---|---|---|
| Configure | No CMAKE_CXX_COMPILER could be found | The shell’s PATH; Developer Prompt on Windows; fresh build dir |
| Configure | Could not find a package configuration file provided by "X" | -dev package installed? CMAKE_PREFIX_PATH / X_DIR; toolchain file |
| Generate | Cannot find source file | Path relative to the current CMakeLists.txt; case |
| Generate | Target "app" links to: X::X but the target was not found | Missing or out-of-scope find_package |
| Build | fatal error: foo.h: No such file or directory | PRIVATE include that should be PUBLIC |
| Build | undefined reference to ... | Missing target_link_libraries; source not in target; ABI |
| Any | Does not match the generator used previously | Separate build dir or cmake --fresh |