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_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_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_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) # Set any custom variables, etc. here, then # populate the content as part of this build add_subdirectory(${foobar_SOURCE_DIR} ${foobar_BINARY_DIR}) endif()Вышеупомянутая схема позволяет другим частям общей иерархии проекта повторно использовать то же самое содержимое и гарантировать, что оно будет загружено только один раз.
Примеры
Рассмотрим иерархию проекта, где projA является верхним уровнем проекта и зависит от проектов projB и projC. Оба projB и projC могут быть построены автономно, и оба также зависят от другого проекта projD. Для простоты этот пример предполагает, что все четыре проекта доступны на сервере компании 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_GetProperties(projB)
if(NOT projb_POPULATED)
FetchContent_Populate(projB)
add_subdirectory(${projb_SOURCE_DIR} ${projb_BINARY_DIR})
endif()
FetchContent_GetProperties(projC)
if(NOT projc_POPULATED)
FetchContent_Populate(projC)
add_subdirectory(${projc_SOURCE_DIR} ${projc_BINARY_DIR})
endif()
projB:
include(FetchContent)
FetchContent_Declare(
projD
GIT_REPOSITORY git@mycompany.com/git/projD.git
GIT_TAG 20b415f9034bbd2a2e8216e9a5c9e632
)
FetchContent_GetProperties(projD)
if(NOT projd_POPULATED)
FetchContent_Populate(projD)
add_subdirectory(${projd_SOURCE_DIR} ${projd_BINARY_DIR})
endif()
projC:
include(FetchContent)
FetchContent_Declare(
projD
GIT_REPOSITORY git@mycompany.com/git/projD.git
GIT_TAG 7d9a17ad2c962aa13e2fbb8043fb6b8a
)
FetchContent_GetProperties(projD)
if(NOT projd_POPULATED)
FetchContent_Populate(projD)
add_subdirectory(${projd_SOURCE_DIR} ${projd_BINARY_DIR})
endif()
Некоторые ключевые моменты, которые следует отметить в приведенном выше примере:
-
projBиprojCопределяют различные данные о содержимом дляprojD, ноprojAтакже определяет набор данных о содержимом дляprojD, и посколькуprojAопределит их первой, данные изprojBиprojCне будут использованы. Данные о переопределении, определённыеprojA, не обязаны совпадать ни с одним из этих данных изprojBилиprojC, но от проекта верхнего уровня зависит, чтобы данные, которые он определяет, всё ещё имели смысл для дочерних проектов. - Хотя
projAопределил данные о содержимом дляprojD, ему не нужно было явно вызыватьFetchContent_Populate(projD)сам. Вместо этого он оставляет это дочернему проекту (в этом случае это будетprojB, так как он добавлен в построение передprojC). ЕслиprojAнеобходимо было настроить способ, которым содержимоеprojDвводилось в построение (например, определить некоторые переменные CMake перед вызовомadd_subdirectory()после загрузки), он выполнил бы вызовFetchContent_Populate(), и т. д., как и для содержимогоprojBиprojC. Для проектов верхнего уровня обычно достаточно просто определить данные о переопределении содержимого и предоставить фактическую загрузку дочерним проектам. Это экономит повторение одного и того же на каждом уровне иерархии проекта. - Несмотря на то, что
projAявляется проектом верхнего уровня в этом примере, он по-прежнему проверяет, были ли уже загруженыprojBиprojCперед продолжением этих загрузки. Это делаетprojAболее лёгким для интеграции в качестве дочернего проекта какого-либо проекта верхнего уровня в будущем, если это потребуется. Всегда защищайте вызовFetchContent_Populate()проверкой наFetchContent_GetProperties(), даже в том, что может считаться проектом верхнего уровня на данном этапе.
Следующий пример демонстрирует, как можно загрузить и распаковать архив firmware с использованием script mode CMake. Вызов FetchContent_Populate() указывает все данные о содержимом, и распакованное firmware будет помещено в каталог 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–2019 Kitware, Inc. and Contributors
Licensed under the BSD 3-clause License.
https://cmake.org/cmake/help/v3.12/module/FetchContent.html