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, если пакет не найден. Если ни то, ни другое не установлено, он должен выводить сообщение об ошибке, если не найдет пакет.
Пакеты, которые находят несколько полунезависимых частей (например, наборы библиотек), должны искать компоненты, перечисленные в 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_, которое (если не указано иное) должно точно совпадать с именем файла FindXxx.cmake, включая регистр. Этот префикс в именах переменных гарантирует, что они не будут конфликтовать с переменными других модулей поиска. Такой же шаблон должен использоваться и для любых макросов, функций и импортированных целей, определенных модулем поиска.
-
Xxx_INCLUDE_DIRS -
Конечный набор каталогов включения, перечисленных в одной переменной для использования клиентским кодом. Это не должно быть записью кэша (обратите внимание, что это также означает, что эта переменная не должна использоваться в качестве переменной результата команды
find_path()- см.Xxx_INCLUDE_DIRниже). -
Xxx_LIBRARIES -
Библиотеки для использования с модулем. Это могут быть цели CMake, полные абсолютные пути к двоичным файлам библиотек или имена библиотек, которые должны быть найдены компоновщиком в пути поиска. Это не должно быть записью кэша (обратите внимание, что это также означает, что эта переменная не должна использоваться в качестве переменной результата команды
find_library()- см.Xxx_LIBRARYниже). -
Xxx_DEFINITIONS -
Определения компиляции для использования при компиляции кода, использующего модуль. Это действительно не должно включать такие параметры, как
-DHAS_JPEG, которые файл клиентского исходного кода использует для принятия решения о том,#include <jpeg.h> -
Xxx_EXECUTABLE -
Полный абсолютный путь к исполняемому файлу. В этом случае
Xxxможет не быть именем модуля, а именем инструмента (обычно преобразуемым в все заглавные буквы), предполагая, что у этого инструмента есть такое широко известное имя, что маловероятно существование другого инструмента с таким же именем. Использование этой переменной в качестве результата командыfind_program()будет уместно. -
Xxx_YYY_EXECUTABLE -
Аналогично
Xxx_EXECUTABLE, за исключением того, что здесьXxxвсегда является именем модуля, аYYY— именем инструмента (снова, обычно все заглавные буквы). Предпочтительнее использовать этот формат, если имя инструмента не очень широко известно или может конфликтовать с другим инструментом. Для большей согласованности, также предпочтительнее использовать этот формат, если модуль предоставляет более одного исполняемого файла. -
Xxx_LIBRARY_DIRS -
Необязательно, конечный набор каталогов библиотек, перечисленных в одной переменной для использования клиентским кодом. Это не должно быть записью кэша.
-
Xxx_ROOT_DIR -
Местоположение базового каталога модуля.
-
Xxx_VERSION_VV -
Переменные такого вида указывают, является ли предоставляемый модуль
XxxверсиейVVмодуля. Для данного модуля не должно быть более одной переменной такого вида, установленной в значение true. Например, модульBarryмог эволюционировать на протяжении многих лет и пройти несколько различных основных версий. Версия 3 модуляBarryмогла установить переменнуюBarry_VERSION_3в значение true, в то время как более старая версия модуля могла установить переменнуюBarry_VERSION_2в значение true вместо этого. Было бы ошибкой, если бы иBarry_VERSION_3, иBarry_VERSION_2были бы установлены в значение true. -
Xxx_WRAP_YY -
Когда переменная такого формата установлена в значение false, это означает, что соответствующая оборачивающая команда не должна использоваться. Оболочка команды зависит от модуля, она может быть подразумеваемой именем модуля или может быть указана частью
YYпеременной. -
Xxx_Yy_FOUND -
Для переменных такого формата
Yy— имя компонента для модуля. Оно должно точно совпадать с одним из допустимых имен компонентов, которые могут быть переданы командеfind_package()для модуля. Если переменная такого формата установлена в значение false, это означает, что компонентYyмодуляXxxне был найден или недоступен. Переменные такого формата обычно используются для необязательных компонентов, чтобы позволить вызывающей стороне проверить доступность необязательного компонента. -
Xxx_FOUND -
При возвращении команды
find_package()вызывающей стороне, эта переменная будет установлена в значение true, если модуль был успешно найден. -
Xxx_NOT_FOUND_MESSAGE -
Должна устанавливаться файлами конфигурации в случае, если она установила
Xxx_FOUNDв значение FALSE. Содержащееся сообщение будет выведено командойfind_package()иfind_package_handle_standard_args(), чтобы проинформировать пользователя о проблеме. Используйте ее вместо непосредственного вызоваmessage()для сообщения о причине невозможности найти модуль или пакет. -
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 -
Путь к библиотеке. Используйте этот формат только тогда, когда модуль предоставляет одну библиотеку. Подходит для использования в качестве переменной результата в команде
find_library(). -
Xxx_Yy_LIBRARY -
Путь к библиотеке
Yy, предоставляемой модулемXxx. Используйте этот формат, когда модуль предоставляет более одной библиотеки или когда другие модули также могут предоставлять библиотеку с тем же именем. Также подходит для использования в качестве переменной результата в командеfind_library(). -
Xxx_INCLUDE_DIR -
Если модуль предоставляет только одну библиотеку, эта переменная может использоваться для указания, где найти заголовочные файлы для использования библиотеки (или, точнее, путь, который потребители библиотеки должны добавить в свой путь поиска заголовочных файлов). Подходит для использования в качестве переменной результата в команде
find_path(). -
Xxx_Yy_INCLUDE_DIR -
Если модуль предоставляет более одной библиотеки или когда другие модули также могут предоставлять библиотеку с тем же именем, рекомендуется использовать этот формат для указания, где найти заголовочные файлы для использования библиотеки
Yy, предоставляемой модулем. Опять же, подходит для использования в качестве переменной результата в командеfind_path().
Чтобы предотвратить перегрузку пользователей настройками, старайтесь по возможности исключить из кэша как можно больше опций, оставив хотя бы одну опцию, которая может быть использована для отключения использования модуля или для поиска отсутствующей библиотеки (например, Xxx_ROOT_DIR). По той же причине большинство опций кэша помечаются как расширенные. Для пакетов, которые предоставляют как отладочные, так и релизные бинарные файлы, принято создавать переменные кэша с суффиксом _LIBRARY_<CONFIG>, например, Foo_LIBRARY_RELEASE и Foo_LIBRARY_DEBUG. Модуль SelectLibraryConfigurations может быть полезен в таких случаях.
Хотя это стандартные имена переменных, вы должны обеспечить обратную совместимость для всех устаревших имён, которые фактически использовались. Убедитесь, что вы прокомментировали их как устаревшие, чтобы никто не начал их использовать.
Пример модуля поиска
Мы опишем, как создать простой модуль поиска для библиотеки 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}
)
В качестве альтернативы, если библиотека доступна с несколькими конфигурациями, вы можете использовать SelectLibraryConfigurations для автоматической установки переменной Foo_LIBRARY:
find_library(Foo_LIBRARY_RELEASE
NAMES foo
PATHS ${PC_Foo_LIBRARY_DIRS}/Release
)
find_library(Foo_LIBRARY_DEBUG
NAMES foo
PATHS ${PC_Foo_LIBRARY_DIRS}/Debug
)
include(SelectLibraryConfigurations)
select_library_configurations(Foo)
Если у вас есть хороший способ получения версии (например, из заголовочного файла), вы можете использовать эту информацию для установки 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. Также будут выводиться сообщения по мере необходимости; обратите внимание, что если пакет был найден, он выведет содержимое первой требуемой переменной, чтобы указать, где он был найден.
На этом этапе нам нужно предоставить способ пользователям модуля поиска связать найденную библиотеку или библиотеки. Есть два подхода, как обсуждалось в разделе «Модули поиска» выше. Традиционный подход с переменными выглядит так:
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–2021 Kitware, Inc. and Contributors
Licensed under the BSD 3-clause License.
https://cmake.org/cmake/help/v3.22/manual/cmake-developer.7.html