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(). Шаги конфигурации, сборки, установки и тестирования явно отключены, и поэтому связанные с ними опции будут игнорироваться. ОпцияSOURCE_SUBDIRявляется исключением, см.FetchContent_MakeAvailable()для получения информации о том, как это влияет на поведение.В большинстве случаев
<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_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_SUBDIR, который влияет только на командуFetchContent_MakeAvailable(). -
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_COMMANDBUILD_COMMANDINSTALL_COMMANDTEST_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. Это позволяет использовать команду для зависимостей, которые делают загруженное содержимое доступным в известном месте, но не требуют или не поддерживают прямое добавление в сборку.Опцию
SOURCE_SUBDIRможно указать в объявленных деталях, чтобы попроситьFetchContent_MakeAvailable()искать файлCMakeLists.txtв подкаталоге ниже корневого уровня (аналогично тому, какSOURCE_SUBDIRиспользуется командойExternalProject_Add()).SOURCE_SUBDIRвсегда должен быть относительным путём. См. следующий раздел для примера использования этой опции.
Примеры
Этот первый довольно простой пример гарантирует, что некоторые популярные фреймворки для тестирования доступны для основной сборки:
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)
Если файл CMakeLists.txt подпроекта не находится в корневом каталоге его дерева исходных файлов, опцию SOURCE_SUBDIR можно использовать для указания FetchContent места его нахождения. Следующий пример показывает, как использовать эту опцию, а также задаёт переменную, имеющую смысл для подпроекта, перед включением его в основную сборку:
include(FetchContent) FetchContent_Declare( protobuf GIT_REPOSITORY https://github.com/protocolbuffers/protobuf.git GIT_TAG v3.12.0 SOURCE_SUBDIR cmake ) set(protobuf_BUILD_TESTS OFF) FetchContent_MakeAvailable(protobuf)
В более сложных иерархиях проектов отношения зависимостей могут быть более сложными. Рассмотрим иерархию, где 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.18/module/FetchContent.html