tl;dr
Most of the time simply use find_package
, set CMAKE_PREFIX_PATH
to include the path specified in CMAKE_INSTALL_PREFIX
when the package was installed like so:
find_package(std_compat REQUIRED)
target_link_libraries(my_library PUBLIC std_compat::std_compat)
Tools like spack
will set CMAKE_PREFIX_PATH
for you. Otherwise specify it using an envionment variable or with a -D
flag to cmake.
How to add the dependency
Adding a dependency in CMake is pretty straight forward, but depends on where it is coming from. You should do the first one that is applicable.
- If a library is defined in the same cmake file or one in a parent directory, you can use it directly.
- If a library is defined in some other cmake project, you often import it with
find_package
- If a library has a pkg-config, you should use
pkg_search_module
to import it. - If a library has a find command such as
llvm-config
or Tensorflow’s you can create an imported target, useexecute_process
to get the flags, and use theSHELL:
prefix fortarget_compile_options
and ortarget_link_options
to set the build options - If a library has none of these, locate the library with
find_library
and header withfind_file
then create a imported target.
Public vs Private
If the thing you are building is a library, generally choose PUBLIC
unless it you are writing a header only library in which case choose INTERFACE
.
If the thing you are building is an executable, choose PRIVATE
.
You can also choose PRIVATE
if none of the headers you link to are in the transitive closure of any of your public header files.
What is the name to pass to find_package
and target_link_libraries
If it is one of the built-ins (i.e. MPI/OpenMP), check the CMake docs.
If it comes from a user-defined package, look for a directory called cmake
installed with the package. In there should be a file called ...Targets.cmake
where the ...
is some name. The text before Targets.cmake
is the entry to pass to find_package
. In this file is a list of expectedTargets
which will contain the names the package exports.
Importing PkgConfig libraries
find_package(PkgConfig REQUIRED)
pkg_search_module(ZSTD IMPORTED_TARGET GLOBAL libzstd)
target_link_libraries(my_library PUBLIC PkgConfig::ZSTD)
There isn’t anything sacred about ZSTD
as the name here. It can be replaced with another name that makes sense.
The libzstd
bit is what pkg-config looks for.
IMPORTED_TARGET GLOBAL
makes things “modern” allowing you to just add a target_link_library
command to add the include flags.
pkg-config --list-all |grep $name_of_package
can help you find package config files.
Importing libraries with a find tool
Example using Tensorflow:
find_package(Python REQUIRED)
execute_process(COMMAND "${Python_EXECUTABLE}" "-c" "import tensorflow as tf; print(*tf.sysconfig.get_compile_flags())"
OUTPUT_VARIABLE TENSORFLOW_COMPILE_FLAGS
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND "${Python_EXECUTABLE}" "-c" "import tensorflow as tf; print(*tf.sysconfig.get_link_flags())"
OUTPUT_VARIABLE TENSORFLOW_LINK_FLAGS
OUTPUT_STRIP_TRAILING_WHITESPACE)
target_link_options(my_library PUBLIC
SHELL:${TENSORFLOW_LINK_FLAGS}
)
target_compile_options(my_library PUBLIC
SHELL:${TENSORFLOW_COMPILE_FLAGS}
)
Transitive Dependencies
tl;dr, add the find_package or similar commands to FooConfig.cmake.in
file
you wrote for each PUBLIC
, INTERFACE
or IMPORTED
dependency that you add.
This isn’t required for PRIVATE
dependencies. Or
LibPressioTools
for an example with several optional dependencies.
Using find_library
find_program
and find_file
Avoid this if possible.
find_library(CUFile_LIBRARY cufile PATHS ${CUDAToolkit_LIBRARY_DIR} REQUIRED)
find_file(CUFile_HEADER cufile.h PATHS ${CUDAToolkit_INCLUDE_DIR} REQUIRED)
# After CMake 3.20, prefer `cmake_path` instead because it doesn't access the filesystem
get_filename_component(CUFile_HEADER_DIR "${CUFile_HEADER}" DIRECTORY)
# it's probably better to create an IMPORTED library here than to just add them all to the library I want to link
# cuFile to, but CUDA::cuda_driver and CUDA::cuda_rt are link # dependencies for cufile that I found in the documentation
target_link_libraries(libpressio PRIVATE CUDA::cuda_driver CUDA::cudart ${CUFile_LIBRARY})
target_include_directories(libpressio PRIVATE ${CUFile_HEADER_DIR})
These often need to be exported differently. See the docs for best practices to export these