cmake-developer(7)
Введение
Данное руководство предназначено для разработчиков, работающих с cmake-language(7) кодом, будь то создание собственных модулей, написание собственных систем сборки или работа над самим CMake.
Чтобы принять участие в разработке CMake, обратитесь к https://cmake.org/get-involved/. Там вы найдете ссылки на инструкции по участию, которые, в свою очередь, ведут к руководствам для разработчиков CMake.
Модули поиска
«Модуль поиска» — это Find<PackageName>.cmake файл, который загружается командой find_package() при вызове для <PackageName>.
Основная задача модуля поиска — определить, существует ли пакет на системе, установить переменную <PackageName>_FOUND для отражения этого и предоставить любые переменные, макросы и импортированные целевые объекты, необходимые для использования пакета. Модуль поиска полезен в тех случаях, когда библиотека вышестоящего уровня не предоставляет пакет конфигурационного файла.
Традиционный подход заключается в использовании переменных для всего, включая библиотеки и исполняемые файлы: см. раздел Стандартные имена переменных ниже. Большинство существующих модулей поиска, предоставляемых CMake, используют именно этот подход.
Более современный подход заключается в поведении как можно более похоже на пакеты конфигурационных файлов, предоставляя импортированные целевые объекты. Это обеспечивает преимущества распространения Требований к использованию целевых объектов к потребителям.
В любом случае (или даже при предоставлении как переменных, так и импортированных целевых объектов) модули поиска должны обеспечивать обратную совместимость со старыми версиями с тем же именем.
Модуль FindFoo.cmake обычно загружается с помощью команды:
find_package(Foo [major[.minor[.patch[.tweak]]]]
[EXACT] [QUIET] [REQUIRED]
[[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[NO_POLICY_SCOPE])
См. документацию по find_package() для получения подробной информации о том, какие переменные устанавливаются для модуля поиска. Большинство из них обрабатываются с помощью FindPackageHandleStandardArgs.
Вкратце, модуль должен находить только версии пакета, совместимые с запрошенной версией, как описано семейством переменных Foo_FIND_VERSION. Если Foo_FIND_QUIETLY установлено в значение true, то он должен избегать вывода сообщений, включая любые сообщения об отсутствии пакета. Если Foo_FIND_REQUIRED установлено в значение true, модуль должен вывести сообщение FATAL_ERROR, если пакет не найден. Если ни одно из этих значений не установлено в true, он должен выводить сообщение об ошибке, если пакет не найден.
Пакеты, которые находят несколько полунезависимых частей (например, наборы библиотек), должны искать компоненты, перечисленные в Foo_FIND_COMPONENTS, если оно задано, и устанавливать Foo_FOUND в значение true только в том случае, если для каждого найденного компонента <c>, который не найден, Foo_FIND_REQUIRED_<c> не установлено в значение true. Аргумент HANDLE_COMPONENTS команды find_package_handle_standard_args() может использоваться для реализации этого.
Если Foo_FIND_COMPONENTS не задано, за какие модули нужно искать и какие из них требуются, решает модуль поиска, но это должно быть задокументировано.
Для внутренней реализации общепринято, что переменные, начинающиеся с подчеркивания, предназначены только для временного использования.
Стандартные имена переменных
Для FindXxx.cmake модуля, который использует подход к установке переменных (либо вместо, либо в дополнение к созданию импортированных целевых объектов), следует использовать следующие имена переменных, чтобы поддерживать согласованность между модулями поиска. Обратите внимание, что все переменные начинаются с Xxx_, чтобы убедиться, что они не конфликтуют с другими модулями поиска; то же самое относится к макросам, функциям и импортированным целевым объектам.
-
Xxx_INCLUDE_DIRS -
Конечный набор каталогов включения, перечисленных в одной переменной для использования клиентским кодом. Это не должна быть переменная кэша.
-
Xxx_LIBRARIES -
Библиотеки для линковки с использованием Xxx. Они должны содержать полные пути. Это не должна быть переменная кэша.
-
Xxx_DEFINITIONS -
Определения для использования при компиляции кода, использующего Xxx. Это действительно не должно включать такие опции, как
-DHAS_JPEG, которые файл клиентского исходного кода использует для принятия решения о том,#include <jpeg.h> -
Xxx_EXECUTABLE -
Расположение инструмента Xxx.
-
Xxx_Yyy_EXECUTABLE -
Расположение инструмента Yyy, который входит в Xxx.
-
Xxx_LIBRARY_DIRS -
Необязательно, конечный набор каталогов библиотек, перечисленных в одной переменной для использования клиентским кодом. Это не должна быть переменная кэша.
-
Xxx_ROOT_DIR -
Расположение базового каталога Xxx.
-
Xxx_VERSION_Yy -
Ожидается версия Yy, если true. Убедитесь, что одновременно истинными могут быть не более одной из этих переменных.
-
Xxx_WRAP_Yy -
Если False, не следует пытаться использовать соответствующую команду обертки CMake.
-
Xxx_Yy_FOUND -
Если False, необязательная часть Yy системы Xxx недоступна.
-
Xxx_FOUND -
Устанавливается в значение false или не определено, если Xxx не найден или не используется.
-
Xxx_NOT_FOUND_MESSAGE -
Должно устанавливаться файлами конфигурации в случае, если он установил
Xxx_FOUNDв значение FALSE. Содержащееся сообщение будет выведено командойfind_package()и модулемfind_package_handle_standard_args(), чтобы проинформировать пользователя о проблеме. -
Xxx_RUNTIME_LIBRARY_DIRS -
Необязательно, путь поиска динамической библиотеки во время выполнения для использования при запуске исполняемого файла, связанного с динамическими библиотеками. Список должен использоваться пользовательским кодом для создания
PATHв Windows илиLD_LIBRARY_PATHв UNIX. Это не должна быть переменная кэша. -
Xxx_VERSION -
Полная строка версии найденного пакета, если таковой имеется. Обратите внимание, что многие существующие модули предоставляют
Xxx_VERSION_STRINGвместо этого. -
Xxx_VERSION_MAJOR -
Основная версия найденного пакета, если таковая имеется.
-
Xxx_VERSION_MINOR -
Дополнительная версия найденного пакета, если таковая имеется.
-
Xxx_VERSION_PATCH -
Версия исправления найденного пакета, если таковая имеется.
Следующие имена обычно не следует использовать в файлах CMakeLists.txt, но они обычно являются переменными кэша, которые пользователи могут редактировать и контролировать поведение модулей поиска (например, ввод пути к библиотеке вручную)
-
Xxx_LIBRARY -
Путь к библиотеке Xxx (например, при использовании с
find_library()). -
Xxx_Yy_LIBRARY -
Путь к библиотеке Yy, которая является частью системы Xxx. Она может быть или не быть необходимой для использования Xxx.
-
Xxx_INCLUDE_DIR -
Расположение заголовков для использования библиотеки Xxx.
-
Xxx_Yy_INCLUDE_DIR -
Расположение заголовков для использования библиотеки Yy системы Xxx.
Чтобы избежать перегрузки пользователей настройками, старайтесь оставлять как можно больше опций вне кэша, оставляя хотя бы одну опцию для отключения использования модуля или поиска отсутствующей библиотеки (например, Xxx_ROOT_DIR). По той же причине помечайте большинство опций кэша как дополнительные. Для пакетов, предоставляющих как отладочные, так и релизные двоичные файлы, обычно создаются переменные кэша с суффиксом _LIBRARY_<CONFIG>, например Foo_LIBRARY_RELEASE и Foo_LIBRARY_DEBUG.
Хотя это стандартные имена переменных, вы должны обеспечивать обратную совместимость со всеми старыми именами, которые фактически использовались. Убедитесь, что вы прокомментируете их как устаревшие, чтобы никто не начал их использовать.
Пример модуля поиска
Мы опишем, как создать простой модуль поиска для библиотеки Foo.
В начале модуля должен быть указан текст лицензии, за которым следует пустая строка и Комментарий в квадратных скобках. Комментарий должен начинаться с .rst:, чтобы указать, что остальная его часть является документацией в формате reStructuredText. Например:
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying # file Copyright.txt or https://cmake.org/licensing for details. #[=======================================================================[.rst: FindFoo ------- Finds the Foo library. Imported Targets ^^^^^^^^^^^^^^^^ This module provides the following imported targets, if found: ``Foo::Foo`` The Foo library Result Variables ^^^^^^^^^^^^^^^^ This will define the following variables: ``Foo_FOUND`` True if the system has the Foo library. ``Foo_VERSION`` The version of the Foo library which was found. ``Foo_INCLUDE_DIRS`` Include directories needed to use Foo. ``Foo_LIBRARIES`` Libraries needed to link to Foo. Cache Variables ^^^^^^^^^^^^^^^ The following cache variables may also be set: ``Foo_INCLUDE_DIR`` The directory containing ``foo.h``. ``Foo_LIBRARY`` The path to the Foo library. #]=======================================================================]
Документация модуля состоит из:
- Подчеркнутого заголовка, указывающего имя модуля.
- Краткого описания того, что находит модуль. Для некоторых пакетов может потребоваться более подробное описание. Если есть оговорки или другие детали, о которых пользователи модуля должны знать, укажите их здесь.
- Раздела, перечисляющего импортированные целевые объекты, предоставляемые модулем, если таковые имеются.
- Раздела, перечисляющего результирующие переменные, предоставляемые модулем.
- Необязательно, раздел, перечисляющий переменные кэша, используемые модулем, если таковые имеются.
Если пакет предоставляет макросы или функции, они должны быть перечислены в дополнительном разделе, но могут быть задокументированы дополнительными .rst: блоками комментариев непосредственно над этими макросами или функциями.
Реализация модуля поиска может начинаться под блоком документации. Теперь нужно найти фактические библиотеки и т. д. Код здесь, очевидно, будет различаться в зависимости от модуля (в этом, собственно, и заключается смысл модулей поиска), но часто встречается общая схема для библиотек.
Сначала мы пытаемся использовать pkg-config для поиска библиотеки. Обратите внимание, что мы не можем полагаться на это, так как оно может быть недоступно, но это хороший отправной пункт.
find_package(PkgConfig) pkg_check_modules(PC_Foo QUIET Foo)
Это должно определить некоторые переменные, начинающиеся с PC_Foo_, которые содержат информацию из файла Foo.pc.
Теперь нам нужно найти библиотеки и файлы заголовков; мы используем информацию из pkg-config для предоставления подсказок CMake о том, где искать.
find_path(Foo_INCLUDE_DIR
NAMES foo.h
PATHS ${PC_Foo_INCLUDE_DIRS}
PATH_SUFFIXES Foo
)
find_library(Foo_LIBRARY
NAMES foo
PATHS ${PC_Foo_LIBRARY_DIRS}
)
Если у вас есть хороший способ получения версии (например, из файла заголовков), вы можете использовать эту информацию для установки Foo_VERSION (хотя обратите внимание, что модули поиска традиционно использовали Foo_VERSION_STRING, поэтому вы можете захотеть установить оба значения). В противном случае попробуйте использовать информацию из pkg-config
set(Foo_VERSION ${PC_Foo_VERSION})
Теперь мы можем использовать FindPackageHandleStandardArgs, чтобы выполнить большую часть остальной работы за нас
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Foo
FOUND_VAR Foo_FOUND
REQUIRED_VARS
Foo_LIBRARY
Foo_INCLUDE_DIR
VERSION_VAR Foo_VERSION
)
Это проверит, что REQUIRED_VARS содержат значения (которые не заканчиваются на -NOTFOUND) и установит Foo_FOUND соответствующим образом. Также будут кэшированы эти значения. Если Foo_VERSION установлено, и запрошенная версия была передана в find_package(), будет проверена запрошенная версия на соответствие версии в Foo_VERSION. Также будут выведены соответствующие сообщения; обратите внимание, что если пакет был найден, он выведет содержимое первой необходимой переменной, чтобы указать, где он был найден.
На этом этапе необходимо предоставить пользователям модуля «find» способ ссылки на найденную библиотеку или библиотеки. Существуют два подхода, как обсуждалось в разделе «Модули поиска» выше. Традиционный подход с использованием переменных выглядит так
if(Foo_FOUND)
set(Foo_LIBRARIES ${Foo_LIBRARY})
set(Foo_INCLUDE_DIRS ${Foo_INCLUDE_DIR})
set(Foo_DEFINITIONS ${PC_Foo_CFLAGS_OTHER})
endif()
Если найдено более одной библиотеки, все они должны быть включены в эти переменные (см. раздел «Стандартные имена переменных» для получения дополнительной информации).
При предоставлении импортированных целей они должны быть именованными (отсюда и префикс Foo::); CMake распознает, что значения, переданные в target_link_libraries(), которые содержат :: в своем имени, должны быть импортированными целями (а не просто именами библиотек), и выдаст соответствующие диагностические сообщения, если такая цель не существует (см. политику CMP0028).
if(Foo_FOUND AND NOT TARGET Foo::Foo)
add_library(Foo::Foo UNKNOWN IMPORTED)
set_target_properties(Foo::Foo PROPERTIES
IMPORTED_LOCATION "${Foo_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${PC_Foo_CFLAGS_OTHER}"
INTERFACE_INCLUDE_DIRECTORIES "${Foo_INCLUDE_DIR}"
)
endif()
Важно отметить, что INTERFACE_INCLUDE_DIRECTORIES и аналогичные свойства должны содержать только информацию о самой цели, а не о её зависимостях. Вместо этого эти зависимости также должны быть целями, и CMake должно быть сообщено, что они являются зависимостями этой цели. CMake затем автоматически объединит всю необходимую информацию.
Тип IMPORTED цели, созданной в команде add_library(), всегда может быть указан как UNKNOWN тип. Это упрощает код в случаях, когда могут быть найдены статические или динамические варианты, и CMake определит тип, проанализировав файлы.
Если библиотека доступна с несколькими конфигурациями, свойство цели IMPORTED_CONFIGURATIONS также должно быть заполнено:
if(Foo_FOUND)
if (NOT TARGET Foo::Foo)
add_library(Foo::Foo UNKNOWN IMPORTED)
endif()
if (Foo_LIBRARY_RELEASE)
set_property(TARGET Foo::Foo APPEND PROPERTY
IMPORTED_CONFIGURATIONS RELEASE
)
set_target_properties(Foo::Foo PROPERTIES
IMPORTED_LOCATION_RELEASE "${Foo_LIBRARY_RELEASE}"
)
endif()
if (Foo_LIBRARY_DEBUG)
set_property(TARGET Foo::Foo APPEND PROPERTY
IMPORTED_CONFIGURATIONS DEBUG
)
set_target_properties(Foo::Foo PROPERTIES
IMPORTED_LOCATION_DEBUG "${Foo_LIBRARY_DEBUG}"
)
endif()
set_target_properties(Foo::Foo PROPERTIES
INTERFACE_COMPILE_OPTIONS "${PC_Foo_CFLAGS_OTHER}"
INTERFACE_INCLUDE_DIRECTORIES "${Foo_INCLUDE_DIR}"
)
endif()
Вариант RELEASE должен быть перечислен первым в свойстве, чтобы этот вариант был выбран, если пользователь использует конфигурацию, которая не является точной копией ни одной из перечисленных IMPORTED_CONFIGURATIONS.
Большинство переменных кэша должны быть скрыты в интерфейсе ccmake, если пользователь явно не попросит их редактировать.
mark_as_advanced( Foo_INCLUDE_DIR Foo_LIBRARY )
Если этот модуль заменяет более старую версию, вы должны установить переменные совместимости, чтобы свести к минимуму возможные нарушения.
# compatibility variables
set(Foo_VERSION_STRING ${Foo_VERSION})
© 2000–2020 Kitware, Inc. and Contributors
Licensed under the BSD 3-clause License.
https://cmake.org/cmake/help/v3.15/manual/cmake-developer.7.html