Spec-Zone.ru › CMake 3.29

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

  • Введение
  • Использование готовых пакетов с 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. Многие не предоставляют файлы, необходимые для поддержки режима конфигурации. В таких случаях может предоставляться отдельный файл модуля поиска, либо проектом, либо CMake. Модуль поиска обычно представляет собой эвристическое решение, которое знает, что обычно предоставляет пакет, и как представить этот пакет проекту. Поскольку модули поиска обычно распространяются отдельно от пакета, они менее надежны. Они обычно поддерживаются отдельно и, вероятно, будут следовать различным графикам выпуска, поэтому они могут легко устареть.

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

Переменная 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–2024 Kitware, Inc. and Contributors
Licensed under the BSD 3-clause License.
https://cmake.org/cmake/help/v3.29/guide/using-dependencies/index.html

Spec-Zone.ru

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