CMake Errors: 10 Common CMake Error Messages and How to Fix
Key takeaways
How to read a CMake error (file, line, command), then the ten configure errors people hit most: version too old, targets used before they exist, parse errors, find_package failures, bad source paths, silent empty variables, duplicate targets and in-source builds.
Modern CMake (English): overview · find_package · targets. find_package pain: Could NOT find ….
Introduction: reading a CMake error
CMake error messages look cryptic mostly because they mix two things: where the error was detected and what went wrong. Almost every configure-time error starts with the same prefix:
CMake Error at CMakeLists.txt:12 (target_link_libraries):
Cannot specify link libraries for target "myapp" which is not built by
this project.
CMakeLists.txt:12 is the file and line, and the name in parentheses is the command that raised the error. That prefix alone does not mean “syntax error”; it is attached to nearly every configure error. The indented text below it is the actual message. When the error comes from inside an included module or a package’s config file, the first line points into that file and a “Call Stack (most recent call first)” section follows, whose last entry is the line in your own CMakeLists.txt. Read that entry first.
It also helps to know which phase failed. CMake runs in two steps: configure (running your CMakeLists.txt, where all the errors below happen) and generate (writing Makefiles or Ninja files). Compiler and linker errors come later, from cmake --build, and CMake did not produce them. A message like undefined reference to ... or cannot find -lmylib is a build error, even though its root cause is often in CMakeLists.txt.
Ten common CMake errors
Error 1: CMake version too old
CMake Error at CMakeLists.txt:1 (cmake_minimum_required):
CMake 3.20 or higher is required. You are running version 3.16.3
Fix 1: Lower cmake_minimum_required, but only if the project really does not use newer features:
# High requirement
cmake_minimum_required(VERSION 3.20)
# Match your installed CMake
cmake_minimum_required(VERSION 3.16)
cmake_minimum_required does two things: it rejects older CMake versions, and it sets policy defaults to that version’s behavior. Lowering the number is therefore not purely cosmetic. If the project uses presets, target_sources(FILE_SET ...) or other newer commands, lowering the requirement just moves the failure to a later, less clear “Unknown CMake command” or “unknown argument” error. Check the project’s documentation or CI configuration for the version it is actually tested with.
Fix 2: Upgrade CMake
# Ubuntu (the distro package is often several versions behind)
sudo apt install cmake
# Or install a newer release
wget https://github.com/Kitware/CMake/releases/download/v3.28.0/cmake-3.28.0-linux-x86_64.sh
sudo sh cmake-3.28.0-linux-x86_64.sh --prefix=/usr/local --skip-license
pip install cmake is another quick way to get a current CMake without root, and Kitware also runs an APT repository for Ubuntu. After installing, check which cmake and cmake --version: an old /usr/bin/cmake earlier on PATH is a common reason the “upgrade” appears not to work.
The opposite problem is also common on recent CMake: a warning that compatibility with very old CMake versions will be removed, which became a hard error in CMake 4.0 for projects declaring a minimum below 3.5. It comes from an ancient cmake_minimum_required(VERSION 2.8) in your project or in a vendored dependency. Raise the version in your own files; for a third-party subproject you cannot edit, -DCMAKE_POLICY_VERSION_MINIMUM=3.5 is the documented escape hatch.
Error 2: Target not found
# Problem: myapp does not exist yet when target_link_libraries runs
target_link_libraries(myapp mylib)
add_executable(myapp main.cpp)
# CMake Error at CMakeLists.txt:2 (target_link_libraries):
# Cannot specify link libraries for target "myapp" which is not built by
# this project.
Fix: Define targets in the right order.
# Correct order
add_library(mylib mylib.cpp)
add_executable(myapp main.cpp)
target_link_libraries(myapp mylib) # mylib already exists
Note which name the error mentions. CMake only requires the first argument, the target being modified, to exist at that point. The libraries you link to are resolved at generate time, so mylib may be defined later in the file or in another subdirectory. If mylib is never defined anywhere, CMake does not complain at all: it assumes mylib is a system library and passes -lmylib to the linker. The failure then shows up during the build as /usr/bin/ld: cannot find -lmylib, which sends people looking for a missing package when the real cause is a typo in a target name.
This is one of the strongest arguments for namespaced targets like fmt::fmt or MyProject::core. A name containing :: is always treated as a target, so a misspelling fails at generate time with an error saying the target links to a target that “was not found”, instead of becoming a mystery linker flag.
Error 3: Syntax error (unbalanced parentheses)
# Problem: missing closing parenthesis
add_executable(myapp
main.cpp
utils.cpp
# CMake Error in CMakeLists.txt:
# Parse error. Function missing ending ")". End of file reached.
Fix:
add_executable(myapp
main.cpp
utils.cpp
)
The parser only notices the missing parenthesis when it reaches the end of the file, so the error does not point at the line where the ) should be. In a long file, look for the last command that opens a parenthesis and never closes it; an editor with CMake syntax highlighting makes this obvious. Unbalanced quotes produce a similar error ending in “Unterminated quoted argument”.
Error 4: Could NOT find package
find_package(Boost REQUIRED)
# CMake Error at /usr/share/cmake-3.22/Modules/FindPackageHandleStandardArgs.cmake:230 (message):
# Could NOT find Boost (missing: Boost_INCLUDE_DIR)
Fix: See CMake “Could NOT find” errors.
set(CMAKE_PREFIX_PATH "/usr/local" ${CMAKE_PREFIX_PATH})
find_package(Boost REQUIRED)
# Or use vcpkg
The file in the first line is CMake’s own helper module, which confuses people into thinking CMake itself is broken. The part that matters is in parentheses: missing: Boost_INCLUDE_DIR names the variable the search could not fill in. For Boost specifically, recent versions ship their own BoostConfig.cmake, and CMake 3.30 deprecated the old FindBoost module (policy CMP0167), so on a new toolchain find_package(Boost CONFIG REQUIRED) with CMAKE_PREFIX_PATH pointing at the Boost install is usually the cleaner route. Section 3 covers the two search modes.
Error 5: Wrong file path
add_executable(myapp
main.cpp
utils.cpp # file missing
)
# CMake Error at CMakeLists.txt:1 (add_executable):
# Cannot find source file:
#
# utils.cpp
Fix: Verify paths. Relative source paths are resolved against the directory of the CMakeLists.txt that contains the command (CMAKE_CURRENT_SOURCE_DIR), not against the directory you ran cmake from and not against the top-level project. Moving a file into src/ without updating the list is the usual cause, and so is case: Utils.cpp versus utils.cpp works on Windows and macOS and fails on Linux.
ls utils.cpp
Some projects switch to globbing to avoid maintaining the list:
# Use with care: new files are not picked up until CMake re-runs
file(GLOB SOURCES CONFIGURE_DEPENDS "src/*.cpp")
add_executable(myapp ${SOURCES})
The CMake documentation recommends against GLOB for source lists because the glob is evaluated at configure time: a newly added .cpp is not compiled until someone re-runs CMake. CONFIGURE_DEPENDS makes the build re-check the glob on every build, which fixes that at a small cost, but the documentation notes it may not work reliably with every generator. Explicit lists are more work and also make code review show exactly which files entered the build.
Error 6: Undefined variable
target_include_directories(myapp PRIVATE ${MY_INCLUDE_DIR})
# No error and no warning: ${MY_INCLUDE_DIR} silently expands to nothing
This one is dangerous precisely because it is quiet. CMake treats an unset variable as an empty string, so the line above adds no include directory and the first sign of trouble is a compiler error like fatal error: mylib.h: No such file or directory much later. A typo such as ${MY_INCLUDE_DRI} behaves the same way. Run cmake --warn-uninitialized -S . -B build to get warnings for these cases; expect some noise from third-party modules.
Fix:
set(MY_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/include")
target_include_directories(myapp PRIVATE ${MY_INCLUDE_DIR})
In a project meant to be used as a subproject (via add_subdirectory or FetchContent), prefer CMAKE_CURRENT_SOURCE_DIR or PROJECT_SOURCE_DIR over CMAKE_SOURCE_DIR. CMAKE_SOURCE_DIR always points at the top-level project, so it points at the wrong place as soon as someone else includes your project in theirs.
Error 7: Duplicate target
add_executable(myapp main.cpp)
add_executable(myapp other.cpp) # same name
# CMake Error: add_executable cannot create target "myapp" because
# another target with the same name already exists.
Fix: Use distinct target names.
add_executable(myapp main.cpp)
add_executable(myapp2 other.cpp)
Target names are global to the whole build, not per directory. The realistic version of this error is not two lines in one file: it is two dependencies pulled in with FetchContent or add_subdirectory that both define a target with a generic name like uninstall or format, or the same dependency added twice through different paths. For your own targets, a project prefix (myproj_core) plus an ALIAS target (add_library(MyProj::core ALIAS myproj_core)) avoids collisions. For dependencies, guard a second inclusion with if(NOT TARGET ...), or declare them all through FetchContent, which populates a given content name only once.
Error 8: Unknown command (typo)
add_executabel(myapp main.cpp) # typo: executable
# CMake Error: Unknown CMake command "add_executabel".
Fix: Check spelling. The same message appears for correctly spelled commands that are newer than your CMake, or that come from a module you have not included (for example FetchContent_Declare without include(FetchContent), or gtest_discover_tests without include(GoogleTest)).
Error 9: Paths with spaces
set(MY_PATH C:/Program Files/MyLib)
# Becomes split: C:/Program and Files/MyLib
# Fix: quote the path
set(MY_PATH "C:/Program Files/MyLib")
target_include_directories(myapp PRIVATE "${MY_PATH}")
CMake splits unquoted arguments on whitespace and semicolons, and a list in CMake is just a string with semicolons. The unquoted set above therefore stores the two-element list C:/Program;Files/MyLib. Quoting when you set and when you expand avoids this. The same rule is why "${SOURCES}" passes the whole list as one argument while ${SOURCES} passes each element separately; for paths you want quotes, for lists of files you usually do not.
Error 10: Dirty in-source build
Running cmake . in the source directory is allowed by default. It fills the source tree with CMakeCache.txt, CMakeFiles/ and generated build files, which then show up in git status and can confuse a later out-of-source build. Many projects therefore refuse in-source builds explicitly, with a check near the top of CMakeLists.txt that prints something like:
CMake Error at CMakeLists.txt:5 (message):
In-source builds are not allowed. Please create a build directory.
Fix: Use an out-of-source build. If you already ran CMake in the source directory, delete the generated CMakeCache.txt and CMakeFiles/ there first. Otherwise the stale cache in the source tree keeps being picked up and the project’s check keeps failing even from a new build directory.
# Avoid
cd project/
cmake .
# Prefer
cd project/
mkdir build
cd build
cmake ..
With CMake 3.13 or later the same thing is one command, which is also easier to put in scripts: cmake -S . -B build, then cmake --build build.
CMakeLists.txt structure
Minimal template
cmake_minimum_required(VERSION 3.16)
project(MyProject LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Threads REQUIRED)
add_executable(myapp
src/main.cpp
src/utils.cpp
)
target_include_directories(myapp PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_link_libraries(myapp PRIVATE
Threads::Threads
)
Two small choices in this template prevent later errors. Threads::Threads instead of a raw pthread works on every platform, including Windows where there is no libpthread, and adds the right compiler flag where one is needed. CMAKE_CXX_STANDARD_REQUIRED ON turns a silent fallback to an older standard into a configure error when the compiler cannot provide C++17.
Useful variables
${CMAKE_SOURCE_DIR} # Top-level source directory
${CMAKE_BINARY_DIR} # Top-level build directory
${CMAKE_CURRENT_SOURCE_DIR}
${PROJECT_NAME}
${CMAKE_CXX_COMPILER}
CMAKE_SOURCE_DIR and CMAKE_BINARY_DIR are the top of the whole build; the CMAKE_CURRENT_* variants are the directory currently being processed. Mixing them up works as long as the project is built on its own and breaks when it becomes someone else’s subproject, which is why most modern CMake code uses the CURRENT or PROJECT_ forms.
find_package errors
find_package has two operating modes, and knowing which one you’re hitting changes how you fix it:
- Module mode: CMake ships a
Find<Package>.cmakescript (e.g.FindZLIB.cmake) that searches known locations. If the package isn’t where CMake expects, set the hint variables the module documents (often<PACKAGE>_ROOT). - Config mode: the package itself ships a
<package>Config.cmake(installed alongside it by its own build). This is what most modern libraries (installed viavcpkg,conan, ormake install) provide.
# Point CMake at non-standard install locations (works for both modes)
set(CMAKE_PREFIX_PATH "/opt/mylib;/usr/local/otherlib" ${CMAKE_PREFIX_PATH})
find_package(MyLib REQUIRED)
# Force config mode explicitly (skip the Find<X>.cmake module search)
find_package(MyLib REQUIRED CONFIG)
# Version constraints
find_package(Boost 1.75 REQUIRED) # at least 1.75
find_package(fmt 9.0...10.0 REQUIRED) # a range (CMake 3.19+)
In config mode the error text is different and more helpful than people give it credit for:
Could not find a package configuration file provided by "MyLib" with any
of the following names:
MyLibConfig.cmake
mylib-config.cmake
Add the installation prefix of "MyLib" to CMAKE_PREFIX_PATH or set
"MyLib_DIR" to a directory containing one of the above files.
It tells you the exact file names it searched for and the two variables that fix it. MyLib_DIR must point at the directory containing the config file (for example /opt/mylib/lib/cmake/MyLib), while CMAKE_PREFIX_PATH takes the install prefix (/opt/mylib). Swapping the two is a common reason the fix “does not work”. On Linux, a library installed from the distro without its -dev package has the .so but no headers and no config file, so it is “installed” and still not found.
With vcpkg, the usual fix for “Could NOT find” is simpler than hunting for hint variables — pass the toolchain file so vcpkg’s own Config files are on the search path:
cmake -B build -DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake
The toolchain file has to be given on the first configure of a build directory. Adding it to an existing build directory has no effect because the toolchain is only read when the compiler is first detected; delete the build directory (or use cmake --fresh) and configure again. I have lost more time to this one than to any actual missing package: the command line looks right, and the cache quietly ignores it.
Target dependencies
target_link_libraries takes a visibility keyword that determines how a dependency propagates — getting this wrong is a common source of confusing “target not found” or unresolved-symbol errors in larger projects:
target_link_libraries(mylib
PUBLIC fmt::fmt # mylib uses fmt in its own headers — anyone linking mylib also gets fmt
PRIVATE internal_utils # only mylib.cpp uses this — not exposed to consumers
INTERFACE header_only_lib # mylib doesn't use it itself, but consumers including its headers need it
)
A frequent mistake: linking a dependency as PRIVATE when one of your public headers actually #includes that dependency’s headers — consumers of your library then fail to compile because they never see that transitive dependency. The rule of thumb: if a dependency’s types or headers appear in your .h files, it must be PUBLIC (or INTERFACE for a header-only library that itself has no .cpp); if it’s only used inside your .cpp files, PRIVATE is correct and keeps your dependency graph smaller for downstream consumers.
A related error appears when you mix the two call styles on one target: The keyword signature for target_link_libraries has already been used with the target "myapp". All uses of target_link_libraries with a target must be either all-keyword or all-plain. Once any call on a target uses PUBLIC/PRIVATE/INTERFACE, every other call on that target must use one too. The fix is to add a keyword to the old plain call, typically PRIVATE. This usually surfaces when a newer CMake module (or your own new code) adds a keyword call to a target that older code links in the plain style.
Debugging a configure step
When the message alone does not explain the failure, these are the tools I reach for, roughly in this order:
message(STATUS "MY_VAR=${MY_VAR}")right before the failing line. Most “why is this path wrong” questions end here.- Delete the build directory, or run
cmake --fresh -S . -B buildon CMake 3.24 and later. A large share of confusing behavior is the cache remembering an old compiler, an old toolchain file or a failed search.find_libraryandfind_pathresults are cached, so after installing a missing library the oldNOTFOUNDvalue can survive until the cache entry is removed. cmake --debug-find(CMake 3.17+) prints the directories afind_packageorfind_librarycall searched. It turns “Could NOT find” from guesswork into a list you can compare against where the file actually is.cmake --trace-expandlogs every command with variables expanded. The output is huge, so redirect it to a file and search for the variable or command you care about.cmake --build build --verbose(or-v) shows the actual compiler and linker command lines, which is how you confirm whether an include directory or library really reached the compiler.
Once configuration works, build speed is a separate topic: Ninja (-G Ninja) and ccache (-DCMAKE_CXX_COMPILER_LAUNCHER=ccache) help most, and cmake --build build -j runs jobs in parallel.
Summary
Quick reference
| Message | Likely cause | Fix |
|---|---|---|
version ... or higher is required | Old CMake | Upgrade, or lower the requirement only if safe |
not built by this project | Target modified before it is defined | Move add_executable / add_library first |
cannot find -lfoo (at build time) | Misspelled or undefined target name | Use the real target, prefer ns::name targets |
Parse error | Unbalanced ( or " | Find the last unclosed command |
Could NOT find / Could not find a package configuration file | Missing package or search path | CMAKE_PREFIX_PATH, <Pkg>_DIR, vcpkg toolchain |
Cannot find source file | Wrong relative path or case | Paths are relative to the current CMakeLists.txt |
| (no error, missing include) | Unset variable expanded to empty | --warn-uninitialized |
Most CMake issues boil down to target ordering, search paths and the cache. Build out of source, define targets before modifying them, use namespaced imported targets for dependencies, and when something makes no sense, reconfigure from a clean build directory before digging deeper.
More related posts
Frequently Asked Questions (FAQ)
Q. I fixed CMakeLists.txt, but CMake still uses the old value. Why?
A. Values set with -D or set(... CACHE ...) are stored in CMakeCache.txt and persist between runs, and set(VAR value CACHE ...) does not overwrite an existing cache entry unless you add FORCE. Remove the entry with cmake -U VAR, edit it with ccmake/cmake-gui, or reconfigure from scratch by deleting the build directory (or cmake --fresh on CMake 3.24 and later).