Spec-Zone.ru › CMake 3.26

Руководство по использованию зависимостей

  • Введение
  • Использование предварительно скомпилированных пакетов с find_package()

    • Пакеты с конфигурационными файлами
    • Поиск файлов модулей
    • Импортированные целевые объекты
  • Загрузка и компиляция из исходного кода с помощью FetchContent
  • FetchContent и find_package() интеграция
  • Поставщики зависимостей

Введение

Проекты часто зависят от других проектов, ресурсов и артефактов. CMake предоставляет несколько способов включения таких элементов в сборку. Проекты и пользователи имеют гибкость выбора методов, наилучшим образом подходящих для их потребностей.

Основными методами включения зависимостей в сборку являются команда find_package() и модуль FetchContent. Модуль FindPkgConfig также иногда используется, хотя он лишен некоторых функций интеграции с другими двумя модулями и более подробно не рассматривается в этом руководстве.

Зависимости также могут быть доступны с помощью пользовательского поставщика зависимостей. Это может быть сторонний менеджер пакетов или пользовательский код, реализованный разработчиком. Поставщики зависимостей взаимодействуют с основными методами, упомянутыми выше, для расширения их гибкости.

Использование предварительно скомпилированных пакетов с find_package()

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

В большинстве случаев проекты должны использовать базовую сигнатуру. Чаще всего это включает имя пакета, возможно, ограничение по версии, и ключевое слово REQUIRED, если зависимость является необязательной. Также может быть указан набор компонентов пакета.

Примеры find_package() базовой сигнатуры
find_package(Catch2)
find_package(GTest REQUIRED)
find_package(Boost 1.79 COMPONENTS date_time)

Команда find_package() поддерживает два основных метода поиска:

Режим конфигурации

С этим методом команда ищет файлы, обычно предоставляемые самим пакетом. Это более надёжный метод из двух, так как детали пакета всегда должны соответствовать самому пакету.

Режим модуля

Не все пакеты совместимы с CMake. Многие не предоставляют файлы, необходимые для поддержки режима конфигурации. В таких случаях можно предоставить отдельный файл модуля Find, либо проектом, либо CMake. Модуль Find представляет собой обычно эвристическое решение, которое знает, что обычно предоставляет пакет и как представить этот пакет проекту. Поскольку модули Find обычно распространяются отдельно от пакета, они менее надёжны. Обычно они поддерживаются отдельно и, вероятно, следуют различным графикам выпуска, поэтому могут легко устаревать.

В зависимости от используемых аргументов, find_package() может использовать один или оба метода. Ограничивая опции только базовой сигнатурой, оба режима поиска (конфигурации и модуля) могут быть использованы для удовлетворения зависимости. Наличие других опций может ограничить вызов только одним из двух методов, потенциально уменьшая способность команды находить зависимость. Обратитесь к документации find_package() для получения подробной информации об этой сложной теме.

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

Пакеты с конфигурационными файлами

Предпочтительный способ предоставления сторонними разработчиками исполняемых файлов, библиотек, заголовков и других файлов для использования с CMake — это предоставление конфигурационных файлов. Это текстовые файлы, поставляемые с пакетом, которые определяют целевые объекты CMake, переменные, команды и т. д. Файл конфигурации — это обычный скрипт CMake, который считывается командой find_package().

Конфигурационные файлы обычно находятся в каталоге, имя которого соответствует шаблону lib/cmake/<PackageName>, хотя они могут находиться и в других местах (см. Процедура поиска в режиме конфигурации). <PackageName> обычно является первым аргументом команды find_package(), а может и единственным. Альтернативные имена также можно указать с помощью опции NAMES

Предоставление альтернативных имен при поиске пакета
find_package(SomeThing
  NAMES
    SameThingOtherName   # Another name for the package
    SomeThing            # Also still look for its canonical name
)

Файл конфигурации должен иметь имя <PackageName>Config.cmake или <LowercasePackageName>-config.cmake (первый используется в остальной части данного руководства, но оба поддерживаются). Этот файл является точкой входа в пакет для CMake. В том же каталоге также может существовать отдельный необязательный файл с именем <PackageName>ConfigVersion.cmake или <LowercasePackageName>-config-version.cmake. Этот файл используется CMake для определения, удовлетворяет ли версия пакета какому-либо ограничению версии, указанному в вызове find_package(). Указывать версию при вызове find_package() необязательно, даже если файл <PackageName>ConfigVersion.cmake присутствует.

Если файл <PackageName>Config.cmake найден и удовлетворяет всем ограничениям по версии, команда find_package() считает пакет найденным, и весь пакет предполагается полным по проекту.

Возможно, существуют дополнительные файлы, предоставляющие команды CMake или импортированные целевые объекты для использования. CMake не навязывает каких-либо правил именования для этих файлов. Они связаны с основным файлом <PackageName>Config.cmake с помощью команды CMake include(). Файл <PackageName>Config.cmake обычно включает их для вас, поэтому, как правило, не требуется дополнительных шагов помимо вызова find_package().

Если расположение пакета находится в каталоге, известном CMake, вызов find_package() должен завершиться успешно. Каталоги, известные CMake, зависят от платформы. Например, пакеты, установленные на Linux с помощью стандартного менеджера пакетов, будут находиться в префиксе /usr автоматически. Пакеты, установленные в Program Files на Windows, будут находиться аналогично автоматически.

Пакеты не будут находиться автоматически без помощи, если они находятся в местах, неизвестных CMake, таких как /opt/mylib или $HOME/dev/prefix. Это нормальная ситуация, и CMake предоставляет несколько способов для пользователей указать, где найти такие библиотеки.

END_OF_DOCUMENT_MARKER

Переменная CMAKE_PREFIX_PATH может быть установлена при вызове CMake. Она обрабатывается как список базовых путей, в которых следует искать файлы конфигурации. Установленный пакет в /opt/somepackage обычно устанавливает файлы конфигурации, такие как /opt/somepackage/lib/cmake/somePackage/SomePackageConfig.cmake. В этом случае /opt/somepackage необходимо добавить в CMAKE_PREFIX_PATH.

Переменная среды CMAKE_PREFIX_PATH также может быть заполнена префиксами для поиска пакетов. Как и переменная среды PATH, это список, но ему требуется использовать разделитель элементов списка для платформы (: в Unix и ; в Windows).

Переменная CMAKE_PREFIX_PATH обеспечивает удобство в случаях, когда необходимо указать несколько префиксов или когда несколько пакетов доступны под одним префиксом. Пути к пакетам также могут быть указаны путём установки переменных, соответствующих <PackageName>_DIR, таких как SomePackage_DIR. Обратите внимание, что это не префикс, а полный путь к каталогу, содержащему файл конфигурации пакета, например, /opt/somepackage/lib/cmake/SomePackage в приведённом выше примере. См. документацию find_package() для получения информации о других переменных CMake и переменных среды, которые могут влиять на поиск.

Файлы модулей поиска

Пакеты, которые не предоставляют файлы конфигурации, всё ещё могут быть найдены с помощью команды find_package(), если доступен файл FindSomePackage.cmake. Эти файлы модулей поиска отличаются от файлов конфигурации тем, что:

  1. Файлы модулей поиска не должны предоставляться самим пакетом.
  2. Доступность файла Find<PackageName>.cmake не указывает на доступность пакета или какой-либо его части.
  3. CMake не ищет места, указанные в переменной CMAKE_PREFIX_PATH для файлов Find<PackageName>.cmake. Вместо этого CMake ищет такие файлы в местах, указанных в переменной CMAKE_MODULE_PATH. Пользователи обычно устанавливают переменную CMAKE_MODULE_PATH при запуске CMake, и проекты CMake обычно добавляют в CMAKE_MODULE_PATH для использования локальных файлов модулей поиска.
  4. CMake поставляется с файлами Find<PackageName>.cmake для некоторых third party packages. Эти файлы являются обременительными для обслуживания CMake, и часто они отстают от последних версий пакетов, с которыми связаны. В общем, новые файлы модулей поиска больше не добавляются в CMake. Проекты должны поощрять поставщиков пакетов к предоставлению файла конфигурации, когда это возможно. Если это не удаётся, проект должен предоставить свой собственный модуль поиска для пакета.

См. Модули поиска для подробного обсуждения написания файла модуля поиска.

Импортированные цели

Файлы конфигурации и файлы модулей поиска могут определять импортированные цели. Обычно они имеют имена вида SomePrefix::ThingName. Если они доступны, проект должен предпочесть их использовать вместо любых переменных CMake, которые также могут быть предоставлены. Такие цели обычно имеют требования к использованию и автоматически применяют такие вещи, как пути поиска заголовков, определения компилятора и т. д. к другим целям, которые с ними связаны (например, с помощью target_link_libraries()). Это более надёжно и удобнее, чем попытка вручную применить те же вещи с помощью переменных. Проверьте документацию пакета или модуля поиска, чтобы узнать, какие импортированные цели он определяет, если таковые имеются.

Импортированные цели также должны оборачивать все пути, специфичные для конфигурации. Это включает в себя местоположение двоичных файлов (библиотек, исполняемых файлов), флаги компилятора и любые другие зависящие от конфигурации величины. Модули поиска могут быть менее надёжны в предоставлении этих деталей, чем файлы конфигурации.

Полный пример, который находит сторонний пакет и использует библиотеку из него, может выглядеть следующим образом:

cmake_minimum_required(VERSION 3.10)
project(MyExeProject VERSION 1.0.0)

# Make project-provided Find modules available
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

find_package(SomePackage REQUIRED)
add_executable(MyExe main.cpp)
target_link_libraries(MyExe PRIVATE SomePrefix::LibName)

Обратите внимание, что вышеупомянутый вызов find_package() может быть разрешён файлом конфигурации или модулем поиска. Он использует только основные аргументы, поддерживаемые Основным сигнатурой. Файл FindSomePackage.cmake в каталоге ${CMAKE_CURRENT_SOURCE_DIR}/cmake позволил бы команде find_package() успешно выполнить поиск в режиме модуля, например. Если такой файл модуля отсутствует, система будет искать файл конфигурации.

Загрузка и компиляция из исходного кода с FetchContent

Зависимости не обязательно должны быть предварительно скомпилированы, чтобы использовать их с CMake. Их можно скомпилировать из исходного кода в рамках основного проекта. Модуль FetchContent предоставляет функциональность для загрузки контента (как правило, исходного кода, но может быть что угодно) и добавления его в основной проект, если зависимость также использует CMake. Исходный код зависимости будет скомпилирован вместе со всем остальным проектом, точно так же, как будто этот исходный код был частью собственных исходных кодов проекта.

Общая схема заключается в том, что проект сначала объявляет все зависимости, которые он хочет использовать, а затем просит сделать их доступными. Следующее демонстрирует принцип (см. Примеры для получения дополнительной информации):

include(FetchContent)
FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        703bd9caab50b139428cea1aaff9974ebee5742e # release-1.10.0
)
FetchContent_Declare(
  Catch2
  GIT_REPOSITORY https://github.com/catchorg/Catch2.git
  GIT_TAG        605a34765aa5d5ecbf476b4598a862ada971b0cc # v3.0.1
)
FetchContent_MakeAvailable(googletest Catch2)

Поддерживаются различные методы загрузки, включая загрузку и извлечение архивов из URL (поддерживается ряд форматов архивов), а также ряд форматов репозиториев, включая Git, Subversion и Mercurial. Также можно использовать пользовательские команды для загрузки, обновления и патчинга для поддержки произвольных случаев использования.

Когда зависимость добавляется в проект с помощью FetchContent, проект связывается с целями зависимости, как с любой другой целью проекта. Если зависимость предоставляет целевые имена с пространством имён в форме SomePrefix::ThingName, проект должен связываться с этими именами, а не с любыми целевыми именами без пространства имён. См. следующий раздел, чтобы понять, почему это рекомендуется.

Не все зависимости можно добавить в проект таким образом. Некоторые зависимости определяют цели, имена которых конфликтуют с другими целями проекта или других зависимостей. Конкретные исполняемые и библиотечные цели, созданные с помощью add_executable() и add_library(), являются глобальными, поэтому каждое из них должно быть уникальным во всем процессе сборки. Если зависимость добавит конфликтующее имя цели, её нельзя напрямую включить в сборку этим методом.

FetchContent и find_package() интеграция

Новое в версии 3.24.

Некоторые зависимости поддерживают добавление как с помощью find_package(), так и с помощью FetchContent. Такие зависимости должны гарантировать, что они определяют те же целевые имена с пространством имён как в установленном, так и в случае сборки из исходного кода. Проект-потребитель, затем, связывает эти целевые имена с пространством имён и может обрабатывать оба сценария прозрачно, если проект не использует ничего другого, чего нет в обоих методах.

Проект может указать, что он рад принять зависимость любым из двух методов, используя опцию FIND_PACKAGE_ARGS для FetchContent_Declare(). Это позволяет FetchContent_MakeAvailable() сначала попробовать удовлетворить зависимость вызовом find_package(), используя аргументы после ключевого слова FIND_PACKAGE_ARGS, если таковые имеются. Если это не находит зависимость, она собирается из исходного кода, как описано ранее.

include(FetchContent)
FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        703bd9caab50b139428cea1aaff9974ebee5742e # release-1.10.0
  FIND_PACKAGE_ARGS NAMES GTest
)
FetchContent_MakeAvailable(googletest)

add_executable(ThingUnitTest thing_ut.cpp)
target_link_libraries(ThingUnitTest GTest::gtest_main)

В приведенном выше примере сначала вызывается find_package(googletest NAMES GTest). CMake предоставляет модуль FindGTest, поэтому если он найдёт установленный пакет GTest где-либо, он сделает его доступным, и зависимость не будет построена из исходного кода. Если пакет GTest не найден, он будет построен из исходного кода. В любом случае, ожидается, что целевой объект GTest::gtest_main будет определён, поэтому мы связываем наш исполняемый файл модульных тестов с этим целевым объектом.

Также доступен высокоуровневый контроль через переменную FETCHCONTENT_TRY_FIND_PACKAGE_MODE. Её можно установить в значение NEVER для отключения всех перенаправлений на find_package(). Её можно установить в значение ALWAYS для попытки использования find_package(), даже если FIND_PACKAGE_ARGS не было указано (это следует использовать с осторожностью).

Проект также может принять решение о том, что определённая зависимость должна быть построена из исходного кода. Это может потребоваться, если требуется исправленная или неопубликованная версия зависимости, или для удовлетворения политики, требующей построения всех зависимостей из исходного кода. Проект может принудительно выполнить это, добавив ключевое слово OVERRIDE_FIND_PACKAGE в FetchContent_Declare(). Вызов find_package() для этой зависимости затем будет перенаправлен на FetchContent_MakeAvailable().

include(FetchContent)
FetchContent_Declare(
  Catch2
  URL https://intranet.mycomp.com/vendored/Catch2_2.13.4_patched.tgz
  URL_HASH MD5=abc123...
  OVERRIDE_FIND_PACKAGE
)

# The following is automatically redirected to FetchContent_MakeAvailable(Catch2)
find_package(Catch2)

Для более сложных случаев использования см. переменную CMAKE_FIND_PACKAGE_REDIRECTS_DIR.

Обзор поставщиков зависимостей

Новое в версии 3.24.

В предыдущем разделе обсуждались методы, которые проекты могут использовать для указания своих зависимостей. В идеале, проект не должен сильно заботиться о том, откуда исходит зависимость, как только она предоставляет ожидаемые элементы (часто просто некоторые импортированные целевые объекты). Проект указывает свои потребности и может также указать, откуда их получить, при отсутствии других деталей, чтобы его можно было по-прежнему создавать автономно.

С другой стороны, разработчик может быть гораздо больше заинтересован в управлении способом предоставления зависимости проекту. Возможно, вы захотите использовать определённую версию пакета, которую вы создали сами. Вы можете захотеть использовать сторонний менеджер пакетов. Вы можете захотеть перенаправить некоторые запросы на другой URL на управляемом вами сервере по соображениям безопасности или производительности. CMake поддерживает такие сценарии с помощью Поставщиков зависимостей.

Поставщик зависимостей может быть настроен для перехвата вызовов find_package() и FetchContent_MakeAvailable(). Поставщику предоставляется возможность удовлетворить такие запросы до возврата к встроенной реализации, если поставщик не выполнит это.

Может быть установлен только один поставщик зависимостей, и он может быть установлен только в очень определённый момент в начале выполнения CMake. Переменная CMAKE_PROJECT_TOP_LEVEL_INCLUDES перечисляет файлы CMake, которые будут читаться при обработке первого вызова project() (и только этого вызова). Это единственный момент, когда может быть установлен поставщик зависимостей. В проекте в целом ожидается использование не более одного поставщика.

В некоторых сценариях пользователю не нужно будет знать подробности о том, как настроен поставщик зависимостей. Сторонний разработчик может предоставить файл, который можно добавить в CMAKE_PROJECT_TOP_LEVEL_INCLUDES, что настроит поставщика зависимостей от имени пользователя. Это рекомендуемый подход для менеджеров пакетов. Разработчик может использовать такой файл следующим образом:

cmake -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=/path/to/package_manager/setup.cmake ...

Подробные сведения о том, как реализовать собственного пользовательского поставщика зависимостей, см. в команде cmake_language(SET_DEPENDENCY_PROVIDER).

© 2000–2023 Kitware, Inc. and Contributors
Licensed under the BSD 3-clause License.
https://cmake.org/cmake/help/v3.26/guide/using-dependencies/index.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API