Руководство по использованию зависимостей
- Введение
- Загрузка и сборка из исходного кода с помощью
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. В таком случае в CMAKE_PREFIX_PATH следует добавить /opt/somepackage.
Переменную среды CMAKE_PREFIX_PATH также можно заполнить префиксами для поиска пакетов. Как и переменная среды PATH, она содержит список, но в ней необходимо использовать платформенный разделитель элементов списка переменных среды (: в Unix и ; в Windows).
Переменная CMAKE_PREFIX_PATH удобна, когда нужно указать несколько префиксов или когда в одном префиксе доступно несколько пакетов. Пути к пакетам также можно указать, задав переменные, соответствующие шаблону <PackageName>_DIR, например SomePackage_DIR. Обратите внимание: это не префикс, а полный путь к каталогу, содержащему файл пакета в формате конфигурации, например /opt/somepackage/lib/cmake/SomePackage в приведенном выше примере. Сведения о других переменных CMake и переменных среды, влияющих на поиск, см. в документации команды find_package().
Файлы модулей поиска
Пакеты, не предоставляющие файлы конфигурации, все равно можно найти с помощью команды find_package(), если доступен файл FindSomePackage.cmake. Эти файлы модулей поиска отличаются от файлов конфигурации следующим:
- Файлы модулей поиска не должны предоставляться самим пакетом.
- Наличие файла
Find<PackageName>.cmakeне означает, что пакет или какая-либо его часть доступны. - CMake не выполняет поиск файлов
Find<PackageName>.cmakeв расположениях, указанных переменнойCMAKE_PREFIX_PATH. Вместо этого CMake ищет такие файлы в расположениях, заданных переменнойCMAKE_MODULE_PATH. Пользователи часто задаютCMAKE_MODULE_PATHпри запуске CMake, а проекты CMake часто дополняютCMAKE_MODULE_PATH, чтобы разрешить использование локальных файлов модулей поиска. - 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–2026 Kitware, Inc. and Contributors
Licensed under the BSD 3-clause License.
https://cmake.org/cmake/help/latest/guide/using-dependencies/index.html