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 install
command 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».
Вам не нужно указывать путь модуля как таковой. CMake поставляется со своим собственным набором встроенных скриптов find_package, и их местоположение находится в CMAKE_MODULE_PATH по умолчанию.
Более обычный вариант использования для зависимых проектов, которые были CMakeified, состоял бы в том, чтобы использовать команду CMake external_project, а затем включить файл Use[Project].cmake из подпроекта. Если вам просто нужен скрипт Find[Project].cmake, скопируйте его из подпроекта и в исходный код вашего собственного проекта, и тогда вам не нужно будет дополнять CMAKE_MODULE_PATH, чтобы найти подпроект на системном уровне.