Windows cmake find package

Whether we want to or not, many folks have to interact with CMake at least once in their life. If they are unlucky, they might have to find dependencies using find_package. If they are extremely unlucky, they might have to write a file to work with find_package. At the end of this, they might have a file that kind of works, but most likely is based off of a tutorial from 2008. A lot of things have changed since then. There’s an easier way to do it these days!

In a previous post, I alluded to a project I’ve been working on since 2018. It’s called IXM, and while it’s not ready for general use, it is where I realized there is a common set of operations for finding packages, their components, and setting their correct properties. We’ll be using some of what I’ve learned and figured out over the past two years to understand how find_package files work and how to make them useful for your projects and the folks who depend on them.

This tutorial is written for CMake 3.14 and later, though I personally recommend using CMake 3.16 as it is the version installed with the most recent Ubuntu LTS release, 20.04.

Additionally, this tutorial isn’t meant to discuss how to write a <package-name>-config.cmake file. Those have a different set of options but also tend to be smaller in practice. Instead I’ll be showing how to write what’s known as a find_package MODULE file. That said, we will eventually tackle how to handle writing a usable find_package file that can be used in cmake --find-package mode, in the so-called Script Mode or with cpack for the External Generator. For now, however, we’ll focus on the most common workflow for CMake: Configure and Build.

The Basics

Within CMake, there are several commands that are used when writing a find_package file. The most important ones are find_program, find_library, find_path, and lastly find_file. Each of these has a purpose, but we will not always use them. Depending on what you are trying to find, you might also find yourself using execute_process, file(READ), file(STRINGS), string(REGEX MATCH), and mark_as_advanced. Additionally, 99.9% of the time, you’ll want to use the CMake provided module FindPackageHandleStandardArgs.

Before we can use find_package, however, we need to make sure CMake can find it in the first place. The most common place to put your cmake scripts is inside the project’s root directory under a cmake/ directory. We can then add this path to our CMAKE_MODULE_PATH variable so CMake knows where to find us.

This is the bare minimum we need for CMake to find our following find_package files.

Understanding the Commands

The commands most typically used have a very large amount of documentation and thus it can be a bit overwhelming. Worry not. Most of the time you don’t need to worry about these additional parameters. We can cover them in detail in later posts.

find_program

This is used to find the path to a file that the system considers executable. Whether it is a script or an actual executable is actually irrelevant. As long as you could execute it via something like exec (or CreateProcess on Windows), it can be used as an executable.

find_library

This is used to find both shared and static libraries. The form for this is a bit odd, as languages with custom static library formats (e.g., Rust’s .rlib) won’t ever be found, however we can find .so, .dylib, .dll, .a, and .lib files by default. on macOS, .frameworks are also searched for first. Users can configure this with the CMAKE_FIND_FRAMEWORK variable.

Finding an Executable

Finding executables in packages is one of the easiest things, compared to finding libraries. Executables can be made into imported targets, much like libraries, and they can then be used in the add_custom_command and add_custom_target commands with their imported name.

A popular tool that we can use as an example here is sphinx-build, the actual «compiler» for the sphinx documentation framework.

Inside of a file named FindSphinx.cmake, we have

You’ll notice that, unlike what you might be used to, we use Sphinx_EXECUTABLE and not SPHINX_EXECUTABLE. This will make more even more sense in later posts, but it’s consider «good behavior» to prefix your variables with the package name, and not an upper cased version of said package name. This also prevents a theoretical find_package(SPHINX) and a find_package(Sphinx) having a variable collision.

That’s the basics of it! We can now let find_package_handle_standard_args take care of business for us, and then hide our cache variable if it’s been found.

Seems simple enough, right? Well, we still need to import this executable so it’s usable by the build system. To do that we’re going to create what’s known as an imported executable and set the IMPORTED_LOCATION property to the value stored in Sphinx_EXECUTABLE

Of course, it’s not enough to just do this. We need to guard our sphinx target creation. What if we never found it in the first place? The file would error for users and that’s no bueno!

Additionally, what if someone wants to name their target Sphinx? It’s best to try to stay out of their way, or our simple documentation generator might interfere with someone’s actual dependency or project! We can do this by namespacing the Sphinx target, which is only permitted for imported targets.

But wait! What if multiple projects in our build tree depend on find_package(Sphinx)? Then it will fail to work because Sphinx::Sphinx is already a target! We should fix that by only creating the target if we’ve found Sphinx and it hasn’t been created yet.

While this might seem simple, once we move into later posts in this tutorial, we will expand on this and get it to its proper state.

Finding a Library

Finding libraries in CMake is deceptively simple. It should just be as easy as finding the path to a single file, setting a variable, and calling it a day… right?

If only that were the case. Remember, we have access to imported targets, and this can be used to provide more information to CMake when generating the build system, and prevents typos for variables. Even more important, we can create dependency lists of libraries and their components so that users can follow the «YOLO» principle (i.e., You Only Link Once).

Worse, sometimes these libraries provide <Library>Config.cmake/<library>-config.cmake files which might not create imported targets and simply export cache variables. One such library is SDL2. SDL2 is a wonderfully neat library, but it’s CMake experience leaves a lot to be desired. Sounds like a perfect way to create our own FindSDL2.cmake file.

Using what we’ve learned above regarding find_package for executable based packages, we can start with a basic skeleton file that’s eerily similar to what we did before.

Try to use this however, and you’ll quickly find there is no way to #include <SDL2/SDL.h> 😱! That just won’t do! We need to find the path where this file is found. This is where find_path comes into play to find the SDL2_INCLUDE_DIR. It’s a requirement for us to use SDL2 at all, so it’ll be added to the REQUIRED_VARS parameter to find_package_handle_standard_args. Because it’s a cache variable, we’ll also mark it as ADVANCED if we found SDL2, and finally add it to the INTERFACE_INCLUDE_DIRECTORIES property for SDL2::SDL2

Awesome! We can now just call target_link_libraries(<my-target> PRIVATE SDL2::SDL2) and we’ve got full access to its headers and automatically link against the library itself.

That… should be it right? Unfortunately, no. On Windows, we must sometimes link against SDL2main (other platforms are permitted to as well), because SDL2 sometimes overrides the main function with a redefinition. We can ask that find_package search for SDL2main by declaring a component called SDL2::Main. Ideally, we will always search for all components, and let find_package and find_package_handle_standard_args take care of the details. However we need to (of course) do some extra work.

Firstly, we need to find the SDL2main library itself. CMake’s find_package cares less about how variables are named, but does care about how the _FOUND variables are named. Effectively, for each component in a package, find_package_handle_standard_args considers a component found if <package>_<component>_FOUND is true or false. Generally, you’ll want to stick to a consistent naming convention, so we’ll name our _LIBRARY variable SDL2_Main_LIBRARY, and if it’s set at all, we’ll just set SDL2_Main_FOUND to YES (one of the strings CMake considers to be a boolean)

We then pass HANDLE_COMPONENTS as a flag to find_package_handle_standard_args.

Next we’ll need to also mark the variable as advanced but only if its found and ONLY if find_package_handle_standard_args didn’t return early. This might seem counter-intuitive, but that’s CMake for ya! We might as well also import the library and make it depend on SDL2::SDL2.

That should be it, right? Again, things are not as simple as they may seem. SDL2 introduces bug fixes, new APIs, etc. in minor point releases. How do we know for sure we’re building with SDL2 2.15? We need to check the version. How does SDL2 express this? Inside of the SDL2/SDL_version.h header. This, here, is the part where things will become very painful. The only solution we have is to… use a regex 😱😭.

SDL2 has the following C preprocessor defines declared:

  • SDL_MAJOR_VERSION
  • SDL_MINOR_VERSION
  • SDL_PATCHLEVEL

Luckily, these are fairly simple to find, so we can just get a list of matching lines via file(STRINGS)

OK, but that doesn’t actually get us the data right? So we need to extract it further, and this is where it really does get messy. We’ll also pass it to find_package_handle_standard_args as the VERSION_VAR and then set the VERSION property of SDL2::SDL2

And that’s all we need to do! We’re done 🎉 (for now 😱)

Exhausting wasn’t it? Luckily, once you write a file like these you rarely need to ever touch them again… At least, until the next entry in this series. 😈

Skip to content

CMake: find_package()

What You Should Know & Your Actions

When we need to add an external project as the dependency, we will use command find_package in CMakeLists.txt to let CMake know where the header files and libraries are located, such that they can be found and linked properly later on.

It is ideal that the developer of the external project provides a <package>Config.cmake file (or a <lower-case-name>-config.cmake file) for us/users, and this file will be cached into database (in the default installation path) after we sudo make install this external project. You can check if they are available by the locate command.

# example commands
locate OpenCVConfig.cmake
locate gflags-config.cmake

# the results (if available)
/usr/share/OpenCV/OpenCVConfig.cmake
/usr/lib/x86_64-linux-gnu/cmake/gflags/gflags-config.cmake

If they are available, you only need to write one single line in the CMakeLists.txt file. For example,

find_package(PCL)   # it will work given that PCLConfig.cmake is available

Unfortunately, there are certain packages that do not provide (they should!) this CMake configuration file (or the files are not adopted by Ubuntu/apt). In this case, we need to use Find<package>.cmake file instead to locate this external project. We can write it on our own, but it is often the case that someone has done it before. So go ahead and find an available one to use.

After you find a good Find<package>.cmake file, we often place it under a folder named cmake or cmake-modules in the root directory of the current project (in parallel to include, src, etc.), and then add the path to this folder to a CMake variable CMAKE_MODULE_PATH (which is empty by default). With this setup, CMake will be able to look for your customized Find<package>.cmake file according to this CMake module path. What you need to do in CMakeLists.txt file is the following two lines of code.

list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake-modules")
find_package(Glog) # works given that FindGlog.cmake is placed under cmake-modules

  • The CMake command find_package has two modes (Config mode and Module mode) to find and load the CMake configuration of an external project.

  • In Config mode, it will look for a <package>Config.cmake or<lower-case-name>-config.cmake file in the default install path in Ubuntu system.

  • In Module mode, it will look for a Find<package>.cmake file in the path specified by variable CMAKE_MODULE_PATH ; this variable is empty unless set by user.

  • Module mode has higher priority than Config mode, which means that you can overwrite the default Config file by providing your own FindXXX file.

  • It will look for a file called Find<package>.cmake, first in CMAKE_MODULE_PATH (empty by default), and then under cmake installation path <CMAKE_ROOT>/Modules (e.g., /usr/share/cmake-3.5/Modules). Some example default modules include FindCUDA.cmake or FindOpenGL.cmake.

  • Therefore, to add our own cmake modules or overwrite system cmake modules, we can add Find<package>.cmake files in a folder called cmake and do the following in CMakeLists.txt, to make the best use of CMAKE_MODULE_PATH.

  • list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")

  • It will look for a file called <package>Config.cmake or <lower-case-name>-config.cmake under CMAKE_INSTALL_PREFIX (default to /usr/local). These cmake files are installed when we do sudo make install for 3rd party libraries. Examples are as follows (excerpts from command output).

  • -- Installing: /usr/local/share/OpenCV/OpenCVConfig.cmake
    -- Up-to-date: /usr/local/share/sophus/cmake/SophusConfig.cmake
    -- Up-to-date: /usr/local/lib/cmake/Ceres/CeresConfig.cmake
    -- Up-to-date: /usr/local/lib/cmake/g2o/g2oConfig.cmake
    -- Up-to-date: /usr/local/lib/cmake/GTest/GTestConfig.cmake
    -- Up-to-date: /usr/local/lib/cmake/DBoW3/DBoW3Config.cmake
    -- Up-to-date: /usr/local/lib/cmake/Pangolin/PangolinConfig.cmake

Commonly used commands to figure out where packages are installed.

  • locate <package>Config.cmake

To set flags

  • set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

  • set(CMAKE_BUILD_TYPE RelWithDebInfo)

  • cmake -DCMAKE_BUILD_TYPE=Debug

Note that all names marked by <xxx> should be replaced by their actual file names or package names. Case sensitive!

Actions Needed After A Package Is Found

In general, once a package is found, a new variable <package>_FOUND will be generated and set to true. Also, there will be a few more variables loaded into the compilation process, such as <package>_INCLUDE_DIR , <package>_INCLUDE_DIRS, <package>_LIBRARIES, <package>_LIBS. Then you can use them to let CMake know where to look for header files and libraries. For example,

find_package(Glog REQUIRED) # REQUIRED: will stop compilation if package is missing
include_directories(${GLOG_INCLUDE_DIRS})

add_executable (main src/main.cpp)
target_link_libraries (main ${GLOG_LIBRARIES})

Note that different projects may adopt different variable names. You need to make sure the spelling is completely correct. (Should it be LIBRARIES or LIBS?)

Typically, the variable names are expected to be documented in the official documentation of this external project. (They should!) If not, you will need to dig into <lower-case-name>-config.cmake or <package>Config.cmake or Find<package>.cmake files, which is where they are defined.

According to and , find_package() has two modes: «Module» mode and «Config» mode. If no module is found and the MODULE option is not given, the command proceeds to Config mode.

To use find_package in CMake, you simply need to include the find_package command in your CMakeLists.txt file and specify the package you want to locate. CMake will then search for the specified package and generate the necessary variables to link with the package in your project. Make sure to provide the correct package name and version to ensure CMake can locate the package successfully. You can also use optional arguments with find_package to customize the search behavior, such as specifying required components or package configuration files. Overall, find_package simplifies the process of locating and using external dependencies in CMake projects.

Best C++ Books to Read of December 2024




1


Rating is 5 out of 5

C Programming Language, 2nd Edition




2


Rating is 4.9 out of 5

Effective C: An Introduction to Professional C Programming




3


Rating is 4.8 out of 5

C# & C++: 5 Books in 1 — The #1 Coding Course from Beginner to Advanced (2023) (Computer Programming)




4


Rating is 4.7 out of 5

Learn C Programming: A beginner’s guide to learning the most powerful and general-purpose programming language with ease, 2nd Edition




5


Rating is 4.6 out of 5

The C Programming Language




6


Rating is 4.5 out of 5

C: The Complete Reference, 4th Ed.




7


Rating is 4.4 out of 5

C Programming For Dummies (For Dummies (Computer/Tech))

How to use find_package in CMake for finding Eigen libraries?

To use find_package in CMake for finding Eigen libraries, follow these steps:

  1. Download and install Eigen library on your system.
  2. Create a CMakeLists.txt file in your project directory.
  3. Inside the CMakeLists.txt file, add the following lines:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
cmake_minimum_required(VERSION 3.0)
project(YourProjectName)

# Find Eigen library
find_package(Eigen3 REQUIRED)
include_directories(${EIGEN3_INCLUDE_DIR})

# Add your project executable or library
add_executable(YourExecutableName YourSourceFile.cpp)
target_link_libraries(YourExecutableName Eigen3::Eigen)
  1. Replace YourProjectName with the name of your project, YourExecutableName with the name of your executable or library, and YourSourceFile.cpp with the name of your source file.
  2. Run CMake to generate the build files for your project.

These steps will allow CMake to find and include the Eigen library in your project.

What is the use of NO_MODULE and NO_DEFAULT_PATH keywords in find_package in CMake?

The NO_MODULE keyword in find_package in CMake is used to prevent find_package from looking for a module file when searching for a package. This can be useful if you want to specify a different package or configuration file to be used instead.

The NO_DEFAULT_PATH keyword, on the other hand, is used to prevent CMake from searching in the default package search paths when looking for a package. This can be useful if you want to specify a custom search path or if you don’t want CMake to search in the default locations for the package.

In summary, NO_MODULE is used to prevent CMake from looking for a module file, and NO_DEFAULT_PATH is used to prevent CMake from searching in the default package search paths.

How to use find_package in combination with add_library in CMake?

To use find_package in combination with add_library in CMake, you need to follow these steps:

  1. Use the find_package command to locate the package you want to use. For example, if you want to use the Boost library:
1
find_package(Boost REQUIRED)
  1. Once the package is found, you can include the necessary directories and libraries using the variables provided by find_package. For example, including Boost directories:
1
include_directories(${Boost_INCLUDE_DIRS})
  1. Finally, use the add_library command to define your library target and link it with the required libraries. For example, creating a library that uses Boost:
1
2
add_library(my_library src/my_library.cpp)
target_link_libraries(my_library ${Boost_LIBRARIES})

This way, you can find and include external libraries using find_package and link them with your own library using add_library.

How to debug find_package issues in CMake?

When encountering issues with the find_package command in CMake, there are several steps you can take to debug the problem:

  1. Check the CMake documentation: The first step is to consult the CMake documentation for the package you are trying to find. Make sure you are using the correct package name and version.
  2. Check the CMakeLists.txt file: Double-check the find_package command syntax and ensure it is correctly spelled and formatted.
  3. Set CMake variables: You can set the CMAKE_PREFIX_PATH, CMAKE_MODULE_PATH, and CMAKE_INCLUDE_PATH variables to help CMake find the package files. These variables specify additional locations to search for packages.
  4. Use the find_package command with VERBOSE: Add the VERBOSE option to the find_package command to display more information about the search process. This can help you identify where CMake is looking for the package files.
  5. Check CMake configuration files: The package you are trying to find may provide a CMake configuration file (e.g., packageNameConfig.cmake or packageNameConfigVersion.cmake). Make sure these files are installed in the correct location and are properly configured.
  6. Check environment variables: Ensure that your environment variables (e.g., PATH, LD_LIBRARY_PATH, CMAKE_PREFIX_PATH) are correctly set to point to the location of the package files.
  7. Check package installation: Verify that the package you are trying to find is installed correctly on your system. Check the installation path and make sure it contains the necessary files for CMake to locate the package.
  8. Use CMake’s graphical interface: If you are using an Integrated Development Environment (IDE) that supports CMake, such as Visual Studio or CLion, you can use their graphical interface to help debug find_package issues.

By following these steps and carefully examining the CMake configuration and package installation, you should be able to identify and resolve any issues with the find_package command in CMake.

How to use find_package in CMake for finding CUDA libraries?

To use find_package in CMake to find CUDA libraries, you need to add the following lines in your CMakeLists.txt file:

1
find_package(CUDA REQUIRED)

This will locate the CUDA installation on your system and set up the necessary variables for building with CUDA.

After calling find_package(CUDA REQUIRED), you can use the variables that it sets up to link against CUDA libraries in your project. For example, you can specify the CUDA libraries you want to link against using the CUDA_LIBRARIES variable:

1
target_link_libraries(your_target_name ${CUDA_LIBRARIES})

Make sure to include the necessary CUDA headers and set the appropriate compiler flags in your CMakeLists.txt file as well. You can do this using the include_directories and set commands:

1
2
include_directories(${CUDA_INCLUDE_DIRS})
set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} -gencode arch=compute_XX,code=sm_XX)  # Replace 'XX' with your desired CUDA compute capability

By following these steps, you can use find_package in CMake to find and link against CUDA libraries in your project.

Синтаксис

  • find_package (pkgname [версия] [EXACT] [QUIET] [ТРЕБУЕТСЯ])
  • включают в себя (FindPkgConfig)
  • pkg_search_module (префикс [ТРЕБУЕТСЯ] [QUIET] pkgname [otherpkg …])
  • pkg_check_modules (префикс [ТРЕБУЕТСЯ] [QUIET] pkgname [otherpkg …])

параметры

параметр подробности
версия (необязательно) Минимальная версия пакета, определяемая основным номером и необязательно небольшим, патчем и номером настройки, в формате major.minor.patch.tweak
EXACT (необязательно) Укажите, что версия, указанная в version является точной версией, которая будет найдена
ТРЕБУЕТСЯ (необязательно) Автоматически выдает ошибку и останавливает процесс, если пакет не найден
QUIET (необязательно) Функция не будет отправлять какое-либо сообщение на стандартный вывод

замечания

  • Способ find_package совместим со всей платформой, тогда как путь pkg-config доступен только на Unix-подобных платформах, таких как Linux и OSX.

  • Полное описание многочисленных параметров и опций find_package можно найти в руководстве .

  • Несмотря на то, что можно указать многие дополнительные параметры, такие как версия пакета, не все модули Find правильно используют все эти параметры. Если произойдет какое-либо неопределенное поведение, может потребоваться найти модуль в пути установки CMake и исправить или понять его поведение.

Используйте find_package и найдите .cmake модули

По умолчанию для поиска установленных пакетов с CMake используется функция find_package в сочетании с файлом Find<package>.cmake . Цель файла — определить правила поиска для пакета и установить разные переменные, такие как <package>_FOUND , <package>_INCLUDE_DIRS и <package>_LIBRARIES .

Многие файлы Find<package>.cmake уже определены по умолчанию в CMake. Однако, если нет ${CMAKE_SOURCE_DIR}/cmake/modules файла для пакета, вы всегда можете написать свой собственный и поместить его в ${CMAKE_SOURCE_DIR}/cmake/modules (или любой другой каталог, если CMAKE_MODULE_PATH был переопределен)

Список модулей по умолчанию можно найти в руководстве (v3.6) . Необходимо проверить руководство в соответствии с версией CMake, используемой в проекте, или же там могут отсутствовать модули. Также можно найти установленные модули с помощью cmake --help-module-list .

Хороший пример для FindSDL2.cmake на Github

Вот базовый CMakeLists.txt , который потребует SDL2:

cmake_minimum_required(2.8 FATAL_ERROR)
project("SDL2Test")

set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules")
find_package(SDL2 REQUIRED)

include_directories(${SDL2_INCLUDE_DIRS})
add_executable(${PROJECT_NAME} main.c)
target_link_libraries(${PROJECT_NAME} ${SDL2_LIBRARIES})

Используйте pkg_search_module и pkg_check_modules

В Unix-подобных операционных системах можно использовать программу pkg-config для поиска и настройки пакетов, которые предоставляют файл <package>.pc .

Чтобы использовать pkg-config , необходимо include(FindPkgConfig) в CMakeLists.txt . Тогда есть две возможные функции:

  • pkg_search_module , который проверяет пакет и использует первый доступный.
  • pkg_check_modules , которые проверяют все соответствующие пакеты.

Вот базовый CMakeLists.txt который использует pkg-config для поиска SDL2 с версией выше или равной 2.0.1:

cmake_minimum_required(2.8 FATAL_ERROR)
project("SDL2Test")

include(FindPkgConfig)
pkg_search_module(SDL2 REQUIRED sdl2>=2.0.1)

include_directories(${SDL2_INCLUDE_DIRS})
add_executable(${PROJECT_NAME} main.c)
target_link_libraries(${PROJECT_NAME} ${SDL2_LIBRARIES})

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Device tree compiler windows
  • Как зайти на удаленный рабочий стол windows xp
  • Не получается расширить экраны windows 10
  • Пользователь postgres не прошел проверку подлинности по паролю windows
  • Восстановление загрузчика системы windows 10