Finding and using dependencies

Questions

  • How can I use CMake to detect and use the dependencies of my project?

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.

We cannot stress this enough: you should only use the other commands in the find_ family in very special, very narrow circumstances. Why so?

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

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

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

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

Exercise 23: Using MPI

In this exercise, you will attempt compiling a “Hello, world” program that uses the message passing interface (MPI).

  1. Check whether a FindMPI.cmake module exists in the built-in module library.

  2. Get acquainted with its components and the variables and imported targets it defines.

The scaffold project is in content/code/day-2/23_mpi-cxx.

  1. Compile the source file to an executable.

  2. Link against the MPI imported target.

  3. Invoke a verbose build and observe how CMake compiles and links.

A working example is in the solution subfolder.

Alternatives: Config scripts and pkg-config

What to do when there is no built-in Find<PackageName>.cmake module for a package you depend on? The package developers might be already prepared to help you out:

  • They ship the CMake-specific file <PackageName>Config.cmake which describes how the imported target should be made for their package. In this case, you need to point CMake to the folder containing the Config file using the special <PackageName>_DIR variable:

    $ cmake -S. -Bbuild -D<PackageName>_DIR=/folder/containing/<PackageName>Config.cmake
    
  • They include a .pc file, which, on Unix-like platforms, can be detected with the pkg-config utility. You can then leverage pkg-config through CMake:

    # find pkg-config
    find_package(PkgConfig REQUIRED)
    # ask pkg-config to find the UUID library and prepare an imported target
    pkg_search_module(UUID REQUIRED uuid IMPORTED_TARGET)
    # use the imported target
    if(TARGET PkgConfig::UUID)
      message(STATUS "Found libuuid")
    endif()
    

    This was the strategy adopted in Probing compilation, linking, and execution when testing the use of the UUID library.

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

  • You can also use the classic Unix tool pkg-config to find software dependencies, but this is not as robust as the CMake-native Find<package> modules.

Footnotes

1

Example adapted from page 85 in OpenMP 4.5 examples.

2

The way in which to trigger a verbose build depends on the native build tool you are using. For Unix Makefiles:

$ cmake --build build -- VERBOSE=1

For Ninja:

$ cmake --build build -- -v