Spec-Zone.ru › CMake 3.17

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_COMMAND
  • BUILD_COMMAND
  • INSTALL_COMMAND
  • TEST_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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API