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:
find_file
to retrieve the full path to a file.find_library
to find a library, shared or static.find_package
to find and load settings from an external project.find_path
to find the directory containing a file.find_program
to find an executable.
The workhorse of dependency discovery is find_package
, which will cover your
needs in almost all use cases.
find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[NO_POLICY_SCOPE])
This command will attempt finding the package with name <PackageName>
by
searching in a number of predefined folders.
It is possible to ask for a minimum or exact version. If REQUIRED
is
given, a failed search will trigger a fatal error. The rules for the search
are obtained from modules named Find<PackageName>.cmake
.
Packages can also have components and you can ask to detect just a handful of them.
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
toyour-target
and to all other targets in your project that will useyour-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.
Try to compile and run.
Browse the documentation of the
Find<PackageName>.cmake
module.Try to compile with verbosity and verify how the imported target modified compile flags and definitions.
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)Source file (
example.f90
):program example use omp_lib !$OMP PARALLEL print *, "hello from thread ", omp_get_thread_num() !$OMP END PARALLEL endAnd the
CMakeLists.txt
file:cmake_minimum_required(VERSION 3.14) project(example LANGUAGES Fortran) add_executable(example example.f90) find_package(OpenMP REQUIRED COMPONENTS Fortran) target_link_libraries(example PRIVATE OpenMP::OpenMP_Fortran)Source file (
example.cpp
):#include <iostream> #include <mpi.h> int main() { MPI_Init(NULL, NULL); int id; MPI_Comm_rank(MPI_COMM_WORLD, &id); std::cout << "hello from rank " << id << std::endl; MPI_Finalize(); }And the
CMakeLists.txt
file:cmake_minimum_required(VERSION 3.14) project(example LANGUAGES CXX) add_executable(example example.cpp) find_package(MPI REQUIRED COMPONENTS CXX) target_link_libraries(example PRIVATE MPI::MPI_CXX)Source file (
example.f90
):program example use mpi implicit none integer :: error integer :: id call MPI_Init(error) call MPI_Comm_rank(MPI_COMM_WORLD, id, error) print *, "hello from rank ", id call MPI_Finalize(error) end programAnd the
CMakeLists.txt
file:cmake_minimum_required(VERSION 3.14) project(example LANGUAGES Fortran) add_executable(example example.f90) find_package(MPI REQUIRED COMPONENTS Fortran) target_link_libraries(example PRIVATE MPI::MPI_Fortran)
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 throughfind_package(<package>)
.