Просто так в мире ничего не происходит. Особенно в мире разработки ПО, где если что-то работает, то лучше это лишний раз не трогать.  Дано: живой проект, который активно развивается, и собирается при помощи рекурсивной сборочной системы на основе GNU Make. Те года, что проект существует, он успешно масштабировался и прорастал этой системой сборки все глубже. Так зачем от нее отказываться в пользу чего-то другого? К поднятию вопроса о переходе на иную систему сборки нас подтолкнул тот факт, что рутинные процессы, выполняемые разработчиками ежедневно, достаточно плохо автоматизированы и требуют совершения большого количества действий.  Так, например, разработчики писали код в самых разных редакторах (тут уж кому какой больше по вкусу), сборку компонентов производили в контейнере в терминале, оттуда же развёртывали компоненты на виртуальную машину через SSH. Понятное дело, что эти действия были так или иначе заскриптованы, однако такой подход все еще остаётся излишне рукописным.  Вопросы дебаггинга, удобства просмотра документации и работы с нашим API для заказчиков также оставались по сути открытыми. Плюс к этому, синтаксис рекурсивной сборочной системы на основе GNU Make казался нам излишне громоздким, а также слишком чувствительным к пробелам, табуляциям, переносам строк.  Назрeла необходимость во внедрении решения, которое бы было менее сложным с точки зрения синтаксиса, и, при этом, предоставляло возможность использования IDE из состава нашего контейнера (Qt Creator). Так мы пришли к системе сборки CMake. Давным давно проект представлял собой одно монолитное приложение, которое решало задачу одного конкретного заказчика. Затем, с течением времени, архитектура проекта стала усложняться, и он эволюционировал в полноценный картографический фреймворк для ЗОСРВ "Нейтрино", на основе которого наши заказчики могут решать свои задачи планирования, навигации, и так далее. По мере усложнения, в проекте появлялись open-source библиотеки, которые собирались с другими системами сборки, отличной от нашей. Позже, для демонстрации работы фреймворка нам потребовалось написать несколько приложений с графическим интерфейсом - стали использовать Qt, и пойдя по пути наименьшего сопротивления, в качестве системы сборки для них был выбран qmake.  Консольные приложения, библиотеки Графические приложения Open-source библиотеки Make qmake CMakeAutotoolsMake Как можно видеть, в проекте присутствовало целое разнообразие разных систем сборки. Как же всё это добро собиралось? При помощи специальных wrapper-ов. Например, для qmake обёртка Make над .pro файлами системы qmake выглядит следующим образом: Ключевая строка здесь - под номером 7, именно тут происходит подключение модуля работы с qmake. Вся основная информация о сборке компонента лежит в .pro файле, как и обычно у всех Qt-проектов. Обёртки для CMake и Autotools выглядят аналогично, отличие лишь в той же строке.  Таким образом мы добились того, что весь проект собирался из корня одной командой: Как мы решили организовать работу по решению такой большой задачи, как смена система сборки? В первую очередь, проанализировали open-source библиотеки, как не зависящий от нас фактор - убедились, что в каждой их них предусмотрена возможность сборки с CMake. Далее, декомпозировали задачу, разбив на три основных этапа: Проще всего было переводить на CMake консольные утилиты, которые написаны на Make. С них мы в первую очередь и начали, а конкретнее - с утилит, состоящих из одного файла исходного кода, и закончили ядром картографического сервиса как наиболее ответственным компонентом проекта. Немного в базовом синтаксисе CMake. Вот так выглядит базовый CMakeLists.txt для проекта Hello, World!: А теперь возьмем как пример простейшую утилиту, которая просто читает заголовок файла во внутреннем формате и выводит информацию в консоль. Утилита состоит всего из одного исходника. На картинке ниже можно видеть различие в синтаксисе сборочных файлов. Даже для такой простой программы видно, что файл common.mk рекурсивной сборки (справа) содержит в себе множество директив include, а базовые конструкции, такие как подключение библиотек и указание флагов компилятора содержат в себе сокращения, которые требуют некоторого времени на осознание (EXTRA_LIBVPATH, EXTRA_INCVPATH, CCFLAGS), особенно для начинающих разработчиков. Также для рекурсивной системы сборки необходимо указание EXTRA_LIBVPATH, в то время как для CMake такая конструкция является излишней. На наш субъективный взгляд, язык CMake предоставляет более человеко-читаемые конструкции, которые позволяют читать CMakeLists.txt как сочинение на английском языке, что намного приятнее глазу. Завершив этап с консольными приложениями и сервисами, мы приступили к графическим приложениям. Вот так выглядит базовый CMakeLists.txt для проекта с использованием Qt (отличия от простого примера с Hello, World! выделил комментариями): Для Qt-приложений у нас достаточно лохматые и большие файлы сборки, поэтому приводить их тут будет излишне, лучше взамен приведем таблицу, содержащую в себе сравнение конструкций, используемых в qmake и CMake для достижения одинаковых целей, которые встречались в нашем проекте: Задача CMake qmake Подключение библиотек Qt find_package( Qt5ㅤㅤㅤCOMPONENTSㅤㅤㅤCore ㅤㅤㅤGuiㅤㅤㅤWidgetsㅤㅤㅤREQUIRED ) target_link_libraries( ㅤㅤㅤtargetㅤㅤㅤㅤㅤ ㅤㅤㅤPUBLIC ㅤㅤㅤQt5::Core ㅤㅤㅤQt5::Gui ㅤㅤㅤQt5::Widgets ) QT += core gui widgets Подключение ресурсов (на примере переводов Qt Linguist) qt5_add_resources( ㅤㅤㅤtarget ㅤㅤㅤFILES ㅤㅤㅤtranslations.qrc) RESOURCES += translations.qrc Инсталлирование config-файла install( TARGETSㅤㅤㅤtargetㅤㅤㅤDESTINATION ㅤㅤㅤbin )install( FILES ㅤㅤㅤconfig/*.conf    ㅤㅤㅤDESTINATIONㅤㅤㅤdata/config ) INSTALLS += target config config.path=$$INSTALLDIRECTORY/../data/config config.files=config/*.conf Здесь можно видеть, что преимущества CMake перед qmake не столь очевидны, поскольку конструкции qmake выглядят лаконичнее. Действительно, qmake предоставляет достаточно высокий уровень абстракции, но использование его для сборки компонентов, не использующих Qt (коих у нас подавляющее большинство) крайне неудобно, поэтому данный этап мы выполняли скорее для единообразия. До этого момента мы затрагивали исключительно наши собственные разработки. Однако, мы еще собираем из исходников open-source библиотеки. Дело в том, что поставлять их в бинарном виде в составе ОС или в составе комплекта разработчика излишне: утяжелять комплект разработчика и Docker-контейнеры собранными под все архитектуры библиотеками для нужд прикладного ПО ни к чему.  С open-source библиотеками по удачному стечению обстоятельств сложилось так, что они все задействуют или могут задействовать CMake. К примеру, библиотека PROJ версии 7.1.0 может собираться как с помощью CMake, так и с Autotools. Выбрали CMake, исключив обёртку в виде рекурсивной сборочной подсистемы, подключили библиотеку как зависимость с помощью обычного target_link_libraries(). Так же поступили и с другими либами. Проблема возникла с библиотекой GDAL, а точнее с ее версией. У нас в проекте была задействована версия 3.1.2, которая поддерживала исключительно Autotools. Однако, в результате недолгих изысканий в Интернете мы выяснили, что поддержка CMake в этой библиотеке была реализована начиная с версии 3.5.0. Портировали самую последнюю на тот момент версию - 3.7.1.  Здесь следует отметить, что портирование новой версии обошлось не совсем гладко, ровно как и переключение с Autotools на CMake. Пришлось вносить какие-то микрофиксы в файлы сборки библиотек, вроде отключения сборки тестов и документации или добавления флагов компилятора с целю сократить количество мата, выпадающего в лог. Опустим эти моменты, поскольку они являются мелкими шероховатостями и рутинной работой.Как итог, мы получили полноценную иерархию проекта! Проверить ее можно, открыв проект в IDE. Забегая вперед, скажу, что мы используем Qt Creator и проверяли "открываемость" проекта именно в нем. Подробнее об использовании Qt Creator будет дальше, пока что лишь приведу скриншот, демонстрирующий тот результат, который мы так долго ждали! Переход на систему сборки CMake дал нам возможность использовать полноценную IDE, в частности мы пользуемся Qt Creator из состава контейнера для разработчиков нашей команды.  Выбираем головной CMakeLists.txt файл в меню Qt Creator и у нас открывается проект. Мы можем собирать его как целиком, так и по отдельным компонентам. Поскольку теперь многие действия выполняются разработчиками из одного окна IDE, стало намного удобнее развёртывать компоненты проекта на целевом устройстве, а также производить дебаг отдельных частей проекта, буквально по нажатию одной кнопки.  Отдельно очень важным считаю отметить, что в Qt Creator интегрирована наша внутренняя документация, которая позволяет по нажатию кнопки F1 при наведении курсора на функцию/тип/класс открыть прямо в IDE интересующую страницу документации. Почитать статью о нашем подходе к ведению документации можно по этой ссылке, а более подробный разбор интеграции справки в Qt Creator описан в этой статье. Разработчики расходуют меньше времени на использование терминала для решения рутинных задач, реже переключаются между окнами редактора кода и браузера для поиска справки, быстрее проводят анализ багов и меньше времени тратят на их устранение.  Использование сборочной системы CMake позволяет нам анализировать зависимости проекта в виде графа при помощи инструмента Graphviz. Он работает как с зависимостями внутри проекта, так и со сторонними зависимостями. Так, вызвав команду следующую команду, мы получаем файл, содержащий в себе граф, описанный на языке DOT: Содержимое файла можно отредактировать на свой вкус при помощи какого-нибудь парсера. Это особенно актуально, если не хочется видеть все зависимости между модулями какой-нибудь open-source библиотеки в составе вашего проекта, или например, не хочется видеть все зависимости от Qt.  Затем полученный файл можно визуализировать любым удобным способом и сохранить в виде изображения. Например, командой: Еще удобно пользоваться онлайн-визуализатором, выполняющим те же функции. Такой граф можно улучшить, добавив ему цвета и расставив блоки по-своему. Увы, это уже ручная работа, однако зависимости меняются крайне редко, поэтому один раз можно засучить рукава. На основе полученного графа мы нарисовали красивую схему зависимостей, которая у нас красуется в головном README репозитория проекта и позволяет разработчикам самостоятельно разбираться, что от чего у нас в проекте зависит. Несмотря на то, что CMake использует под капотом те же Makefile, время сборки существенно сократилось. Замер проводился на моей инструментальной машине в контейнере, сборка проводилась в 8 потоков только для аппаратной платформы x86.  Команда сборки для Make: Команды конфигурации и сборки для СMake (генератор Unix Makefiles): Команды конфигурации и сборки для СMake (генератор Ninja): Результаты следующие: Сборочная система Время сборки (мин:сек) Рекурсивный Make 05:22 CMake (генератор Unix Makefiles) 03:02 CMake (генератор Ninja) 03:01 На наш субъективный взгляд, язык CMake легче читается. Также, по нашей практике, он еще и легче в освоении для стажеров-разработчиков, плюс многие студенты так или иначе сталкивались с CMake в решении своих академических задач. Иными словами, найти новые кадры в проект становится легче и меньше времени тратится на обучение специалиста "уже на месте". Вдобавок, при передаче исходного кода наших демонстрационных приложений нашим заказчикам, у них появляется меньше вопросов касаемо системы сборки, поскольку с CMake знакомы практически все.  Приведем пару примеров улучшения читаемости файлов сборки. Ниже представлены строки из common.mk для разделяемой библиотеки, которая статически линкует в себя еще одну библиотеку: Тут можно видеть, что в переменную EXTRA_LIBVPATH мы помещаем некоторый путь, где, очевидно, лежит статическая библиотека. Поскольку статические библиотеки не инсталлируются, нам необходимо знать точный путь до нее в директории сборки. Приведу краткое объяснение, полное и более технически корректное можно прочитать в нашей официальной документации. Итак, простыми словами, в рекурсивной сборочной подсистеме директория сборки определяется следующим образом: 1) В директории компонента обязательно есть директория с фиксированным именем для целевой ОС (например, linux64, nto, win32, win64). В нашем случае разработка ведется под ЗОСРВ "Нейтрино", поэтому далее рассмотрим содержимое папки nto. 2) В директории целевой ОС располагаются директории по аппаратным платформам. Например, x86, arm, ppc, e2k (Эльбрус), mips. 3) В директории целевой аппаратной платформы мы выбираем вариант платформы, как правило, это означает порядок байтов (big-endian, little-enidan). Зная весь этот механизм, можем разгадать, какой путь нам нужен и поместить его в файл сборки. Выглядит громоздко и излишне сложно. Теперь посмотрим как эта операция выглядит в CMake: И всё! Правда, для полной картины необходимо привести CMakeLists.txt для my_static_lib: Нет загадочных сложных путей, их построение мы делегировали CMake. Плюс, мы получили построение зависимости: перед сборкой разделяемой библиотеки у нас автоматически происходит сборка статической, без каких-либо дополнительных команд. А если некоторое приложение будет линковать эту разделяемую библиотеку, то CMake раскрутит эту связку зависимостей, и будет собирать сначала статическую библиотеку, затем разделяемую, а затем уже приложение. Одной из нетривиальных задач при сборке одной из наших библиотек является запаковка многочисленных .csv файлов в один объектный файл, который затем будет прилинкован к самой библиотеке, внутри которой мы будем потом этот файл читать при помощи специальных символов _binary_*_start[] и _binary_*_end[]. Для превращения пачки .csv файлов в один объектник вызывается линковщик. На Make такая операция имела довольно страшный синтаксис: Аналогичная операция на CMake: Да, строк получилось больше, но зато код выглядит куда опрятнее и читабельнее. А именно отказ от его поддержки ввиду отсутствия необходимых .cmake файлов, необходимых для работы функции find_package() языка CMake. Вполне резонно можно сказать, что поддержка такой версии Qt в 2023 году это жуткий архаизм, однако, еще буквально недавно некоторые наши заказчики пользовались именно этой версией Qt. Теперь мы переползли на Qt 5.12.2. Например, одна из основополагающих open-source библиотек проекта - GDAL - до перехода на CMake была версии 3.1.2 и собиралась исключительно при помощи Autotools, однако поддержка CMake внутри самого GDAL появилась лишь в версии 3.5.0. Пришлось портировать самую последнюю на тот момент версию - 3.7.1. Обошлось без каких-либо явных проблем, за исключением того, что для сборки GDAL под архитектуру Эльбрус (e2kle) было необходимо явно отключать включенные по умолчанию опции BUILD_APPS и BUILD_TESTS, поскольку компилятор lcc от МЦСТ выдает чрезмерно много ошибок и варнингов. Поскольку эти тесты и приложения не являются важными компонентами, да и в принципе являются опциональными, они для этой аппаратной платформы не собираются. Наша собственая система тестирования проекта никаких отклонений в работе непосредственно библиотеки не выявила. Пришлось привыкнуть к тому, что инсталляция зависимостей таргета происходит только после окончания сборки самого таргета. Например, чтобы собрать и установить наш проект с помощью Make выполнялась комнада:CPULIST=x86 make -C src install -j8Каждый собранный компонент устанавливался сразу после своей сборки, не дожидаясь пока завершится сборка всех таргетов в проекте. На мой взгляд, это очень удобно, и для меня было удивлением, что подобного механизма CMake не предоставляет. CMake придерживается идеи трёх стадий сборки: конфигурация, сборка, установка. На этапе конфигурации генерируются необходимые для сборки файлы в  ${CMAKE_BINARY_DIR} с расширением .cmake и .make или .ninja. Эти файлы подготавливаюстя для второго этапа - непосредственно компиляции и линковки компонентов проекта. Последний же этап - этап установки - не представляет собой ничего, кроме простого копирования всех файлов из ${CMAKE_BINARY_DIR} в папку инсталляции.  Появление новых варнингов при сборке. Большинство из них были действительно по делу, их причины были устранены. Те, причины которых показались нам ошибочными мы заглушили флагами компилятора, особенно если речь о сборке open-source библиотек. Приятно видеть лог, который состоит исключительно из текущего состояния сборки и собираемого объектника, без засоренности лога. Переход на новую систему сборки дал нам не только те преимущества, что были описаны выше, но и потенциально принесет нам в будущем новые: Усовершенствование системы тестирования.С CMake мы получаем возможность легкой интеграции CTest или GTest. Можно будет отказаться от самописной системы из собственных shell-скриптов и юнит-тестов, которые лежат в отдельном репозитории, как будто бы "обособленно" от проекта.  Возможность формирования разных конфигураций сборки - Debug/Release. Упаковка с помощью CPack.  Ведущий инженер-программист