Pull to refresh

Comments 51

«CMake и C++ — братья навек» — звучит как угроза.

Мне и то, и другое нравится, поэтому никаких негативных ассоциаций не возникает.

Надо поменять местами, чтобы в рифму было: «C++ и CMake — братья навек».

Отличная мысль, чёрт возьми.

я хочу рассказать, как достаточно просто организовать заголовочную библиотеку на языке C++ в системе CMake

Это и без cmake просто делается.


А вот как просто организовать не одну, а несколько библиотек, при этом не все из которых заголовочные, с зависимостями друг между другом и от внешних библиотек?
С вишенкой на торте в виде бинарника программы.
И при этом состоящих не из полутора файлов, а из десятков структурированных по папкам.


Это всё весело, но туториалов по быстрому старту с cmake столько, что хоть на зиму соли. Нюанс, тем не менее, в том, что программы, для которых нужна такая мощная система сборки, сложнее чем заголовочная библиотека, и когда пытаешься на cmake просто сделать что-то работающее и удобное, то либо получается груда костылей, либо на написание сценариев для cmake уходит времени больше, чем на саму программу.

Это и без cmake просто делается.

Можно вообще всё руками делать. Но лучше иметь удобный инструмент.


А вот как просто организовать не одну, а несколько библиотек, при этом не все из которых заголовочные, с зависимостями друг между другом и от внешних библиотек?

На самом деле, не сильно сложнее. Если это не стёб, а реальный вопрос, то могу попробовать подготовить соответствующий материал.

Присоединяюсь к запросу на материал более высокого уровня и про кроссплатформенную сборку тоже расскажите. Большие проекты не только GCC собираются.

Но лучше иметь удобный инструмент.

Безусловно, но после тулинга в языках, авторы которых уже наелись C++, нырять во все эти ninja, cmake, autotools и прочее довольно напрягает. CMake при всех его достоинствах не самый удобный инструмент всё же.


Если это не стёб, а реальный вопрос, то могу попробовать подготовить соответствующий материал.

Не стёб, к сожалению. Адекватного материала на тему того, как удобно организовывать проекты сложнее hello world с использованием cmake и при этом без "ну, давайте тут вставим костыль, который используется в нашей компании для легаси-кода с cmake 2.8" днём с огнём не сыщешь.

Я ни одного проекта не видел без костылей. Зайдите на github, возьмите любой проект… Там такой дурдом везде, что можно плакать сразу. Это же происходит и в других коммерческих проектах. Чтобы был cmake нужно держать отдельного специалиста по cmake =)

Есть опыт в проекте, не большой, но и не hello world. Мы разбиваем проект на модули, которые собираются как библиотеки (статические, динамические, объектные — не важно). У этих целей настроены PUBLIC target_link_libraries, target_include_directories, соответственно, когда мы линкуем модуль к другому, все эти настройки подтягиваются. Так получается весьма модулльная структура, особо не зависящая от того, что где лежит и от этих include_subdirectory и прочих танцев.

А не поделитесь ли опытом, как эти модули потом находить друг относительно друга? Например есть три модуля A, B и С, при том A и B зависят от C: им нужны его инклюды и его нужно слинковать с ними. Вопрос: как организовать структуру проекта без уродливых "../" или модуля D который в правильном порядке выставляет add_subdirectory? При этом A, B и C отдельными проектами не являются (от них зависят другие модули которые потом в сумме могут использоваться в нескольких полноценных проектах), но было бы здорово собирать их в статические библиотеки для тестирования например.

Вот, я сделал пример. Надеюсь, это вам поможет.


Из недостатков (на мой взгляд), то, что модули инклюдятся как


#include "a.h"

а не как


#include "module_a/a.h"

Наверное, это весьма и весьма существенный недостаток, когда публичных хидеров в модуле много. Ах да, забыл. В папку include модулей кладутся их публичные хидера, которые должны быть видны другим. Если требуются какие-то приватные хидера, то мы кладем их прямо в src модуля.

Спасибо! Хотя это не совсем то, что я ожидал. Скорее всего я хочу слишком много.

Говоря «модули» я имел в виду отдельные проекты в терминах CMake. Например проблема в том, что я не могу собрать отдельно проект A, не собирая B и не используя CMakeLists.txt из корня.

Пишет, что пример не найден...

Есть большой опыт работы с CMake в крупных проектах по ~150-200 модулей. Используемые языки программирования C C++ Fortran.
Начинал с версии 2.8. С этого момента много чего поменялось, и меняется все к лучшему. Так как для себя явных неудобств не находил.
На самом деле порядка недели уходит для полной настройки каркаса нового проекта, связка с серверами и т.д. А после модули уже добавляются по аналогии с существующими на раз и два, проблем не наблюдаем. Отдельно выделенного человека под эти цели у нас нет :)

А как происходит подключение проекта в другой проект, чтобы get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY) работало?
Через add_subdirectory, ExternalProject, include() или find_package(… PATHS ...)?

Мне вот недавно пришлось собирать gRPC под достаточно большой зоопарк компиляторов. Если бы я не перечитал содержимое папки test, я бы очень долго возился с тем, чтобы получить gRPC, которы находятся через find_package.
Очень много надо делать велосипедов, чтобы нормально поддержать debug/release. Не говоря уже об автоматическом создании сборок cmake package.

Да, к сожалению, цмейком многие пользуются неправильно или не до конца правильно. Поэтому иногда приходится делать работу за таких горе-разработчиков библиотек. Однако, если следовать лучшим практикам, то всё легко и просто.

Ещё, к сожалению, стандартные скрипты тоже далеки от совершенства. Простой пример — собираем zlib с помощью CMake. И вот без танцев с бубном линковаться с ним статически не получается. Хотя это очень популярная библиотека.
По факту, нужно очень много велосипедить, чтобы получить красивую сборку. А если разрабатываешь не конечное приложение, а какой-либо SDK, то вообще мрак. Но за неимением альтернатив приходится пользоваться. Нормальных руководств я так и не нашёл, собираю везде знания по крупицам. Дошло до того, что пишу сейчас свой велосипед на CMake, который собирает нужные мне thirdparty. Может, выложу его в open source.

Если я правильно понимаю, так называемые "стандартные" скрипты тоже пишут сами разработчики библиотек.

На самом деле, всё зло, как обычно, от наследия царского режима. А именно, от версии 2.x. Многие скрипты и учебники либо оттуда, либо тащат за собой весь этот хлам.

За последнее время было несколько докладов про современный cmake.
И даже статья на хабре есть: https://habr.com/ru/post/330902/



Так, CMake значит говорите. Говорите, что с помощью CMake можно обеспечить статический анализ с минимальными телодвижениями? Понятненько… Ok, мы идём к этому проекту.

И, видимо, статический анализ обошел сам CMake. Ждите скоро статью. Коллега как раз пишет и пока раздумывает, поликорректно ли использовать в названии слово «говнокод» :).

Даже если и так, то мне не известно ни одного случая, когда CMake упал бы или сделал что-то не то, что его просили.
Что, впрочем, не отменяет культуру разработки.

В embedded сталкивался. Когда cmake не верно передавал g++ параметры linker-а. В 3.10.1 кажется. В 3.14 уже поправили.

Пожалуй, я наврал. Сейчас подумал и тоже вспомнил случай, когда от порядка перечисления компонент в команде target_link_libraries зависел конечный результат.

Дык самому линкеру важен порядок аргументов.

Ну, как сказать… с одной стороны, язык декларативный, то есть порядок выполнения он выбирает сам. С другой стороны, в некоторых случаях порядок описания сборки важен. И перечисление либ для линковщика это ещё самое очевидное.

Хорошая статья! Я бы ещё упомянул модуль GNUInstallDirs — мне кажется, что это более правильный способ указания target install directories. Плюс, для полноты картины можно подробнее рассказать о тестировании (add_test, etc.).

CTest не люблю, потому что он заставляет каждый тест оформлять в виде отдельного исполняемого файла, а это, мягко говоря, не очень удобно.
В проекте-шаблоне я использую сторонний тестовый фреймворк, так что и в статье про CTest ни слова.

Ну, вообще-то необязательно.
Если тест-фреймворк такой, что всё пихает в единственный файл и отдельные тесты там нельзя выбрать — это проблема фреймворка. А если можно выбрать — то единственный файл не помеха запуску отдельных тестов. И даже (если фреймворк популярен) скорее всего уже и модуль для cmake соответствующий есть.


Самый очевидный, навскидку, пример — gtests. Да, единственный бинарь. Но модуль cmake умеет его исследовать и создаёт множество соответствующих тестов для ctest. В каждом запускается бинарь с нужными ключами, чтобы выполнился именно конкретный тест, а не вся сюита.


Ну и плюшки в виде экспорта результата (тестов, а заодно вывода конфигурации, компиляции, coverage и санитайзеров) в xml и загрузки всего этого добра в cdash (который отлично живёт в докере). Или несложного преобразования в формат junit и закачки туда, где его понимают (например, gitlab CI)

Создавать бинарник со всеми тестами, а потом запускать его N раз по разу на каждый тест, когда можно запустить один раз и получить тот же результат — неэффективно, долго и бессмысленно.


И, главное, для чего? Просто чтобы использовать CTest?


… плюшки ...

Все перечисленные вещи есть в Catch2 или doctest. При этом, в отличие от gtest, их не нужно компилировать и линковать.

Опции компиляции

Вот и недоработки CMake на лицо. На msvc это работать не будет, придётся другие опции указывать. А способов задавать компиляторонезависимые опции в CMake пока ещё не завезли.
Вы еще скажите, что кросс-компиляции с cmake не бывает.

В какой-то степени завезли. Хотя бы версию C++ или всякие там "хочу в целом собираться с отладкой" можно указать.

Базовые опции в зависимости от вида сборки (debug/release и т.д.) уже есть встроенные и достаточно кросс-компиляторные (тем, кто по-привычке вручную конфигурирует -O0 -g для debug, -O2 для release и т.д. рекомендую попробовать их убрать и посмотреть, как будет без них (make VERBOSE=1 покажет)). А специфические (включить какой-нибудь санитайзер или thread_safety_analysis для clang) на то и специфические, что в других компиляторах просто отсутствуют. Да, там другие опции.
Разные пробрасываемые определения, которые в gcc задаются через -D а в msvc через /D cmake и так умеет. И в процессе развития научился различать определения, опции компилятора, опции линкера, инклюды и т.д. (раньше почти всё приходилось через add_definitions добавлять, теперь есть разные варианты)

Честно говоря, никак не отношусь.
Генерировал тем же си-мейком, но не увидел никакой разницы в положительную сторону по сравнению с make.
Отдельно от CMake не пробовал.

У ниндзи единственное преимущество по сравнению с make — это автоматическое распараллеливание билда при вызове без дополнительных опций. Для make придется указывать опцию -j, чтобы задать сборку в несколько потоков.

А как правильно добавить python bindings для pybind11, boost python, swig. И так что бы можно было потом установить отдельно библиотеку, отдельно bindings, пакетом и через pypi/conda

Не совсем понятно что именно вы хотите сделать, но посмотрите вот на этот проект: https://github.com/scikit-build/scikit-build


Чтобы что-то ставить через conda, нужно писать рецепт для conda, и я вообще не советовал бы пользоваться кондой. Как высказался про конду один компетентный товарищ:


Ужасная кодовая база, беспорядок в рецептах, устаревшие версии пакетов, отсутствие нативной поддержки pypi и чего бы то ни было ещё, без рецептов.

А ещё conda очень медленная.

Не стану спорить, что у конды есть недостатки, но иногда она позволяет легко создать python-окружение там, где иначе на это требуется слишком много усилий. За линк спасибо, но раскрыть этот вопрос в туториале, как это сделано для документации и тестов не помешало бы.
UFO just landed and posted this here
Sign up to leave a comment.

Articles