Finding and using dependencies

Objectives

  • Learn how to use find_package.

  • Learn what other detection alternatives exist.

The vast majority of software projects do not happen in a vacuum: they will have dependencies on existing frameworks and libraries. Good documentation will instruct your users to ensure that these are satisfied in their programming environment. The build system is the appropriate place to check that these preconditions are met and that your project can be built correctly. In this episode, we will show you few examples of how to detect and use dependencies in your CMake build system.

Finding dependencies

CMake offers a family of commands to find artifacts installed on your system:

The workhorse of dependency discovery is find_package, which will cover your needs in almost all use cases.

  • For a large selection of common dependencies, the Find<PackageName>.cmake modules shipped with CMake work flawlessly and are maintained by the CMake developers. This lifts the burden of programming your own dependency detection tricks.

  • find_package will set up imported targets: targets defined outside your project that you can use with your own targets. The properties on imported targets defines usage requirements for the dependencies. A command such as:

    target_link_libraries(your-target
      PUBLIC
        imported-target
      )
    

    will set compiler flags, definitions, include directories, and link libraries from imported-target to your-target and to all other targets in your project that will use your-target.

These two points above simplify enormously the burden of dependency detection and consistent usage within a multi-folder project.

Using find_package

When attempting dependency detection with find_package, you should make sure that:

  • A Find<PackageName>.cmake module exists,

  • Which components, if any, it provides, and

  • What imported targets it will set up.

You can browse the documentation of a module like this:

$ cmake --help-module FindOpenMP | less

When browsing documentation of Find<PackageName>.cmake module, always first look for IMPORTED targets. These are the targets you will want to link against. Then all the rest will just work and you won’t need to deal with compile flags, definitions, and include paths.

A complete list of Find<PackageName>.cmake can be found from the command-line interface:

$ cmake --help-module-list | grep "Find"

Another way is to browse available modules: https://github.com/Kitware/CMake/tree/master/Modules

Exercise: working with find_package

Choose one of the examples below which is closest to your work and most relevant for your code.

  1. Try to compile and run.

  2. Browse the documentation of the Find<PackageName>.cmake module.

  3. Try to compile with verbosity and verify how the imported target modified compile flags and definitions.

  4. Bonus: Try to adapt what we learned to an example which uses the BLAS or LAPACK library.

Source file (example.cpp):

#include <cstdlib>
#include <iostream>

#include <omp.h>

int main() {

  #pragma omp parallel
  {
    std::cout << "hello from thread " << omp_get_thread_num() << std::endl;
  }

  return EXIT_SUCCESS;
}

And the CMakeLists.txt file:

cmake_minimum_required(VERSION 3.14)

project(example LANGUAGES CXX)

add_executable(example example.cpp)

find_package(OpenMP REQUIRED COMPONENTS CXX)

target_link_libraries(example PRIVATE OpenMP::OpenMP_CXX)

Keypoints

  • CMake has a rich ecosystem of modules for finding software dependencies. They are called Find<package>.cmake.

  • The Find<package>.cmake modules are used through find_package(<package>).