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()вызывается более одного раза для одного и того же имени содержимого в рамках выполнения конфигурации, то второй вызов завершится с ошибкой. Проекты могут и должны проверять, была ли уже обработана загрузка содержимого с помощью команды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) ... 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 выполняется в каталоге сборки.
Наконец, следующий пример демонстрирует, как можно загрузить и распаковать архив прошивки с помощью 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.17/module/FetchContent.html