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 tar с использованием 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.13/module/FetchContent.html