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 с помощью CMake’s script mode. Вызов 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.11/module/FetchContent.html