Обновить
27
Антон Пантюхин@pananton

Senior C++ backend developer

7
Подписчики
Отправить сообщение

Ммм, не уверен, что полностью вас понимаю. Из своей библиотеки нужно как-то найти чужую. find_package при этом не работает, т.к. third-party либа не экспортирует файл конфигурации пакета, и не достаточна популярна, чтобы CMake сам по себе имел для нее find-модуль (как, например, для буста и опенссл). Поэтому, чтобы find_package заработал, нужно написать свой find-модуль. Что вы подразумеваете под config module я до конца не понял, т.к. в документации CMake такого понятия нет, насколько мне известно. Если вы предлагаете просто где-то руками создать импортированный таргет для third-party либы и потом без всякого find_package его использовать - то для каких-то частных случаев это может и сгодиться, но в общем случае - это костыльный подход, с которым потом вероятно вылезут проблемы. Например, когда вы свою библиотеку инсталлируете, вам придется в своем файле конфигурации как-то искать third-party либу, которая непонятно где в системе может быть установлена. Если вы напишите свой find-модуль, то можете его просто ставить вместе со своей библиотекой и использовать в файле конфигурации пакета, чтобы find_dependency отрабатывал, как надо.

Насчет переменных, выставляемых в файлах конфигурации библиотек, про которые вы писали. Когда я готовил статью, я некоторое время раздумывал на тему того, нужно ли мне тоже показать, как получить эти пути, потому что вообще говоря там не все просто. Но в итоге пришел к выводу, что раз уж использование этих путей приводит к плохому стилю чужих CMakeLists.txt, не стоит мне искушать пользователей моих библиотек и давать им такую возможность. Поэтому сейчас я придерживаюсь мнения, что импортированного таргета должно быть достаточно всем. Если кто-то все еще не использует CMake 3.0 и старше, то это дополнительный сигнал, что пора)

Да, вы верно говорите. В том руководстве, о котором я написал в комментарии не возникает проблем с разными версиями рантайма (библиотеки-то в итоге все с разными именами и линкуется с нужной конфигурацией/рантаймом).

Замечание про MAP_IMPORTED_CONFIG_<CONFIG> я уверен полезно для читателей, спасибо.

Имхо в пресете надо все-таки указывать минимальную версию, нужную именно для сборки проекта. Я не вижу причин, по которым было бы предпочтительнее указывать версию, нужную для поддержки вашего варианта пресета (которых тоже уже 5 штук), а вот обратное в общем случае неверно. Потому что чисто теоретически, ничто не мешает IDE самой распарсить пресет и при вызове CMake вообще его не указывать, а, скажем, переменные cacheVariables просто передавать в командной строке. Тогда вам не нужен именно CMake 3.19 и выше, потому что `cmake --preset ...` просто не используется. У других авторов я находил именно такой вариант (т.е. в пресете запросто указывается именно версия, нужная для сборки, а не для поддержки самого пресета).

Честно говоря, у меня опыта по CPack особого нет. Мне он вообще представляется довольно простым - устанавливай всякие переменные, и все дела. Вероятно я просто не в курсе о всяких подводных камнях, которые можно было бы раскрыть в статье.

В данный момент я больше задумываюсь о туториале по рецептам для Conan.

Я вам ответил - нужно написать find module, см. п. 2

Пожалуйста, я вот раньше не вытерпел)

Про то, как можно работать с multi-config генераторами расскажу в отдельном ответе.

Тут вообще говоря обычно используется два пути: установка разных конфигураций (static/shared, Debug/Release/..) в разные директории (тогда ничего специального делать не нужно), или же установка разных конфигураций в одну директорию, т.е. по одному CMAKE_INSTALL_PREFIX.

Во втором случае вам придется решить проблему с одинаковыми именами файлов. В качестве примера можно рассмотреть Windows. При установке обычно копируются такие файлы:

  1. Публичные заголовки библиотеки - они должны быть одинаковыми для всех конфигураций, поэтому без разницы, сколько разных конфигураций вы установите в одну директорию. Единственный момент здесь - это файл, генерируемый generate_public_header. Он имеет разный вид для static и shared, поэтому в статье я называю эти файлы по-разному для static и shared

  2. Файл версии проекта (генерируется write_basic_package_version_file) - одинаковый для разных конфигураций, можно смело перезаписывать

  3. Файл конфигурации проекта можно (и нужно) писать так, чтобы он не зависел от конфигурации сборки

  4. Файл с определением таргета библиотеки (создается install(EXPORT)) - он разный для static и shared, но НЕ зависит от типа билда (Debug, Release и т.д.). Поэтому нужно называть их по-разному, если вы планируете устанавливать статическую и динамическую версию в одну директорию. При этом install(EXPORT) кроме этих файлов генерирует еще для текущей конфигурации файл <targets-file-name>-<config>.cmake (например, mylib-static-targets-debug.cmake, mylib-static-targets-release.cmake и т.д.) Когда ваш файл конфигурации библиотеки (mylib-config.cmake) включает файл с импортируемым таргетом библиотеки (например, mylib-static-targets.cmake), последний дополнительно включает все файлы с именем mylib-static-targets-*.cmake. В этих файлах, если не вдаваться в детали, для импортируемого таргета библиотеки устанавливается свойство IMPORTED_LOCATION_<CONFIG>, в которое записывается путь до бинари библиотеки для конкретной конфигурации. В проекте, который использует вашу библиотеку, потом можно менять конфигурацию, и линковаться будет та, что нужно.

  5. Бинари библиотеки - они очевидно разные, поэтому нужно продумать какую-то схему по их переименованию. Например, я иногда использую постфиксы "" (пустой) для Release, "d" для Debug, "m" для MinSizeRel и "r" для RelWithDebInfo. Для статической версии дополнительно в начало постфикса ставится буква "s". Не надо только свою схему навязывать всем, прописывая эти постфиксы прямо в set_target_properties в CMakeLists.txt. Вместо этого можно в пресете или в командной строке использовать переменную CMAKE_<CONFIG>_POSTFIX.

  1. Если вы про то, что для того, чтобы пометить функцию как экспортируемую, в Linux и Windows используются разные директивы компилятора (__attribute__ и __declspec), то CMake предоставляет функцию generate_export_header, которая сгенерирует файл, в котором за макросом вида <LIBNAME>_EXPORT спрячет платформо-зависимую директиву. Этот файл надо включать в свои исходники и устанавливать вместе с вашей библиотекой. В статье есть этот вопрос освещается в разделе "Экспорт символов"

  2. В своей библиотеке вы делаете find_package(otherLib), потом target_link_libraries(myLib PUBLIC|PRIVATE otherLib). PUBLIC вы определяете, если нужно, чтобы проекты, которые будут линковаться с myLib видели заголовочные файлы otherLib (как правило, из-за того, что заголовки myLib включают заголовки otherLib), в противном случае - PRIVATE. Еще потребуется в файле конфигурации вашей библиотеки выполнить команду find_dependency(otherLib), чтобы проинициализировать зависимости для импортируемого таргета. Этот способ сработает, если разработчик otherLib все сделал правильно и предоставил файл конфигурации для своей библиотеки. Если нет, то вам надо написать свой find module для otherLib, который будет находить ее в системе и создавать для нее импортированный таргет. Этот модуль затем вы будете вероятно устанавливать вместе со своей библиотекой, чтобы использовать его в своем файле конфигурации при вызове find_dependency.

  3. В CMakeLists.txt нужно хардкодить только build requirements, т.е. опции, без которых ваш проект в принципе не соберется. Все остальное - в пресетах. Например, флаг Werror, который делает предупреждения компилятора ошибками, никогда не должен указываться в вашем CMakeLists.txt (ну или по крайней мере должна быть опции в вашем проекте, которая позволит его отключить, но лучше использовать пресет). Причины, по котором это необходимо делать, объясняются в статье. Если вкратце, любой захардкоденный флаг в вашем проекте - проблема на мейнтейнеров пакетных менеджеров, т.к. им придется патчить ваши CMakeLists.txt, когда они будут собирать вашу либу под какую-нибудь платформу, на которой захардкоденная опция невалидна.

Не совсем согласен с вами. Решение есть, оно достаточно типовое, так что его вполне можно загнать в какой-то генератор, например упоминавшийся уже cmake-init. Проблема однако в том, что лично мне не удалось найти его сформулированным в одном месте, пришлось ознакомиться с большим количество источников, в которых раскрывались отдельные вопросы, но не все целиком. Не понимаю, почему CMake не может обновить свой tutorial с учетом всех новых фич. Может для того, чтобы докладчиков на CppCon без хлеба не оставлять, которые из года в год рассказывают, как надо правильно делать.

Информация

В рейтинге
Не участвует
Откуда
Томск, Томская обл., Россия
Дата рождения
Зарегистрирован
Активность

Специализация

Бэкенд разработчик
Ведущий
C++
Базы данных
NoSQL
Docker
Проектирование архитектуры приложений
Кросс-платформенная разработка
Алгоритмы и структуры данных
Математика