Cmake

Экспорт и установка пакетов: install(), CMake config-файлы для других проектов

Введение

Статья 578 показала использование Config-режима find_package для готовых библиотек — в этой статье разберём, как создать такую готовую, «находимую» через find_package библиотеку самостоятельно: команду install() и генерацию собственных Config-файлов для распространения своей библиотеки другим проектам.

Концепция

install() определяет, какие файлы (скомпилированные библиотеки, исполняемые файлы, заголовочные файлы) и в какие директории должны быть скопированы при выполнении отдельного шага установки (cmake --install . или make install), отдельного от обычной сборки — это нужно как для конечного развёртывания приложения (статья 451 DevOps-цикла про упаковку), так и для подготовки библиотеки к использованию ДРУГИМИ проектами через find_package. Генерация Config-файла (через install(EXPORT ...) в сочетании со вспомогательными командами модуля CMakePackageConfigHelpers) создаёт именно тот <Имя>Config.cmake файл, который другой проект сможет найти через find_package(ВашаБиблиотека CONFIG) (статья 578), со всеми необходимыми, корректно настроенными импортированными целями, включая правильную передачу видимости зависимостей (статья 575) этой библиотеки.

Пример кода: Экспорт и установка пакетов

# Установка скомпилированной библиотеки, её заголовков, и экспорт для других проектов
add_library(MathUtils math_utils.cpp)
target_include_directories(MathUtils PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>     # путь ПРИ СБОРКЕ исходного проекта
    $<INSTALL_INTERFACE:include>                                 # путь ПОСЛЕ установки, относительно install-префикса
)
# ^ генераторное выражение (статья 580) — РАЗНЫЙ путь в зависимости от контекста использования!

install(TARGETS MathUtils EXPORT MathUtilsTargets
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
    RUNTIME DESTINATION bin
)
install(DIRECTORY include/ DESTINATION include) # копирование ВСЕХ публичных заголовков

install(EXPORT MathUtilsTargets
    FILE MathUtilsTargets.cmake
    NAMESPACE MathUtils::         # потребители будут использовать MathUtils::MathUtils
    DESTINATION lib/cmake/MathUtils
)
# Генерация Config-файла через стандартный, переиспользуемый механизм
include(CMakePackageConfigHelpers)

configure_package_config_file(
    Config.cmake.in                      # шаблон, написанный разработчиком библиотеки
    "${CMAKE_CURRENT_BINARY_DIR}/MathUtilsConfig.cmake"
    INSTALL_DESTINATION lib/cmake/MathUtils
)

install(FILES "${CMAKE_CURRENT_BINARY_DIR}/MathUtilsConfig.cmake"
    DESTINATION lib/cmake/MathUtils
)
# Config.cmake.in — шаблон Config-файла
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/MathUtilsTargets.cmake") # подключение целей, экспортированных install(EXPORT ...)
check_required_components(MathUtils)

Пояснения к коду

Генераторное выражение $<BUILD_INTERFACE:...>/$<INSTALL_INTERFACE:...> для путей включения показывает специфичную, важную деталь — путь к заголовкам должен отличаться в зависимости от того, использует ли библиотеку другой код, входящий в тот же, текущий проект сборки (где заголовки находятся в исходной директории), или внешний проект, использующий уже УСТАНОВЛЕННУЮ версию библиотеки (где заголовки находятся в директории установки, скопированной туда install(DIRECTORY ...)), и генераторное выражение даёт единый способ корректно описать оба этих случая в одной команде target_include_directories. install(EXPORT MathUtilsTargets ... NAMESPACE MathUtils::) показывает создание именно того файла с информацией о целях, который генерирует псевдонимы вида MathUtils::MathUtils (аналогично статье 574 про ALIAS), ожидаемые потребителями современных, качественно оформленных CMake-пакетов. configure_package_config_file (из вспомогательного модуля CMakePackageConfigHelpers) автоматизирует типичные, повторяющиеся детали генерации корректного Config-файла (вроде обработки относительных путей установки и версионирования), избавляя разработчика библиотеки от необходимости вручную писать всю эту логику с нуля для каждого нового проекта.

Подводные камни

  • Забытое разделение путей включения через BUILD_INTERFACE/INSTALL_INTERFACE, использование единственного, не различающего эти два контекста пути — это работает для собственной, «внутренней» сборки текущего проекта, но ломается для внешних проектов, использующих уже установленную версию библиотеки через find_package, поскольку реальный, физический путь к заголовкам после установки обычно отличается от пути в исходной структуре директорий разрабатываемого проекта.
  • Отсутствие версионирования экспортируемого пакета, без которого потребители не могут запросить конкретную, требуемую версию через find_package(MathUtils 2.0 REQUIRED) — для качественного, production-готового пакета стоит явно использовать дополнительные вспомогательные команды модуля CMakePackageConfigHelpers (write_basic_package_version_file) для генерации соответствующего файла версии, дающего потребителям возможность точно контролировать совместимость используемой версии библиотеки.
  • Игнорирование необходимости установить и сами заголовочные файлы (install(DIRECTORY include/ ...)), а не только скомпилированные библиотечные файлы — без этого шага потребители смогут найти и слинковаться с библиотекой через сгенерированный Config-файл, но не смогут реально скомпилировать собственный код, использующий эту библиотеку, из-за отсутствия доступных заголовочных файлов в директории установки.
  • Недостаточное тестирование процесса установки и последующего использования через find_package на действительно ОТДЕЛЬНОМ, независимом тестовом проекте, а не только в рамках исходного, «родного» проекта самой библиотеки — реальная, корректная работа экспортированного пакета для СТОРОННЕГО потребителя (с собственным, независимым CMakeLists.txt, не имеющим прямого доступа к исходным файлам и структуре оригинального проекта библиотеки) — единственная по-настоящему надёжная проверка того, что весь процесс экспорта/установки настроен действительно корректно, а не просто «случайно работает» в контексте, где доступны дополнительные, неявные детали оригинальной структуры проекта.