Spec-Zone.ru › CMake 3.15

FetchContent

  • Обзор
  • Объявление деталей содержимого
  • Заполнение содержимого
  • Примеры

Обзор

Этот модуль позволяет заполнять содержимое во время конфигурации с помощью любого метода, поддерживаемого модулем ExternalProject. В то время как ExternalProject_Add() загружает содержимое во время сборки, модуль FetchContent делает содержимое доступным немедленно, позволяя шагу конфигурации использовать содержимое в командах, таких как add_subdirectory(), include() или операциях file().

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

Ниже показан типичный пример объявления деталей содержимого:

FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        release-1.8.0
)

В большинстве типичных случаев заполнение содержимого можно выполнить одной командой, как показано ниже:

FetchContent_MakeAvailable(googletest)

Вышеупомянутая команда не только заполняет содержимое, но и добавляет его в основную сборку (если это возможно), чтобы основная сборка могла использовать цели заполненного проекта и т.д. В некоторых случаях основной проект может потребовать более точного управления заполнением или должен явно определить шаги заполнения (например, если необходимо поддерживать версии CMake, предшествующие 3.14). Типичная структура таких пользовательских шагов выглядит так:

FetchContent_GetProperties(googletest)
if(NOT googletest_POPULATED)
  FetchContent_Populate(googletest)
  add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR})
endif()

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

Модуль FetchContent также поддерживает определение и заполнение содержимого в одном вызове, без проверки того, было ли содержимое уже заполнено в другом месте проекта. Это операция более низкого уровня и обычно не используется, но иногда полезна при реализации какой-либо функции более высокого уровня или для заполнения некоторого содержимого в режиме сценариев CMake.

Объявление деталей содержимого

FetchContent_Declare
FetchContent_Declare(<name> <contentOptions>...)

Функция FetchContent_Declare() записывает параметры, описывающие способ заполнения указанного содержимого, но если такие детали уже были записаны ранее в этом проекте (независимо от того, где в иерархии проекта), этот и все последующие вызовы для того же содержимого <name> игнорируются. Этот подход «первым записал, победил» позволяет иерархическим проектам родительским проектам переопределять детали содержимого дочерних проектов.

Содержимое <name> может быть любой строкой без пробелов, но хорошей практикой является использование только букв, цифр и символов подчеркивания. Имя будет обрабатываться без учета регистра, и оно должно быть очевидным для представляемого содержимого, часто являясь именем дочернего проекта или значением, заданным для его команды верхнего уровня project() (если это проект CMake). Для известных публичных проектов имя должно, как правило, совпадать с официальным именем проекта. Выбор необычного имени делает маловероятным, что другие проекты, нуждающиеся в том же содержимом, будут использовать то же имя, что приведет к многократному заполнению содержимого.

Параметр <contentOptions> может быть любым из параметров загрузки или обновления/патча, которые понимает команда ExternalProject_Add(). Шаги конфигурации, сборки, установки и тестирования явным образом отключены, поэтому связанные с ними параметры будут игнорироваться. В большинстве случаев <contentOptions> будет всего лишь несколько параметров, определяющих метод загрузки и детали метода, такие как тег коммита или хэш архива. Например:

FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        release-1.8.0
)

FetchContent_Declare(
  myCompanyIcons
  URL      https://intranet.mycompany.com/assets/iconset_1.12.tar.gz
  URL_HASH 5588a7b18261c20068beabfb4f530b87
)

FetchContent_Declare(
  myCompanyCertificates
  SVN_REPOSITORY svn+ssh://svn.mycompany.com/srv/svn/trunk/certs
  SVN_REVISION   -r12345
)

Заполнение содержимого

В большинстве распространенных случаев заполнение означает предоставление содержимого основной сборке в соответствии с ранее объявленными деталями этой зависимости. Существуют два основных шаблона для заполнения содержимого, один основан на вызове FetchContent_GetProperties() и FetchContent_Populate() для более точного управления, а другой — на вызове FetchContent_MakeAvailable() для более простого и автоматизированного подхода. Первый обычно соответствует следующему каноническому шаблону:

# Check if population has already been performed
FetchContent_GetProperties(<name>)
string(TOLOWER "<name>" lcName)
if(NOT ${lcName}_POPULATED)
  # Fetch the content using previously declared details
  FetchContent_Populate(<name>)

  # Set custom variables, policies, etc.
  # ...

  # Bring the populated content into the build
  add_subdirectory(${${lcName}_SOURCE_DIR} ${${lcName}_BINARY_DIR})
endif()

Этот шаблон настолько распространён, что там, где не нужны пользовательские шаги между вызовами FetchContent_Populate() и add_subdirectory(), эквивалентный код можно получить, вызвав FetchContent_MakeAvailable() (и следует предпочесть его, если это отвечает потребностям проекта).

FetchContent_Populate
FetchContent_Populate( <name> )

В большинстве случаев единственным аргументом, передаваемым команде FetchContent_Populate(), является <name>. В этом случае команда предполагает, что данные о содержимом были записаны ранее вызовом FetchContent_Declare(). Данные хранятся в глобальной переменной, поэтому они не зависят от области видимости переменных или каталогов. Поэтому не имеет значения, где в проекте данные были объявлены ранее, лишь бы они были объявлены до вызова FetchContent_Populate(). Эти сохранённые данные затем используются для построения вызова ExternalProject_Add() в частном подпроекте для немедленного заполнения содержимого. Реализация ExternalProject_Add() гарантирует, что если содержимое уже было заполнено в предыдущем запуске CMake, то это содержимое будет повторно использовано, а не заполняться заново. В случае, когда заполнение включает загрузку содержимого, стоимость загрузки оплачивается только один раз.

Внутренняя глобальная переменная фиксирует, когда запрос на заполнение определённого содержимого был обработан. Если FetchContent_Populate() вызывается более одного раза для одного и того же имени содержимого в рамках конфигурации, то второй вызов завершится с ошибкой. Проекты могут и должны проверять, было ли содержимое уже обработано с помощью команды FetchContent_GetProperties() перед вызовом FetchContent_Populate().

FetchContent_Populate() задаст три переменные в области видимости вызывающей функции: <lcName>_POPULATED, <lcName>_SOURCE_DIR и <lcName>_BINARY_DIR, где <lcName> — это строка <name> в нижнем регистре. <lcName>_POPULATED всегда будет установлено в значение True вызовом. <lcName>_SOURCE_DIR — это расположение, где содержимое будет найдено по возврату (оно уже будет заполнено), а <lcName>_BINARY_DIR — это каталог, предназначенный для использования в качестве соответствующего каталога сборки. Основной сценарий использования двух переменных каталогов — это вызов add_subdirectory() сразу после заполнения, например:

FetchContent_Populate(FooBar ...)
add_subdirectory(${foobar_SOURCE_DIR} ${foobar_BINARY_DIR})

Значения трёх переменных также могут быть извлечены из любой точки иерархии проекта с помощью команды FetchContent_GetProperties().

Ряд переменных кэша влияют на поведение всех операций заполнения содержимого, выполненных с использованием данных, сохранённых с помощью вызова FetchContent_Declare():

FETCHCONTENT_BASE_DIR

В большинстве случаев сохранённые данные не содержат каких-либо параметров, связанных с каталогами, используемыми для внутреннего подпроекта, конечного исходного и каталогов сборки. В целом лучше всего предоставить модулю FetchContent возможность обработки этих решений от имени проекта. Переменная кэша FETCHCONTENT_BASE_DIR управляет точкой, в которой собираются все каталоги заполнения содержимого, но в большинстве случаев разработчикам не нужно изменять это значение. По умолчанию это значение ${CMAKE_BINARY_DIR}/_deps, но если разработчики изменят это значение, они должны стремиться к сохранению короткого пути, находящегося непосредственно под верхним уровнем дерева сборки, чтобы избежать проблем с длиной пути в Windows.

FETCHCONTENT_QUIET

Вывод протоколирования во время заполнения может быть довольно подробным, что делает этап конфигурации довольно шумным. Этот параметр кэша (ON по умолчанию) скрывает весь вывод заполнения, если не возникает ошибка. Если возникают проблемы с зависанием загрузок, временное отключение этого параметра может помочь в диагностике проблем с заполнением содержимого.

FETCHCONTENT_FULLY_DISCONNECTED

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

FETCHCONTENT_UPDATES_DISCONNECTED

Это менее жёсткий контроль загрузки/обновления по сравнению с FETCHCONTENT_FULLY_DISCONNECTED. Вместо того, чтобы обойти всю логику загрузки и обновления, FETCHCONTENT_UPDATES_DISCONNECTED отключает только этап обновления. Следовательно, если содержимое не было загружено ранее, оно всё равно будет загружено при включённом этом параметре. Это может ускорить этап конфигурации, но не так сильно, как FETCHCONTENT_FULLY_DISCONNECTED. По умолчанию он OFF.

В дополнение к указанным выше переменным кэша, для каждого имени содержимого также определены следующие переменные кэша (<ucName> — это верхний регистр <name>):

FETCHCONTENT_SOURCE_DIR_<ucName>

Если это установлено, то шаги загрузки и обновления для указанного содержимого не выполняются, а переменная <lcName>_SOURCE_DIR возвращается вызывающей функции и указывает на это расположение. Это даёт разработчикам возможность иметь отдельный откат содержимого, который они могут свободно изменять без вмешательства сборки. Сборка просто использует этот существующий исходный код, но по-прежнему определяет <lcName>_BINARY_DIR для указания внутри своей области сборки. Разработчикам настоятельно рекомендуется использовать этот механизм, а не редактировать содержимое, заполненное по умолчанию, так как изменения в исходниках по умолчанию могут быть утеряны при изменении данных заполнения содержимого проектом.

FETCHCONTENT_UPDATES_DISCONNECTED_<ucName>

Это эквивалент переменной FETCHCONTENT_UPDATES_DISCONNECTED для каждого содержимого. Если глобальный параметр или этот параметр ON, то обновления будут отключены для указанного содержимого. Отключение обновлений для отдельных содержимых может быть полезно для содержимого, данные которого редко изменяются, при этом оставляя другие часто изменяемые содержимые с включёнными обновлениями.

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

FetchContent_Populate( <name>
  [QUIET]
  [SUBBUILD_DIR <subBuildDir>]
  [SOURCE_DIR <srcDir>]
  [BINARY_DIR <binDir>]
  ...
)

Этот формат имеет ряд ключевых различий с форматом, где указан только <name>:

  • Все необходимые данные заполнения предполагается, что были предоставлены непосредственно в вызове FetchContent_Populate(). Любые сохранённые данные для <name> игнорируются.
  • Не проверяется, было ли содержимое для <name> уже заполнено.
  • Не устанавливается глобальная переменная для фиксации того, что заполнение произошло.
  • Не устанавливаются глобальные переменные, регистрирующие исходные или двоичные каталоги, используемые для заполненного содержимого.
  • Переменные кэша FETCHCONTENT_FULLY_DISCONNECTED и FETCHCONTENT_UPDATES_DISCONNECTED игнорируются.

Переменные <lcName>_SOURCE_DIR и <lcName>_BINARY_DIR всё ещё возвращаются вызывающей функции, но поскольку эти расположения не хранятся как глобальные переменные при использовании этого формата, они доступны только в области видимости вызова и ниже, а не во всей иерархии проекта. Переменная <lcName>_POPULATED не устанавливается в области видимости вызывающей функции в этом формате.

Поддерживаемые параметры для FetchContent_Populate() идентичны параметрам для FetchContent_Declare(). Эти несколько параметров, показанных выше, либо специфичны для FetchContent_Populate() , либо их поведение немного отличается от того, как ExternalProject_Add() их обрабатывает.

QUIET

Параметр QUIET может быть задан для скрытия вывода, связанного с заполнением указанного содержимого. Если заполнение завершится неудачей, вывод будет показан независимо от того, был ли задан этот параметр или нет, чтобы можно было диагностировать причину сбоя. Глобальная переменная кэша FETCHCONTENT_QUIET не оказывает влияния на вызовы FetchContent_Populate(), где данные содержимого предоставляются непосредственно.

SUBBUILD_DIR

Аргумент SUBBUILD_DIR может быть предоставлен для изменения расположения подпроекта, созданного для выполнения заполнения. Значение по умолчанию — ${CMAKE_CURRENT_BINARY_DIR}/<lcName>-subbuild, и обычно нет необходимости в перезаписи этого значения по умолчанию. Если указан относительный путь, он будет интерпретироваться как относительный к CMAKE_CURRENT_BINARY_DIR.

SOURCE_DIR, BINARY_DIR

Аргументы SOURCE_DIR и BINARY_DIR поддерживаются ExternalProject_Add(), но в FetchContent_Populate() используются разные значения по умолчанию. SOURCE_DIR по умолчанию ${CMAKE_CURRENT_BINARY_DIR}/<lcName>-src, а BINARY_DIR по умолчанию ${CMAKE_CURRENT_BINARY_DIR}/<lcName>-build. Если указан относительный путь, он будет интерпретироваться как относительный к CMAKE_CURRENT_BINARY_DIR.

В дополнение к указанным выше явным параметрам любые другие нераспознанные параметры передаются без изменений в ExternalProject_Add() для выполнения шагов загрузки, обработки и обновления. Следующие параметры явно запрещены (они отключены командой FetchContent_Populate()):

  • CONFIGURE_COMMAND
  • BUILD_COMMAND
  • INSTALL_COMMAND
  • TEST_COMMAND

При использовании FetchContent_Populate() в режиме сценариев CMake имейте в виду, что реализация создаёт подпроект, который, следовательно, требует наличия генератора CMake и инструмента сборки. Если эти элементы не могут быть найдены по умолчанию, то переменные CMAKE_GENERATOR и/или CMAKE_MAKE_PROGRAM необходимо установить соответствующим образом в командной строке вызова скрипта.

FetchContent_GetProperties

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

FetchContent_GetProperties( <name>
  [SOURCE_DIR <srcDirVar>]
  [BINARY_DIR <binDirVar>]
  [POPULATED <doneVar>]
)

Опции SOURCE_DIR, BINARY_DIR и POPULATED могут быть использованы для указания свойств, которые следует извлечь. Каждая опция принимает значение, которое является именем переменной для хранения этого свойства. Большинство же случаев ограничиваются только <name>, в этом случае вызов устанавливает те же переменные, что и вызов FetchContent_Populate(name). Это позволяет использовать следующий канонический шаблон, гарантирующий, что соответствующие переменные будут всегда определены, независимо от того, была ли уже выполнена обработка содержимого в других частях проекта:

FetchContent_GetProperties(foobar)
if(NOT foobar_POPULATED)
  FetchContent_Populate(foobar)
  ...
endif()

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

FetchContent_MakeAvailable
FetchContent_MakeAvailable( <name1> [<name2>...] )

Эта команда реализует общий шаблон, обычно необходимый для большинства зависимостей. Она итерирует по каждой из указанных зависимостей и для каждой из них примерно следует тому же каноническому шаблону, что представлено в начале этого раздела. Единственное небольшое отличие от этого шаблона заключается в том, что он вызовет add_subdirectory() только для обработанного содержимого, если в каталоге верхнего уровня исходного кода существует файл CMakeLists.txt. Это позволяет использовать команду для зависимостей, которые делают загруженное содержимое доступным в известном месте, но которые не нуждаются или не поддерживают добавление непосредственно в сборку.

Примеры

Этот первый довольно простой пример гарантирует, что некоторые популярные фреймворки для тестирования доступны для основной сборки:

include(FetchContent)
FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        release-1.8.0
)
FetchContent_Declare(
  Catch2
  GIT_REPOSITORY https://github.com/catchorg/Catch2.git
  GIT_TAG        v2.5.0
)

# After the following call, the CMake targets defined by googletest and
# Catch2 will be defined and available to the rest of the build
FetchContent_MakeAvailable(googletest Catch2)

В более сложных иерархиях проектов отношения зависимостей могут быть более сложными. Рассмотрим иерархию, где projA — это проект верхнего уровня, и он напрямую зависит от проектов projB и projC. Оба projB и projC могут быть собраны автономно, и они также оба зависят от другого проекта projD. projB дополнительно зависит от projE. Этот пример предполагает, что все пять проектов доступны на сервере компании git. В файле CMakeLists.txt каждого проекта могут быть разделы, подобные следующим:

projA:

include(FetchContent)
FetchContent_Declare(
  projB
  GIT_REPOSITORY git@mycompany.com:git/projB.git
  GIT_TAG        4a89dc7e24ff212a7b5167bef7ab079d
)
FetchContent_Declare(
  projC
  GIT_REPOSITORY git@mycompany.com:git/projC.git
  GIT_TAG        4ad4016bd1d8d5412d135cf8ceea1bb9
)
FetchContent_Declare(
  projD
  GIT_REPOSITORY git@mycompany.com:git/projD.git
  GIT_TAG        origin/integrationBranch
)
FetchContent_Declare(
  projE
  GIT_REPOSITORY git@mycompany.com:git/projE.git
  GIT_TAG        origin/release/2.3-rc1
)

# Order is important, see notes in the discussion further below
FetchContent_MakeAvailable(projD projB projC)

projB:

include(FetchContent)
FetchContent_Declare(
  projD
  GIT_REPOSITORY git@mycompany.com:git/projD.git
  GIT_TAG        20b415f9034bbd2a2e8216e9a5c9e632
)
FetchContent_Declare(
  projE
  GIT_REPOSITORY git@mycompany.com:git/projE.git
  GIT_TAG        68e20f674a48be38d60e129f600faf7d
)

FetchContent_MakeAvailable(projD projE)

projC:

include(FetchContent)
FetchContent_Declare(
  projD
  GIT_REPOSITORY git@mycompany.com:git/projD.git
  GIT_TAG        7d9a17ad2c962aa13e2fbb8043fb6b8a
)

# This particular version of projD requires workarounds
FetchContent_GetProperties(projD)
if(NOT projd_POPULATED)
  FetchContent_Populate(projD)

  # Copy an additional/replacement file into the populated source
  file(COPY someFile.c DESTINATION ${projd_SOURCE_DIR}/src)

  add_subdirectory(${projd_SOURCE_DIR} ${projd_BINARY_DIR})
endif()

Необходимо обратить внимание на несколько ключевых моментов в вышеприведённом примере:

  • projB и projC определяют различные данные о содержимом для projD, но projA также определяет набор данных о содержимом для projD. Поскольку projA определит их первыми, данные из projB и projC не будут использованы. Переопределяемые данные, определённые projA, не обязательно должны совпадать ни с одними из тех, что определены projB или projC, но проект верхнего уровня должен убедиться, что определяемые им данные всё ещё имеют смысл для дочерних проектов.
  • В вызове projA FetchContent_MakeAvailable(), projD перечислен до projB и projC, чтобы гарантировать, что projA управляет тем, как projD обрабатывается.
  • Хотя projA определяет данные о содержимом для projE, ему не нужно явно вызывать FetchContent_MakeAvailable(projE) или FetchContent_Populate(projD) сам. Вместо этого он оставляет это дочерним projB. Для проектов верхнего уровня часто достаточно просто определить переопределяемые данные о содержимом и предоставить дочерним проектам обработку. Это позволяет избежать ненужного повторения одних и тех же действий на каждом уровне иерархии проекта.

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

cmake_minimum_required(VERSION 3.14)

include(FetchContent)
FetchContent_Declare(
  mycom_toolchains
  URL  https://intranet.mycompany.com//toolchains_1.3.2.tar.gz
)
FetchContent_MakeAvailable(mycom_toolchains)

project(CrossCompileExample)

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

cmake -DCMAKE_TOOLCHAIN_FILE=_deps/mycom_toolchains-src/toolchain_arm.cmake /path/to/src

Когда CMake обрабатывает файл CMakeLists.txt, он загрузит и распакует архив в _deps/mycompany_toolchains-src относительно каталога сборки. Переменная CMAKE_TOOLCHAIN_FILE не используется до достижения команды project(), в этот момент CMake ищет указанный файл инструментальной цепочки относительно каталога сборки. Поскольку архив уже загружен и распакован к этому моменту, файл инструментальной цепочки будет на месте, даже в первый раз, когда cmake запускается в каталоге сборки.

Наконец, следующий пример демонстрирует, как можно загрузить и распаковать архив прошивки с помощью script mode CMake. Вызов FetchContent_Populate() указывает все данные о содержимом, и распакованная прошивка будет размещена в каталоге firmware ниже текущего каталога.

getFirmware.cmake:

# NOTE: Intended to be run in script mode with cmake -P
include(FetchContent)
FetchContent_Populate(
  firmware
  URL        https://mycompany.com/assets/firmware-1.23-arm.tar.gz
  URL_HASH   MD5=68247684da89b608d466253762b0ff11
  SOURCE_DIR firmware
)

© 2000–2020 Kitware, Inc. and Contributors
Licensed under the BSD 3-clause License.
https://cmake.org/cmake/help/v3.15/module/FetchContent.html

Spec-Zone.ru

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