Введение
Статья 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, не имеющим прямого доступа к исходным файлам и структуре оригинального проекта библиотеки) — единственная по-настоящему надёжная проверка того, что весь процесс экспорта/установки настроен действительно корректно, а не просто «случайно работает» в контексте, где доступны дополнительные, неявные детали оригинальной структуры проекта.