Какой смысл использовать find_package (), если вам все равно нужно указать CMAKE_MODULE_PATH?

167

Я пытаюсь заставить кросс-платформенную систему сборки работать с использованием 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относительно?

MarcDefiant
источник
Эта модель кажется мне очень странной. Библиотеки, использующие CMake, не должны выставлять свой модуль 'find' таким образом. Как вы нашли такой способ найти «SomeLib»? И что это за библиотека?
SirDarius
2
Нечто подобное сделано в cmake.org/Wiki/… . И это ОГРЕ.
MarcDefiant
2
Раздел, на который вы ссылаетесь, упоминает это: «Так как CMake (в настоящее время) не отправляет его, вам придется отправлять его в рамках вашего проекта». Это то, что я сделал во flvmeta, чтобы найти LibYAML (см. Github.com/noirotm/flvmeta/tree/master/cmake/modules ). Путь к модулю указывает на этот каталог внутри моего проекта.
SirDarius
3
Я обычно копирую модули FindXXX в свой проект и устанавливаю CMAKE_MODULE_PATH (если эти модули, конечно, не представлены в CMake), я также видел этот шаблон много раз в других проектах
szx

Ответы:

214

Команда find_packageимеет два режима: Moduleрежим и Configрежим. Вы пытаетесь использовать Moduleрежим, когда вам действительно нужен Configрежим.

Модульный режим

Find<package>.cmakeфайл находится в вашем проекте. Что-то вроде этого:

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имеет высокий приоритет и может быть полезно, когда вам нужно переписать стандартный Find<package>.cmakeфайл.

Режим настройки (установка)

<package>Config.cmakeфайл находится снаружи и создается по install команде другого проекта ( 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)

Упрощенная версия конфигурационного файла:

> 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}")

По умолчанию проект установлен в 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

Режим конфигурации (использование)

Используйте find_package(... CONFIG)для включения FooConfig.cmakeс импортированной целью 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

Обратите внимание, что импортируемая цель легко настраивается. Смотри мой ответ .

Обновить

Сообщество
источник
1
Ваш ответ великолепен. Однако пример на github более сложный, чем IMO. В общем случае, когда подкаталог (модуль) экспортирует один артефакт, скажем, lib вместе с заголовками, вам не нужно генерировать пользовательский * Config.cmake. В результате конфигурация может быть значительно сокращена. Я думаю, я сделаю аналогичный пример сам.
Димитрис
2
@Dimitris Да, это можно немного упростить. Я обновил пример GitHub, так что теперь он не использует configure_package_config_file. Кстати, если у вас есть какие-либо другие предложения, вы можете отправить мне запрос.
1
@rusio Вот мой пример . Он поддерживает монолитную сборку (все модули из корневой папки) или автономную сборку (каждый модуль отдельно требует установки).
Димитрис
1
@Dimitris Хорошо, теперь я вижу. Обычно файл, который вы «оптимизируете», служит для загрузки дополнительных вещей, таких как find_dependency . Я думаю, что это хороший шаблон для запуска, поэтому я буду держать его, даже если он на самом деле не используется. Остальная часть кода выглядит проще, потому что вам не хватает некоторых функций, таких как версия, экспорт для dll, макетирование с помощью bin/lib(попробуйте установить исполняемый файл и запустить его в Windows). И пространства имен выглядят очень красиво, поэтому я тоже буду их сохранять :) Также я добавил monolithicсборку.
1
Каждый из ваших примеров был очень полезен для меня. Спасибо вам обоим!
Zmb
2

Если вы работаете 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}и сохранить это место в реестре пользоваегося пакета путем добавления двух вызовов на экспорт () к 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 поможет вам.

Райан Фили
источник
1

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

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

zjm555
источник
12
their location is in the default CMAKE_MODULE_PATHпо умолчанию CMAKE_MODULE_PATHпусто
Может подтвердить комментарий @ user2288008 в 2018. В CMAKE_MODULE_PATHWindows пусто.
Йерун
Это специфичная для проекта переменная для модулей, поставляемых вместе с вашим проектом. «По умолчанию он пуст, он предназначен для установки проектом». cmake.org/cmake/help/latest/variable/CMAKE_MODULE_PATH.html
Farway
1

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

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

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

einpoklum
источник