CMake: Для чего нужен find_package(), если вам все равно нужно указать CMAKE_MODULE_PATH?

Я пытаюсь заставить кросс-платформенную систему сборки работать с использованием CMake. Теперь у программного обеспечения есть несколько зависимостей. Я сам скомпилировал их и установил в моей системе.

Некоторые примеры файлов, которые были установлены:

-- Installing: /usr/local/share/SomeLib/SomeDir/somefile
-- Installing: /usr/local/share/SomeLib/SomeDir/someotherfile
-- Installing: /usr/local/lib/SomeLib/somesharedlibrary
-- Installing: /usr/local/lib/SomeLib/cmake/FindSomeLib.cmake
-- Installing: /usr/local/lib/SomeLib/cmake/HelperFile.cmake

Теперь CMake имеет find_package() который открывает Find*.cmake файл и ищет библиотеку в системе и определяет некоторые переменные, такие как SomeLib_FOUND и т.п.

Мой CMakeLists.txt содержит что-то вроде этого:

set(CMAKE_MODULE_PATH "/usr/local/lib/SomeLib/cmake/;${CMAKE_MODULE_PATH}")
find_package(SomeLib REQUIRED)

Первая команда определяет, где CMake ищет после Find*.cmake и я добавил каталог SomeLib где FindSomeLib.cmake можно найти, так find_package() работает как положено.

Но это странно, потому что одна из причин, почему find_package() существует, чтобы уйти от жестко закодированных путей, не являющихся кросс-платформенными.

Как это обычно делается? Должен ли я скопировать cmake/ каталог SomeLib в мой проект и установить CMAKE_MODULE_PATH относительно?

5 ответов

Решение

Команда find_package has two modes: Module режим и Config Режим. Вы пытаетесь использовать Module mode when you actually need Config Режим.

Module mode

Find<package>.cmake file located within your project. Что-то вроде этого:

CMakeLists.txt
cmake/FindFoo.cmake
cmake/FindBoo.cmake

CMakeLists.txt содержание:

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
find_package(Foo REQUIRED) # FOO_INCLUDE_DIR, FOO_LIBRARIES
find_package(Boo REQUIRED) # BOO_INCLUDE_DIR, BOO_LIBRARIES

include_directories("${FOO_INCLUDE_DIR}")
include_directories("${BOO_INCLUDE_DIR}")
add_executable(Bar Bar.hpp Bar.cpp)
target_link_libraries(Bar ${FOO_LIBRARIES} ${BOO_LIBRARIES})

Обратите внимание, что CMAKE_MODULE_PATH has high priority and may be usefull when you need to rewrite standard Find<package>.cmake файл.

Config mode (install)

<package>Config.cmake file located outside and produced by installcommand of other project (Foo например).

foo библиотека:

> cat CMakeLists.txt 
cmake_minimum_required(VERSION 2.8)
project(Foo)

add_library(foo Foo.hpp Foo.cpp)
install(FILES Foo.hpp DESTINATION include)
install(TARGETS foo DESTINATION lib)
install(FILES FooConfig.cmake DESTINATION lib/cmake/Foo)

Simplified version of config file:

> cat FooConfig.cmake 
add_library(foo STATIC IMPORTED)
find_library(FOO_LIBRARY_PATH foo HINTS "${CMAKE_CURRENT_LIST_DIR}/../../")
set_target_properties(foo PROPERTIES IMPORTED_LOCATION "${FOO_LIBRARY_PATH}")

By default project installed in CMAKE_INSTALL_PREFIX каталог:

> cmake -H. -B_builds
> cmake --build _builds --target install
-- Install configuration: ""
-- Installing: /usr/local/include/Foo.hpp
-- Installing: /usr/local/lib/libfoo.a
-- Installing: /usr/local/lib/cmake/Foo/FooConfig.cmake

Config mode (use)

использование find_package(... CONFIG) включать FooConfig.cmake with imported target foo:

> cat CMakeLists.txt 
cmake_minimum_required(VERSION 2.8)
project(Boo)

# import library target `foo`
find_package(Foo CONFIG REQUIRED)

add_executable(boo Boo.cpp Boo.hpp)
target_link_libraries(boo foo)
> cmake -H. -B_builds -DCMAKE_VERBOSE_MAKEFILE=ON
> cmake --build _builds
Linking CXX executable Boo
/usr/bin/c++ ... -o Boo /usr/local/lib/libfoo.a

Note that imported target is highly configurable. See my answer.

Обновить

Если вы работаете cmake чтобы генерировать SomeLib себя (скажем, как часть суперстройки), рассмотрите возможность использования реестра пакетов пользователей. Это не требует жестко закодированных путей и является кроссплатформенным. В Windows (включая mingw64) он работает через реестр. Если вы посмотрите, как список префиксов установки создается CONFIG В режиме команды find_packages() вы увидите, что реестр пользовательских пакетов является одним из элементов.

Краткое руководство

Связать цели SomeLib что вам нужно за пределами этого внешнего проекта, добавив их в набор экспорта в CMakeLists.txt файлы, в которых они созданы:

add_library(thingInSomeLib ...)
install(TARGETS thingInSomeLib Export SomeLib-export DESTINATION lib)

Создать XXXConfig.cmake файл для SomeLib в его ${CMAKE_CURRENT_BUILD_DIR} и сохраните это местоположение в реестре пакетов пользователей, добавив два вызова к export() к CMakeLists.txt связаны с SomeLib:

export(EXPORT SomeLib-export NAMESPACE SomeLib:: FILE SomeLibConfig.cmake) # Create SomeLibConfig.cmake
export(PACKAGE SomeLib)                                                    # Store location of SomeLibConfig.cmake

Выдать свой find_package(SomeLib REQUIRED) командовать в CMakeLists.txt файл проекта, который зависит от SomeLib без "непросплатформенных жестко закодированных путей", CMAKE_MODULE_PATH,

Когда это может быть правильный подход

Этот подход, вероятно, лучше всего подходит для ситуаций, когда вы никогда не будете использовать свое программное обеспечение ниже каталога сборки (например, вы кросс-компилируете и никогда ничего не устанавливаете на своем компьютере, или вы создаете программное обеспечение только для запуска тестов в каталог сборки), поскольку он создает ссылку на файл.cmake в выходных данных "сборки", которые могут быть временными.

Но если вы никогда не устанавливаете SomeLib в вашем рабочем процессе, звоня EXPORT(PACKAGE <name>) позволяет избежать жестко закодированного пути. И, конечно же, если вы устанавливаете SomeLibвы, вероятно, знаете свою платформу, CMAKE_MODULE_PATHи т. д., поэтому отличный ответ @user2288008 поможет вам.

Как это обычно делается? Должен ли я скопировать cmake/ каталог SomeLib в мой проект и установить CMAKE_MODULE_PATH относительно?

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

Обратите внимание, что FindFoo.cmake каждый из модулей является своего рода мостом между зависимостью от платформы и независимостью от платформы - они ищут в различных местах, зависящих от платформы, чтобы найти пути в переменных, имена которых не зависят от платформы.

По сути, это механизм поиска библиотеки, ее заголовочных файлов и т. д., все в одном удобном методе.

Как следует из названия, find_package лучше всего работает с менеджерами пакетов. Илиsystem package managersилиlanguage package managers.

  • системные менеджеры пакетов:APT,yum, и т. д.
  • менеджеры языковых пакетов:vcpkg,conan, и т. д.

упрощает использование библиотеки из менеджера пакетов.

Вот пошаговый пример использования очень популярной библиотеки C++ (https://github.com/fmtlib/fmt ).

1.) Установите библиотеку

      # Note I'm using APT but it doesn't really matter
sudo apt install libfmt-dev

2.) Найдите библиотеку в своемCMakeLists.txt

      project(example_project)

add_executable(foobar PRIVATE main.cpp)

# Find your library and link to it.
find_package(fmt CONFIG REQUIRED)
target_link_libraries(foobar PRIVATE fmt::fmt)

Теперь вам должно быть хорошо идти. Если вы используетеsystemменеджер пакетов.

Это потому чтоfind_*вызовы в CMake проверяют системные местоположения по умолчанию.

Чтобы узнать, как CMake нашел ваш пакет, вы можете использовать--debug-find-pkg.

cmake -S . -B build --debug-find-pkg=fmt --fresh

Здесь было показано все места, где CMake ищет файл с именемfmt-config.cmake.

На моей машине это было найдено здесь:

      /usr/lib/x86_64-linux-gnu/cmake/fmt/fmt-config.cmake

--debug-find— ОЧЕНЬ полезный инструмент для выяснения того, как CMake что-то ищет.

А как насчет несистемных менеджеров пакетов?

Существуют и другие способы описания расположения пакетов. Очень распространенным является изменениеCMAKE_PREFIX_PATH.

Из документации

CMAKE_PREFIX_PATH — это список каталогов, разделенных точкой с запятой, определяющий установочные префиксы, которые должны быть найдены с помощью команд find_package(), find_program(), find_library(), find_file() и find_path(). Каждая команда добавит соответствующие подкаталоги (например, bin, lib или include), как указано в ее собственной документации. По умолчанию CMAKE_PREFIX_PATH пуст. Предполагается, что это будет установлено проектом.

Менеджеры пакетов, такие как vcpkg, сделают это автоматически. Делаем это так же легко, как если бы вы использовали системный менеджер пакетов.

Поиск конфигурации и модуля

Теперь, как намекает ваш первоначальный вопрос, существует некоторая путаница в поиске вещей.

Есть 2 основных режима поиска вещей.

  • режим

В этом режиме CMake ищет файл с именем Find.cmake, сначала просматривая местоположения, перечисленные в CMAKE_MODULE_PATH, а затем среди модулей поиска, предоставленных установкой CMake.

  • режим

В этом режиме CMake ищет файл с именем -config.cmake или Config.cmake.

Теперь, как вы могли заметить, выше приведен пример используемого режима.

Configрежим обычно является желательным решением, ЕСЛИ он доступен. Так как это по сути не требует от вас никаких усилий. Режим требует написания модуля CMake, который поможет вам находить нужные вещи.

CMake на самом деле имеет несколько встроенныхModuleпомощники режима.

https://cmake.org/cmake/help/latest/manual/cmake-modules.7.html#find-modules

Как модуль FindPython3. Вот небольшой пример

      find_package(Python3 REQUIRED)
message(STATUS "${Python3_EXECUTABLE}")

Как показано в этом примере Python,find_packageВ более общем смысле это способ найти несколько файлов, которые принадлежат друг другу, и упростить их использование.

ПРИМЕЧАНИЕ:

Поскольку эти модули встроены, вам не нужно изменять CMAKE_MODULE_PATH. Они вам уже предоставлены. Конечно, если вам нужно, вы можете сделать это самостоятельно.

Заключительные замечания

Я надеюсь, что мой ответ будет более проясняющим, чем запутанным, и поможет вам лучше понять find_package.

Для получения более подробной информации о поиске поведения в CMake я рекомендую книгу Крейга Скотта «Профессиональный CMake».

https://crascit.com/professional-cmake/

Вам не нужно указывать путь модуля как таковой. CMake поставляется со своим собственным набором встроенных скриптов find_package, и их местоположение находится в CMAKE_MODULE_PATH по умолчанию.

Более обычный вариант использования для зависимых проектов, которые были CMakeified, состоял бы в том, чтобы использовать команду CMake external_project, а затем включить файл Use[Project].cmake из подпроекта. Если вам просто нужен скрипт Find[Project].cmake, скопируйте его из подпроекта и в исходный код вашего собственного проекта, и тогда вам не нужно будет дополнять CMAKE_MODULE_PATH, чтобы найти подпроект на системном уровне.

Другие вопросы по тегам