Spec-Zone.ru › CMake 3.18

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(). Шаги конфигурации, сборки, установки и тестирования явно отключены, и поэтому связанные с ними опции будут игнорироваться. Опция SOURCE_SUBDIR является исключением, см. FetchContent_MakeAvailable() для получения информации о том, как это влияет на поведение.

В большинстве случаев <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_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_SUBDIR, который влияет только на команду FetchContent_MakeAvailable().

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. Это позволяет использовать команду для зависимостей, которые делают загруженное содержимое доступным в известном месте, но не требуют или не поддерживают прямое добавление в сборку.

Опцию SOURCE_SUBDIR можно указать в объявленных деталях, чтобы попросить FetchContent_MakeAvailable() искать файл CMakeLists.txt в подкаталоге ниже корневого уровня (аналогично тому, как SOURCE_SUBDIR используется командой ExternalProject_Add()). SOURCE_SUBDIR всегда должен быть относительным путём. См. следующий раздел для примера использования этой опции.

Примеры

Этот первый довольно простой пример гарантирует, что некоторые популярные фреймворки для тестирования доступны для основной сборки:

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)

Если файл CMakeLists.txt подпроекта не находится в корневом каталоге его дерева исходных файлов, опцию SOURCE_SUBDIR можно использовать для указания FetchContent места его нахождения. Следующий пример показывает, как использовать эту опцию, а также задаёт переменную, имеющую смысл для подпроекта, перед включением его в основную сборку:

include(FetchContent)
FetchContent_Declare(
  protobuf
  GIT_REPOSITORY https://github.com/protocolbuffers/protobuf.git
  GIT_TAG        v3.12.0
  SOURCE_SUBDIR  cmake
)
set(protobuf_BUILD_TESTS OFF)
FetchContent_MakeAvailable(protobuf)

В более сложных иерархиях проектов отношения зависимостей могут быть более сложными. Рассмотрим иерархию, где projA — это проект верхнего уровня, и он напрямую зависит от проектов projB и projC. Оба projB и projC могут быть построены автономно, и оба также зависят от другого проекта projD. projB дополнительно зависит от projE. Этот пример предполагает, что все пять проектов доступны на сервере 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_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.18/module/FetchContent.html

Spec-Zone.ru

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