find_package and linking libraries
find_package asks CMake to locate a library the system provides: a compiler package, a compression library, a testing framework. A successful search defines an imported target, and linking that target brings the include directories, the flags and the library file.
The Threads package is the one every concurrent program needs and the one that is always there. This lesson links it, then looks at a package that is missing and at a search that is allowed to fail.
A package that is required at configure time fails early, in the terminal where the developer can install it. That is the whole reason to search at configure time instead of at link time.
The search runs at configure time, so the versions and the paths are decided once and written into the build files. A missing package is a configure error, before a single object is compiled.
An imported target can also carry a version and a configuration, so a project can require at least a version and let CMake fail with the package name in the message. That is the portable form of a dependency check.
A package that is not found in one configuration and found in another is a difference the configure log records, which helps when a build works on one machine and not another.
Threads::Threads
find_package(Threads REQUIRED) locates the thread library of the platform and defines the imported target Threads::Threads. Linking it adds the right flag, which is -pthread with GCC.
The program starts a thread, joins it and prints a number. The build succeeds on a system where the flag is needed, which is the reason to use the imported target instead of guessing.
The imported target carries every property the library needs: the link flag, the include directory and, on some platforms, a compile definition. A project that links it is portable.
main.cpp
#include <iostream>
#include <thread>
int main()
{
int value = 0;
std::thread worker{[&value] { value = 42; }};
worker.join();
std::cout << value << '\n';
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(threads_demo CXX)
find_package(Threads REQUIRED)
add_executable(demo main.cpp)
target_link_libraries(demo PRIVATE Threads::Threads)
cmake -S . -B build
cmake --build build
./build/demo
-- The CXX compiler identification is GNU 15.2.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success
-- Found Threads: TRUE
-- Configuring done (0.6s)
-- Generating done (0.0s)
-- Build files have been written to: /tmp/cppcourse/work/L103/s0/build
[ 50%] Building CXX object CMakeFiles/demo.dir/main.cpp.o
[100%] Linking CXX executable demo
[100%] Built target demo
42
A package that is missing
A REQUIRED search that finds nothing stops the configure with a message that names the package and the file it looked for. That is the right failure for a dependency the project cannot do without.
The program asks for a package that does not exist and CMake stops at the find_package line. The message names the missing package and lists the directories it searched.
The fix is to install the development package of the library: on Alpine the -dev package, on Debian the -dev package of the same name. The configure error is usually clearer than the compile error that follows a missing header.
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(missing_demo CXX)
find_package(NoSuchPackage REQUIRED)
add_executable(demo main.cpp)
main.cpp
int main()
{
return 0;
}
cmake -S . -B build
-- The CXX compiler identification is GNU 15.2.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
CMake Error at CMakeLists.txt:4 (find_package):
By not providing "FindNoSuchPackage.cmake" in CMAKE_MODULE_PATH this
project has asked CMake to find a package configuration file provided by
"NoSuchPackage", but CMake did not find one.
Could not find a package configuration file provided by "NoSuchPackage"
with any of the following names:
NoSuchPackageConfig.cmake
nosuchpackage-config.cmake
Add the installation prefix of "NoSuchPackage" to CMAKE_PREFIX_PATH or set
"NoSuchPackage_DIR" to a directory containing one of the above files. If
"NoSuchPackage" provides a separate development package or SDK, be sure it
has been installed.
-- Configuring incomplete, errors occurred!
A package that is optional
find_package(X QUIET) does not stop when the package is missing; it sets X_FOUND to false and the project decides what to do. The usual pattern is a compile definition that turns a feature on when the library is there.
The program searches for a package that is not installed, finds it missing and prints the fallback. The configure output shows nothing about the search, which is what QUIET means.
An optional dependency is how a project supports a compression format or a database driver when the system has it. The source uses #ifdef around the code that needs it, and the build defines the macro only when the package was found.
main.cpp
#include <iostream>
int main()
{
#ifdef HAVE_ZLIB
std::cout << "compression: available\n";
#else
std::cout << "compression: not available\n";
#endif
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(optional_demo CXX)
find_package(ZLIB QUIET)
add_executable(demo main.cpp)
if(ZLIB_FOUND)
target_compile_definitions(demo PRIVATE HAVE_ZLIB)
target_link_libraries(demo PRIVATE ZLIB::ZLIB)
endif()
cmake -S . -B build
cmake --build build
./build/demo
-- The CXX compiler identification is GNU 15.2.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.6s)
-- Generating done (0.0s)
-- Build files have been written to: /tmp/cppcourse/work/L103/s2/build
[ 50%] Building CXX object CMakeFiles/demo.dir/main.cpp.o
[100%] Linking CXX executable demo
[100%] Built target demo
compression: not available
What the imported target carries
An imported target has properties, and the configure step can print them. The program below prints the include directories and the link libraries of Threads::Threads, which shows that the target is the whole interface.
The output for the thread package is a pair of empty lists and the library name, because on this platform the flag is a compile option and there is no separate file. The point is that the project never spells any of it.
message(STATUS ...) writes to the configure log. It is how a CMakeLists.txt reports a decision, and it appears in the transcript of the configure command.
main.cpp
int main()
{
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(properties_demo CXX)
find_package(Threads REQUIRED)
get_target_property(includes Threads::Threads INTERFACE_INCLUDE_DIRECTORIES)
get_target_property(libraries Threads::Threads INTERFACE_LINK_LIBRARIES)
message(STATUS "Threads include directories: ${includes}")
message(STATUS "Threads link libraries: ${libraries}")
add_executable(demo main.cpp)
target_link_libraries(demo PRIVATE Threads::Threads)
cmake -S . -B build
cmake --build build
-- The CXX compiler identification is GNU 15.2.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success
-- Found Threads: TRUE
-- Threads include directories: includes-NOTFOUND
-- Threads link libraries: libraries-NOTFOUND
-- Configuring done (0.6s)
-- Generating done (0.0s)
-- Build files have been written to: /tmp/cppcourse/work/L103/s3/build
[ 50%] Building CXX object CMakeFiles/demo.dir/main.cpp.o
[100%] Linking CXX executable demo
[100%] Built target demo
Exercises
- Add a test with CTest that runs the demo and checks its exit status.
- Find a package that is installed on this machine and print one of its properties.
- Make the optional package required and read the configure error.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What does
find_package(Threads REQUIRED)do?Answer
Locates the platform thread library and defines Threads::Threads
The package is found from the system and its imported target carries the flags.
-
What does linking Threads::Threads give the target?
Answer
The link flag and the compile options the platform needs
The imported target is the full interface: flags, definitions and the library.
-
What happens when a REQUIRED package is not found?
Answer
Configure stops with an error naming the package
REQUIRED turns a missing package into a configure error before any compiler runs.
-
What does the QUIET keyword change?
Answer
The package is optional and no message is printed
QUIET suppresses the search messages and leaves the decision to the project.
-
What does this program print?
std::thread t{[] { std::cout << 1; }}; t.join();Answer
1
The thread runs the lambda once and join waits for it.
-
Which variable does find_package(ZLIB QUIET) set?
Answer
ZLIB_FOUND
Every package sets <Package>_FOUND, and the project tests it to decide what to compile.
-
What does
message(STATUS ...)do?Answer
Prints during configure
The status message appears in the configure output, which is where build decisions are reported.
-
Why is the imported target better than adding -pthread by hand?
Answer
It carries the flags of the platform, which are not always -pthread
The flag is platform-specific; the imported target knows the right one.
-
What does
get_target_property(includes Threads::Threads INTERFACE_INCLUDE_DIRECTORIES)do?Answer
Reads a property of the imported target into a variable
get_target_property reads a property; set_target_properties writes one.
-
This project is configured when ZLIB is not found, so the build does not define
HAVE_ZLIB. What does the program print?#include <iostream> int main() { #ifdef HAVE_ZLIB std::cout << "compression: available\n"; #else std::cout << "compression: not available\n"; #endif }Answer
compression: not available
The HAVE_ZLIB definition is only added when the package was found, so the #else branch is compiled.
The code on this page was built with g++ 15.2.0 and CMake 4.2.3 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container. The programs whose section shows a compiler error were compiled until that error and were not run.
Article author: Arthur Isaev