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()вызывается более одного раза для одного и того же имени контента в рамках выполнения configure, второй вызов остановится с ошибкой. Проекты могут и должны проверять, была ли уже обработана загрузка контента с помощью команды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 -
Выходные данные логов во время загрузки могут быть довольно подробными, что делает этап configure довольно шумным. Этот параметр кэша (
ONпо умолчанию) скрывает все выходные данные загрузки, если не возникает ошибка. Если возникают проблемы с зависанием скачивания, временное отключение этого параметра может помочь в диагностике проблем с конкретной загрузкой контента. -
FETCHCONTENT_FULLY_DISCONNECTED -
Когда этот параметр включён, не делается попытка скачать или обновить какой-либо контент. Предполагается, что весь контент уже был загружен в предыдущем запуске или исходные директории были указаны на существующие данные, предоставленные разработчиком вручную (используя параметры, описанные ниже). Если разработчик знает, что никаких изменений в данных контента не было, включение этого параметра
ONможет значительно ускорить этап configure. Он включен по умолчаниюOFF. -
FETCHCONTENT_UPDATES_DISCONNECTED -
Это менее жёсткий контроль загрузки/обновления по сравнению с
FETCHCONTENT_FULLY_DISCONNECTED. Вместо полного игнорирования логики загрузки и обновления,FETCHCONTENT_UPDATES_DISCONNECTEDотключает только этап обновления. Таким образом, если контент не был загружен ранее, он всё равно будет загружен, когда этот параметр включён. Это может ускорить этап configure, но не так сильно, как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) ... 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.
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 выполняется в каталоге сборки.
Наконец, следующий пример демонстрирует, как можно загрузить и распаковать архив с прошивкой, используя CMake’s script mode. Вызов 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.16/module/FetchContent.html