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

  1. Add a test with CTest that runs the demo and checks its exit status.
  2. Find a package that is installed on this machine and print one of its properties.
  3. 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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. What does message(STATUS ...) do?

    Answer

    Prints during configure

    The status message appears in the configure output, which is where build decisions are reported.

  8. 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.

  9. 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.

  10. 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