Comments 51
я хочу рассказать, как достаточно просто организовать заголовочную библиотеку на языке C++ в системе CMake
Это и без cmake просто делается.
А вот как просто организовать не одну, а несколько библиотек, при этом не все из которых заголовочные, с зависимостями друг между другом и от внешних библиотек?
С вишенкой на торте в виде бинарника программы.
И при этом состоящих не из полутора файлов, а из десятков структурированных по папкам.
Это всё весело, но туториалов по быстрому старту с cmake столько, что хоть на зиму соли. Нюанс, тем не менее, в том, что программы, для которых нужна такая мощная система сборки, сложнее чем заголовочная библиотека, и когда пытаешься на cmake просто сделать что-то работающее и удобное, то либо получается груда костылей, либо на написание сценариев для cmake уходит времени больше, чем на саму программу.
Это и без cmake просто делается.
Можно вообще всё руками делать. Но лучше иметь удобный инструмент.
А вот как просто организовать не одну, а несколько библиотек, при этом не все из которых заголовочные, с зависимостями друг между другом и от внешних библиотек?
На самом деле, не сильно сложнее. Если это не стёб, а реальный вопрос, то могу попробовать подготовить соответствующий материал.
Присоединяюсь к запросу на материал более высокого уровня и про кроссплатформенную сборку тоже расскажите. Большие проекты не только GCC собираются.
Но лучше иметь удобный инструмент.
Безусловно, но после тулинга в языках, авторы которых уже наелись C++, нырять во все эти ninja, cmake, autotools и прочее довольно напрягает. CMake при всех его достоинствах не самый удобный инструмент всё же.
Если это не стёб, а реальный вопрос, то могу попробовать подготовить соответствующий материал.
Не стёб, к сожалению. Адекватного материала на тему того, как удобно организовывать проекты сложнее hello world с использованием cmake и при этом без "ну, давайте тут вставим костыль, который используется в нашей компании для легаси-кода с cmake 2.8" днём с огнём не сыщешь.
Есть опыт в проекте, не большой, но и не hello world. Мы разбиваем проект на модули, которые собираются как библиотеки (статические, динамические, объектные — не важно). У этих целей настроены PUBLIC target_link_libraries, target_include_directories, соответственно, когда мы линкуем модуль к другому, все эти настройки подтягиваются. Так получается весьма модулльная структура, особо не зависящая от того, что где лежит и от этих include_subdirectory и прочих танцев.
Вот, я сделал пример. Надеюсь, это вам поможет.
Из недостатков (на мой взгляд), то, что модули инклюдятся как
#include "a.h"
а не как
#include "module_a/a.h"
Наверное, это весьма и весьма существенный недостаток, когда публичных хидеров в модуле много. Ах да, забыл. В папку include модулей кладутся их публичные хидера, которые должны быть видны другим. Если требуются какие-то приватные хидера, то мы кладем их прямо в src модуля.
Говоря «модули» я имел в виду отдельные проекты в терминах CMake. Например проблема в том, что я не могу собрать отдельно проект A, не собирая B и не используя CMakeLists.txt из корня.
Пишет, что пример не найден...
Продолжение истории: https://habr.com/ru/post/463295/
Продолжение истории: https://habr.com/ru/post/463295/
Есть большой опыт работы с CMake в крупных проектах по ~150-200 модулей. Используемые языки программирования C C++ Fortran.
Начинал с версии 2.8. С этого момента много чего поменялось, и меняется все к лучшему. Так как для себя явных неудобств не находил.
На самом деле порядка недели уходит для полной настройки каркаса нового проекта, связка с серверами и т.д. А после модули уже добавляются по аналогии с существующими на раз и два, проблем не наблюдаем. Отдельно выделенного человека под эти цели у нас нет :)
А как происходит подключение проекта в другой проект, чтобы get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY) работало?
Через add_subdirectory, ExternalProject, include() или find_package(… PATHS ...)?
Через add_subdirectory
.
find_package
работает с уже установленным проектом, а там исходных CMake-скриптов уже нет.
Мне вот недавно пришлось собирать gRPC под достаточно большой зоопарк компиляторов. Если бы я не перечитал содержимое папки test, я бы очень долго возился с тем, чтобы получить gRPC, которы находятся через find_package.
Очень много надо делать велосипедов, чтобы нормально поддержать debug/release. Не говоря уже об автоматическом создании сборок cmake package.
Да, к сожалению, цмейком многие пользуются неправильно или не до конца правильно. Поэтому иногда приходится делать работу за таких горе-разработчиков библиотек. Однако, если следовать лучшим практикам, то всё легко и просто.
По факту, нужно очень много велосипедить, чтобы получить красивую сборку. А если разрабатываешь не конечное приложение, а какой-либо SDK, то вообще мрак. Но за неимением альтернатив приходится пользоваться. Нормальных руководств я так и не нашёл, собираю везде знания по крупицам. Дошло до того, что пишу сейчас свой велосипед на CMake, который собирает нужные мне thirdparty. Может, выложу его в open source.
Если я правильно понимаю, так называемые "стандартные" скрипты тоже пишут сами разработчики библиотек.
На самом деле, всё зло, как обычно, от наследия царского режима. А именно, от версии 2.x. Многие скрипты и учебники либо оттуда, либо тащат за собой весь этот хлам.
За последнее время было несколько докладов про современный cmake.
И даже статья на хабре есть: https://habr.com/ru/post/330902/
Так, CMake значит говорите. Говорите, что с помощью CMake можно обеспечить статический анализ с минимальными телодвижениями? Понятненько… Ok, мы идём к этому проекту.
И, видимо, статический анализ обошел сам CMake. Ждите скоро статью. Коллега как раз пишет и пока раздумывает, поликорректно ли использовать в названии слово «говнокод» :).
Даже если и так, то мне не известно ни одного случая, когда CMake упал бы или сделал что-то не то, что его просили.
Что, впрочем, не отменяет культуру разработки.
Ну, как сказать… с одной стороны, язык декларативный, то есть порядок выполнения он выбирает сам. С другой стороны, в некоторых случаях порядок описания сборки важен. И перечисление либ для линковщика это ещё самое очевидное.
CTest не люблю, потому что он заставляет каждый тест оформлять в виде отдельного исполняемого файла, а это, мягко говоря, не очень удобно.
В проекте-шаблоне я использую сторонний тестовый фреймворк, так что и в статье про CTest ни слова.
Ну, вообще-то необязательно.
Если тест-фреймворк такой, что всё пихает в единственный файл и отдельные тесты там нельзя выбрать — это проблема фреймворка. А если можно выбрать — то единственный файл не помеха запуску отдельных тестов. И даже (если фреймворк популярен) скорее всего уже и модуль для cmake соответствующий есть.
Самый очевидный, навскидку, пример — gtests. Да, единственный бинарь. Но модуль cmake умеет его исследовать и создаёт множество соответствующих тестов для ctest. В каждом запускается бинарь с нужными ключами, чтобы выполнился именно конкретный тест, а не вся сюита.
Ну и плюшки в виде экспорта результата (тестов, а заодно вывода конфигурации, компиляции, coverage и санитайзеров) в xml и загрузки всего этого добра в cdash (который отлично живёт в докере). Или несложного преобразования в формат junit и закачки туда, где его понимают (например, gitlab CI)
Создавать бинарник со всеми тестами, а потом запускать его N раз по разу на каждый тест, когда можно запустить один раз и получить тот же результат — неэффективно, долго и бессмысленно.
И, главное, для чего? Просто чтобы использовать CTest?
… плюшки ...
Все перечисленные вещи есть в Catch2 или doctest. При этом, в отличие от gtest, их не нужно компилировать и линковать.
Кто интересуется системами сборки, посмотрите также https://buck.build — это космос.
Опции компиляции
Вот и недоработки CMake на лицо. На msvc это работать не будет, придётся другие опции указывать. А способов задавать компиляторонезависимые опции в CMake пока ещё не завезли.
В какой-то степени завезли. Хотя бы версию C++ или всякие там "хочу в целом собираться с отладкой" можно указать.
Базовые опции в зависимости от вида сборки (debug/release и т.д.) уже есть встроенные и достаточно кросс-компиляторные (тем, кто по-привычке вручную конфигурирует -O0 -g
для debug, -O2
для release и т.д. рекомендую попробовать их убрать и посмотреть, как будет без них (make VERBOSE=1 покажет)). А специфические (включить какой-нибудь санитайзер или thread_safety_analysis для clang) на то и специфические, что в других компиляторах просто отсутствуют. Да, там другие опции.
Разные пробрасываемые определения, которые в gcc задаются через -D а в msvc через /D cmake и так умеет. И в процессе развития научился различать определения, опции компилятора, опции линкера, инклюды и т.д. (раньше почти всё приходилось через add_definitions добавлять, теперь есть разные варианты)
Дмитрий, а как вы относитесь к Ninja?
Честно говоря, никак не отношусь.
Генерировал тем же си-мейком, но не увидел никакой разницы в положительную сторону по сравнению с make
.
Отдельно от CMake не пробовал.
А как правильно добавить python bindings для pybind11, boost python, swig. И так что бы можно было потом установить отдельно библиотеку, отдельно bindings, пакетом и через pypi/conda
Не совсем понятно что именно вы хотите сделать, но посмотрите вот на этот проект: https://github.com/scikit-build/scikit-build
Чтобы что-то ставить через conda, нужно писать рецепт для conda, и я вообще не советовал бы пользоваться кондой. Как высказался про конду один компетентный товарищ:
Ужасная кодовая база, беспорядок в рецептах, устаревшие версии пакетов, отсутствие нативной поддержки pypi и чего бы то ни было ещё, без рецептов.
А ещё conda очень медленная.
Действительно, это работает. При этом в документации об этом ни слова, а сама опция заявляется как вызов справки.
https://cmake.org/cmake/help/v3.15/manual/cmake.1.html#view-help
CMake и C++ — братья навек