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:

  1. Configure — CMake runs your CMakeLists.txt: detects compilers, runs find_package, evaluates variables. Errors end with Configuring incomplete, errors occurred!.
  2. 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.
  3. Build — the compiler and linker run. Errors come from g++, cl.exe or ld, 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.exe is only on PATH inside a Developer Command Prompt (or after running vcvars64.bat). A plain PowerShell or a CI step without the VS environment gives exactly this error. The Visual Studio generators find MSVC themselves; -G Ninja does not.
  • MinGW / MSYS2: the bin directory isn’t on PATH, 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:

  1. Only the runtime package is installed. On Debian/Ubuntu the config files come with libfmt-dev, not libfmt9. That’s what the “separate development package” sentence is about.
  2. 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 the lib/cmake/fmt directory — or -Dfmt_DIR=/opt/fmt/lib/cmake/fmt.
  3. vcpkg or Conan wasn’t wired in. vcpkg needs -DCMAKE_TOOLCHAIN_FILE=<vcpkg>/scripts/buildsystems/vcpkg.cmake on the first configure; Conan 2 generates conan_toolchain.cmake that must be passed the same way.
  4. Case and name. The first argument must match the config file name: find_package(OpenCV) finds OpenCVConfig.cmake; find_package(opencv) looks for opencvConfig.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.

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.

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.

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?

KeywordUsed when building the targetPropagated to consumersTypical use
PRIVATEYesNoDependencies used only in .cpp files
PUBLICYesYesDependencies that appear in the target’s public headers
INTERFACENoYesHeader-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 .cpp with 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 -l flags 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 (/MDd vs /MD) on MSVC, or libraries built against a different _GLIBCXX_USE_CXX11_ABI setting 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 failedTypical messageFirst thing to check
ConfigureNo CMAKE_CXX_COMPILER could be foundThe shell’s PATH; Developer Prompt on Windows; fresh build dir
ConfigureCould not find a package configuration file provided by "X"-dev package installed? CMAKE_PREFIX_PATH / X_DIR; toolchain file
GenerateCannot find source filePath relative to the current CMakeLists.txt; case
GenerateTarget "app" links to: X::X but the target was not foundMissing or out-of-scope find_package
Buildfatal error: foo.h: No such file or directoryPRIVATE include that should be PUBLIC
Buildundefined reference to ...Missing target_link_libraries; source not in target; ABI
AnyDoes not match the generator used previouslySeparate build dir or cmake --fresh