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

Дано: живой проект, который активно развивается, и собирается при помощи рекурсивной сборочной системы на основе GNU Make. Те года, что проект существует, он успешно масштабировался и прорастал этой системой сборки все глубже. Так зачем от нее отказываться в пользу чего-то другого?

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

Так, например, разработчики писали код в самых разных редакторах (тут уж кому какой больше по вкусу), сборку компонентов производили в контейнере в терминале, оттуда же развёртывали компоненты на виртуальную машину через SSH. Понятное дело, что эти действия были так или иначе заскриптованы, однако такой подход все еще остаётся излишне рукописным.

Вопросы дебаггинга, удобства просмотра документации и работы с нашим API для заказчиков также оставались по сути открытыми. Плюс к этому, синтаксис рекурсивной сборочной системы на основе GNU Make казался нам излишне громоздким, а также слишком чувствительным к пробелам, табуляциям, переносам строк.

Назрeла необходимость во внедрении решения, которое бы было менее сложным с точки зрения синтаксиса, и, при этом, предоставляло возможность использования IDE из состава нашего контейнера (Qt Creator). Так мы пришли к системе сборки CMake.

Исходное состояние

Давным давно проект представлял собой одно монолитное приложение, которое решало задачу одного конкретного заказчика. Затем, с течением времени, архитектура проекта стала усложняться, и он эволюционировал в полноценный картографический фреймворк для ЗОСРВ "Нейтрино", на основе которого наши заказчики могут решать свои задачи планирования, навигации, и так далее. По мере усложнения, в проекте появлялись open-source библиотеки, которые собирались с другими системами сборки, отличной от нашей. Позже, для демонстрации работы фреймворка нам потребовалось написать несколько приложений с графическим интерфейсом - стали использовать Qt, и пойдя по пути наименьшего сопротивления, в качестве системы сборки для них был выбран qmake.

Консольные приложения, библиотеки

Графические приложения

Open-source библиотеки

Make

qmake

CMake
Autotools
Make

Как можно видеть, в проекте присутствовало целое разнообразие разных систем сборки. Как же всё это добро собиралось? При помощи специальных wrapper-ов. Например, для qmake обёртка Make над .pro файлами системы qmake выглядит следующим образом:

ifndef QCONFIG
  QCONFIG=qconfig.mk
endif
include $(QCONFIG)
include $(MKFILES_ROOT)/qmacros.mk
include $(MKFILES_ROOT)/qmake.mk
include $(MKFILES_ROOT)/qtargets.mk

Ключевая строка здесь - под номером 7, именно тут происходит подключение модуля работы с qmake. Вся основная информация о сборке компонента лежит в .pro файле, как и обычно у всех Qt-проектов. Обёртки для CMake и Autotools выглядят аналогично, отличие лишь в той же строке.

include $(MKFILES_ROOT)/cmake.mk
или
include $(MKFILES_ROOT)/autotools.mk

Таким образом мы добились того, что весь проект собирался из корня одной командой:

make

Начинаем переход

Как мы решили организовать работу по решению такой большой задачи, как смена система сборки? В первую очередь, проанализировали open-source библиотеки, как не зависящий от нас фактор - убедились, что в каждой их них предусмотрена возможность сборки с CMake. Далее, декомпозировали задачу, разбив на три основных этапа:

Make -> CMake

Проще всего было переводить на CMake консольные утилиты, которые написаны на Make. С них мы в первую очередь и начали, а конкретнее - с утилит, состоящих из одного файла исходного кода, и закончили ядром картографического сервиса как наиболее ответственным компонентом проекта.

Немного в базовом синтаксисе CMake. Вот так выглядит базовый CMakeLists.txt для проекта Hello, World!:

cmake_minimum_required( VERSION 3.23 FATAL_ERROR )

project( hello LANGUAGES C )

add_executable( hello hello.c )

install( TARGETS hello DESTINATION bin )

А теперь возьмем как пример простейшую утилиту, которая просто читает заголовок файла во внутреннем формате и выводит информацию в консоль. Утилита состоит всего из одного исходника. На картинке ниже можно видеть различие в синтаксисе сборочных файлов. Даже для такой простой программы видно, что файл common.mk рекурсивной сборки (справа) содержит в себе множество директив include, а базовые конструкции, такие как подключение библиотек и указание флагов компилятора содержат в себе сокращения, которые требуют некоторого времени на осознание (EXTRA_LIBVPATH, EXTRA_INCVPATH, CCFLAGS), особенно для начинающих разработчиков. Также для рекурсивной системы сборки необходимо указание EXTRA_LIBVPATH, в то время как для CMake такая конструкция является излишней. На наш субъективный взгляд, язык CMake предоставляет более человеко-читаемые конструкции, которые позволяют читать CMakeLists.txt как сочинение на английском языке, что намного приятнее глазу.

CMake (слева) и GNU Make (справа)
CMake (слева) и GNU Make (справа)

qmake -> CMake

Завершив этап с консольными приложениями и сервисами, мы приступили к графическим приложениям.

Вот так выглядит базовый CMakeLists.txt для проекта с использованием Qt (отличия от простого примера с Hello, World! выделил комментариями):

cmake_minimum_required( VERSION 3.23 FATAL_ERROR )

project( hello LANGUAGES CXX )

set( CMAKE_AUTOMOC ON )  # Обработка мета-объектным компилятором (moc)
set( CMAKE_AUTOUIC ON )  # Обработка файлов интерфейса (.ui)
set( CMAKE_AUTORCC ON )  # Обработка файлов ресурсов (.qrc)

add_executable( hello hello.c )

find_package( Qt5
              COMPONENTS
              Core
              REQUIRED )  # Находим установленные библиотеки Qt

target_link_libraries( hello
                       Qt5::Core )  # И линкуем их к приложению

install( TARGETS hello DESTINATION bin )

Для 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 -> CMake

До этого момента мы затрагивали исключительно наши собственные разработки. Однако, мы еще собираем из исходников 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.

Открытый проект в Qt Creator. Можно заметить, что у каждой директории виднеется логотип CMake
Открытый проект в Qt Creator. Можно заметить, что у каждой директории виднеется логотип CMake

Здесь следует отметить, что портирование новой версии обошлось не совсем гладко, ровно как и переключение с Autotools на CMake. Пришлось вносить какие-то микрофиксы в файлы сборки библиотек, вроде отключения сборки тестов и документации или добавления флагов компилятора с целю сократить количество мата, выпадающего в лог. Опустим эти моменты, поскольку они являются мелкими шероховатостями и рутинной работой.

Как итог, мы получили полноценную иерархию проекта! Проверить ее можно, открыв проект в IDE. Забегая вперед, скажу, что мы используем Qt Creator и проверяли "открываемость" проекта именно в нем. Подробнее об использовании Qt Creator будет дальше, пока что лишь приведу скриншот, демонстрирующий тот результат, который мы так долго ждали!

Выгода от перевода на CMake

Использование IDE

Переход на систему сборки CMake дал нам возможность использовать полноценную IDE, в частности мы пользуемся Qt Creator из состава контейнера для разработчиков нашей команды.

Выбираем головной CMakeLists.txt файл в меню Qt Creator и у нас открывается проект. Мы можем собирать его как целиком, так и по отдельным компонентам. Поскольку теперь многие действия выполняются разработчиками из одного окна IDE, стало намного удобнее развёртывать компоненты проекта на целевом устройстве, а также производить дебаг отдельных частей проекта, буквально по нажатию одной кнопки.

Отдельно очень важным считаю отметить, что в Qt Creator интегрирована наша внутренняя документация, которая позволяет по нажатию кнопки F1 при наведении курсора на функцию/тип/класс открыть прямо в IDE интересующую страницу документации. Почитать статью о нашем подходе к ведению документации можно по этой ссылке, а более подробный разбор интеграции справки в Qt Creator описан в этой статье.

Проект, открытый в Qt Creator с открытой страницей документации
Проект, открытый в Qt Creator с открытой страницей документации

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

Упрощен анализ зависимостей

Использование сборочной системы CMake позволяет нам анализировать зависимости проекта в виде графа при помощи инструмента Graphviz. Он работает как с зависимостями внутри проекта, так и со сторонними зависимостями. Так, вызвав команду следующую команду, мы получаем файл, содержащий в себе граф, описанный на языке DOT:

cmake --graphviz=graph.dot .

Содержимое файла можно отредактировать на свой вкус при помощи какого-нибудь парсера. Это особенно актуально, если не хочется видеть все зависимости между модулями какой-нибудь open-source библиотеки в составе вашего проекта, или например, не хочется видеть все зависимости от Qt.

Затем полученный файл можно визуализировать любым удобным способом и сохранить в виде изображения. Например, командой:

dot -Tsvg graph.dot -o graph.svg

Еще удобно пользоваться онлайн-визуализатором, выполняющим те же функции.

Максимально упрощённый и причёсанный граф, привожу его исключительно для демонстрации того, что примерно выдает инструмент на выходе. Реальный граф будет в разы крупнее и содержать сотни стрелок.
Максимально упрощённый и причёсанный граф, привожу его исключительно для демонстрации того, что примерно выдает инструмент на выходе. Реальный граф будет в разы крупнее и содержать сотни стрелок.

Такой граф можно улучшить, добавив ему цвета и расставив блоки по-своему. Увы, это уже ручная работа, однако зависимости меняются крайне редко, поэтому один раз можно засучить рукава. На основе полученного графа мы нарисовали красивую схему зависимостей, которая у нас красуется в головном README репозитория проекта и позволяет разработчикам самостоятельно разбираться, что от чего у нас в проекте зависит.

Оформленная схема зависимостей с разделением по типам компонентов (цвет, форма блоков, тип линий).
Оформленная схема зависимостей с разделением по типам компонентов (цвет, форма блоков, тип линий).

Время сборки

Несмотря на то, что CMake использует под капотом те же Makefile, время сборки существенно сократилось. Замер проводился на моей инструментальной машине в контейнере, сборка проводилась в 8 потоков только для аппаратной платформы x86.

Команда сборки для Make:

CPULIST=x86 make -C src install -j8

Команды конфигурации и сборки для СMake (генератор Unix Makefiles):

cmake --toolchain=$KPDA_HOST/mk/cmake/toolchain-nto-x86.cmake ..
cmake --build . -j8

Команды конфигурации и сборки для СMake (генератор Ninja):

cmake -G "Ninja" \
      -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja \
      --toolchain=$KPDA_HOST/mk/cmake/toolchain-nto-x86.cmake ..
cmake --build . -j8

Результаты следующие:

Сборочная система

Время сборки (мин:сек)

Рекурсивный Make

05:22

CMake (генератор Unix Makefiles)

03:02

CMake (генератор Ninja)

03:01

Синтаксис

На наш субъективный взгляд, язык CMake легче читается. Также, по нашей практике, он еще и легче в освоении для стажеров-разработчиков, плюс многие студенты так или иначе сталкивались с CMake в решении своих академических задач. Иными словами, найти новые кадры в проект становится легче и меньше времени тратится на обучение специалиста "уже на месте". Вдобавок, при передаче исходного кода наших демонстрационных приложений нашим заказчикам, у них появляется меньше вопросов касаемо системы сборки, поскольку с CMake знакомы практически все.

Приведем пару примеров улучшения читаемости файлов сборки.

Пример: линковка статической библиотеки

Ниже представлены строки из common.mk для разделяемой библиотеки, которая статически линкует в себя еще одну библиотеку:

EXTRA_LIBVPATH += $(PROJECT_ROOT)/../my_static_lib/$(OS)/$(CPU)/a.shared$(subst so,,$(COMPOUND_VARIANT))
LDFLAGS += -Wl,--whole-archive -lmy_static_libS -Wl,--no-whole-archive

Тут можно видеть, что в переменную EXTRA_LIBVPATH мы помещаем некоторый путь, где, очевидно, лежит статическая библиотека. Поскольку статические библиотеки не инсталлируются, нам необходимо знать точный путь до нее в директории сборки. Приведу краткое объяснение, полное и более технически корректное можно прочитать в нашей официальной документации. Итак, простыми словами, в рекурсивной сборочной подсистеме директория сборки определяется следующим образом:

1) В директории компонента обязательно есть директория с фиксированным именем для целевой ОС (например, linux64, nto, win32, win64). В нашем случае разработка ведется под ЗОСРВ "Нейтрино", поэтому далее рассмотрим содержимое папки nto.

2) В директории целевой ОС располагаются директории по аппаратным платформам. Например, x86, arm, ppc, e2k (Эльбрус), mips.

3) В директории целевой аппаратной платформы мы выбираем вариант платформы, как правило, это означает порядок байтов (big-endian, little-enidan).

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

Теперь посмотрим как эта операция выглядит в CMake:

target_link_libraries( gishelper my_static_lib )

И всё! Правда, для полной картины необходимо привести CMakeLists.txt для my_static_lib:

add_library( my_static_lib STATIC ${SOURCES} )

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

Пример: линковка произвольных файлов в бибиотеку

Одной из нетривиальных задач при сборке одной из наших библиотек является запаковка многочисленных .csv файлов в один объектный файл, который затем будет прилинкован к самой библиотеке, внутри которой мы будем потом этот файл читать при помощи специальных символов _binary_*_start[] и _binary_*_end[]. Для превращения пачки .csv файлов в один объектник вызывается линковщик. На Make такая операция имела довольно страшный синтаксис:

RESOURCES:=*.csv
EXTRA_OBJS=$(addsuffix .o,$(RESOURCES))
%.csv.o:
	cp $(subst .o,,$(PROJECT_ROOT)/palettes/$@) .
	$(LDPREF) -nostdlib -Wl,-r -Wl,-b -Wl,binary -o palettes.csv.o $(subst .o,,$@)
	rm $(subst .o,,$@)

Аналогичная операция на CMake:

set( SRCPATH    ${CMAKE_CURRENT_SOURCE_DIR}/palettes )
set( DSTPATH    ${CMAKE_BINARY_DIR}/lib/gishelper )

add_custom_command( OUTPUT              ${DSTPATH}/palettes.csv.o
                    WORKING_DIRECTORY   ${SRCPATH}/palettes
                    COMMAND             ${LINKER} -nostdlib -r -b binary -o palettes.csv.o *.csv
                    COMMAND             ${CMAKE_COMMAND} -E copy palettes.csv.o ${DSTPATH}/palettes.csv.o
                    COMMAND             ${CMAKE_COMMAND} -E rm   palettes.csv.o )

add_custom_target( linking_extra_object_files
                   DEPENDS
                   ${CMAKE_BINARY_DIR}/lib/gishelper/palettes.csv.o )

add_dependencies( gishelper linking_extra_object_files )

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

С какими трудностями мы столкнулись

Потеря совместимости с Qt 4.8.7.

А именно отказ от его поддержки ввиду отсутствия необходимых .cmake файлов, необходимых для работы функции find_package() языка CMake. Вполне резонно можно сказать, что поддержка такой версии Qt в 2023 году это жуткий архаизм, однако, еще буквально недавно некоторые наши заказчики пользовались именно этой версией Qt. Теперь мы переползли на Qt 5.12.2.

Портирование новых версий open-source библиотек

Например, одна из основополагающих 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.


Комментарии (31)


  1. Sazonov
    14.12.2023 08:53

    Попробуйте ещё vcpkg для зав исимостей. Если хорошо его изучить, то это значительно упростит вам работу с third party.

    А можете привести реальный пример из жизни, когда была реальная необходимость поддержки единой кодовой базы совместимой с обеими мажорными версиями Qt? Обычно таким заморачиваются только при миграции с одной версии на другую.


    1. missing_id Автор
      14.12.2023 08:53

      Спасибо за рекомендацию, рассмотрим vcpkg, когда дойдут руки!

      А что касается поддержки разных мажорных версий Qt, то здесь мы ориентировались на потребность наших заказчиков: какую версию Qt они использовали для разработки своего ПО на основе нашего продукта. Сейчас запроса на Qt 4.8.7 уже не осталось, поэтому эту совместимость мы решили более не поддерживать в угоду развитию проекта.


      1. Sazonov
        14.12.2023 08:53

        Начать с vcpkg несложно. Вытянуть с гита и передать путь одним аргументом в CMAKE_TOOLCHAIN_FILE. И оппа, у вас из коробки сами собираются нужные зависимости (vcpkg install список чего нужно).


        1. eao197
          14.12.2023 08:53

          ИМХО, при работе c vcpkg следует сразу начинать с файлов манифестов (vcpkg.json), чтобы в каждом проекте можно было держать именно нужные версии зависимостей. Ибо при наличии единственного "глобального" vcpkg и ручного vcpkg install ... очень легко случайно обновиться до самых свежих, но несовместимых с предыдущими, версий зависимостей.


          1. Sazonov
            14.12.2023 08:53

            И да и нет. Не буду спорить, тут надо понимать что делаешь и это уже не совсем базовое использование. Попробую кратко рассказать про свой костыльный подход. Возможно я сделал велосипед и для моих нужд можно средствами vcpkg создать набор автономных собранных пакетов. Но мне удобно всем этим рулить через vcpkg.json.

            Я использую манифест в каждом проекте, но внутри cmake я принудительно выключаю режим манифеста. Цель: исключить запуск vcpkg на стадии конфигурирования проекта и исключить копирование собранных пакетов в build директорию. У меня есть скрипт который берёт манифест и ставит его содержимое в installed для vcpkg. Я осознанно ставлю специфические версии внутрь инстанса vcpkg.

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

            Всё это позволяет использовать версионирование, но при этом даёт полный контроль над тем, когда запускается vcpkg. Это очень удобно на CI в контейнерах, где до начала сборки проекта можно все third party «закэшировать» в отдельный слой.


            1. eao197
              14.12.2023 08:53

              Цель: исключить запуск vcpkg на стадии конфигурирования проекта и исключить копирование собранных пакетов в build директорию.

              А зачем это может потребоваться? Что-то я не понял :(


              1. Sazonov
                14.12.2023 08:53

                Пример из недавнего прошлого. Требование к CI: в чистую папку выкачать проект, скомпилировать, прогнать тесты, собрать дистрибутив.

                vcpkg на распаковку кэша (время на первую сборку опустим) тратил порядка 7-9 минут на билд агенте. Из тяжеловесов там был буст и кутэ.

                Поэтому vcpkg install с передачей манифеста зашивается в докер. А сама сборка (из запущенного контейнера) уже видит все нужные зависимости.


  1. abagnale
    14.12.2023 08:53

    target_link_libraries(ㅤㅤㅤtargetㅤㅤㅤㅤㅤ ㅤㅤㅤPUBLIC Qt5::Core ㅤㅤㅤQt5::Gui ㅤㅤㅤQt5::Widgets)

    У вас target_link_libraries() везде без ключевого слова (то есть по умолчанию PRIVATE), а здесь PUBLIC (что, кстати, приведёт к ошибке при конфигурации). Это специально так (тогда зачем)? Просто если вы соберёте статический Qt, а ваша библиотека будет динамическая, то этот PUBLIC заставит следующий в цепочке проект (который будет линковаться с вашей библиотекой) тоже искать бинарники Qt. Но у вас тут не библиотека, так что наверное не очень важно.

    add_library(my_static_lib STATIC ...)

    Я бы не рекомендовал хардкодить тип библиотеки, это должно контролироваться через BUILD_SHARED_LIBS при конфигурации. Но если она у вас не экспортирует никаких символов, тогда да, имеет смысл.

    install(TARGETS hello DESTINATION bin)

    Вместо руками написанных hello и bin лучше было бы сделать так (для библиотеки инструкций будет побольше, но пути такие же):

    include(GNUInstallDirs)
    install(TARGETS ${PROJECT_NAME}
        # можно даже их вообще не прописывать, они и так по умолчанию такие,
        # только лучше тогда CMAKE_INSTALL_PREFIX явно указать при конфигурации,
        # иначе он попытается поставить в /usr/local или Program Files 
        #RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} # bin
    )
    


    1. Playa
      14.12.2023 08:53

      install(TARGETS ${PROJECT_NAME}

      Так не пойдет, если таргетов несколько.


      1. abagnale
        14.12.2023 08:53

        Ну это очевидно, как бы.
        Но мне пока чаще попадались проекты с одним таргетом на один CMakeLists.txt.


  1. ruraspb
    14.12.2023 08:53

    Работа проделана огромная. Но ещё столько же впереди, если учесть комментарии.


  1. DungeonLords
    14.12.2023 08:53

    А чем add_executable отличается от функции qt_add_executable (из мира наступившего Qt6)?


    1. Sazonov
      14.12.2023 08:53

      qt_add_executable это обвязка которая делает очень много работы связанной с тулзами кутэ и автоматической регистрацией типов. Хотя моё личное мнение, что это актуально только для проектов на qml. (Впрочем, на мак собирать бандлы стало возможно без лишних телодвижений)


  1. cortl
    14.12.2023 08:53

    CPULIST=x86 make -C src install -j8

    Здесь сразу производилась установка. В других замерах только сборка. Не так ли?


    1. missing_id Автор
      14.12.2023 08:53

      Да, верно. Действительно, для полноты картины следовало привести еще этап cmake install. Однако, на результирующее время это бы ничтожно повлияло, т. к. в CMake инсталляция представляет собой обычное копирование файлов. Этот этап занимает меньше секунды по времени, он практически мгновенный.


      1. cortl
        14.12.2023 08:53

        Бывает, что обычное копирование файлов занимает значительное время. На самом деле больший интерес представляет именно сборка.


  1. ReymanTi
    14.12.2023 08:53

    Результаты опроса к статье показательны: большинство проголосовавших выбирают CMake. Автор, вы пошли по верному пути, как я считаю. Для развития вашего проекта шаг однозначно правильный. Успехов!


    1. Sazonov
      14.12.2023 08:53

      Увы, cmake выбирают из-за отсутствия альтернатив :). Это наверное самое стабильное и популярное собрание костылей. Одни generator expressions чего стоят.


    1. DungeonLords
      14.12.2023 08:53

      Почему в эпоху ИИ этот самый интеллект не переводит с морально устаревшего GNU Autotools на современные системы сборки такие как cmake? Я видел множество нелепых проектов, в основном люди пытаются найти в ИИ собеседника, но кажется лучшее применение ИИ - это перевод на современные системы сборки.


      1. KanuTaH
        14.12.2023 08:53

        Ну, для начала, у CMake просто-напросто нет аналога для ряда конструкций, которые есть в "морально устаревшем" Autotools. Например, там нет аналога AC_SEARCH_LIBS, нужно будет писать свой велосипед.


        1. Sazonov
          14.12.2023 08:53

          Ээээ, а CheckCXXSourceCompiles тогда что делает?

          Я никогда не юзал autotools но вроде это оно, судя по Гуглу


          1. KanuTaH
            14.12.2023 08:53

            Нет, это не оно. Это то, с использованием чего придётся писать соответствующий велосипед.


            1. Sazonov
              14.12.2023 08:53

              Ну под велосипедом вы понимаете сэмулировать вызов функции по её имени? Или одну строку кода: взять адрес функции. Не выглядит особой проблемой, да и такой функционал мне не кажется нужным на регулярной основе :)

              Но ок. В целом согласен.


              1. KanuTaH
                14.12.2023 08:53

                Нет. Суть AC_SEARCH_LIBS в том, что она определяет, какую именно библиотеку из заданного списка следует подключить для того, чтобы можно было использовать некую функцию (а может быть, вообще не потребуется ничего подключать, это она тоже определяет). CheckCXXSourceCompilesделает всего лишь часть этой работы - проверяет, что некий код компилируется, а все остальное кто делать будет?


                1. DungeonLords
                  14.12.2023 08:53

                  Mой месседж был о том, что разработчикам ИИ стоило натренировать его на переписывание кода на современные системы сборки. С костылями или без - другой вопросы.
                  Но вы утверждаете что без AC_SEARCH_LIBS система сборки можно сказать неполноценная? Такое CMake решение одобряете? Вы задавались вопросом как же вся вселенная Qt собирается без AC_SEARCH_LIBS?


                  1. KanuTaH
                    14.12.2023 08:53

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

                    Такое CMake решение одобряете?

                    А где там решение? Частный случай модуля для поиска конкретно std::filesystem - это не решение.

                    Вы задавались вопросом как же вся вселенная Qt собирается без AC_SEARCH_LIBS?

                    А причём тут "вселенная Qt"? Факт, что эта конструкция широко используется в софте, использующем Autotools, а аналогичного механизма в CMake нет. Тут и до "тренировки ИИ" нужно сделать кучу работы по реализации каких-то support module, видимо.


                1. Sazonov
                  14.12.2023 08:53

                  Увы, за 15 лет мне ни разу не понадобился такой сценарий. Я всегда осознанно подключаю внешние либы и знаю что где лежит. А если для cmake написан аккуратный модуль (install config), то вообще всё прекрасно.

                  А для каких языков это работает? Только для си? Плюсы с перегрузкой функций/методов умеет? Учитывается ли разный ABI компиляторов (например при попытке подкинуть собранную mingw либу в msvc?


                  1. KanuTaH
                    14.12.2023 08:53

                    Ну, а мне приходилось использовать. Ибо на некоторых платформах для работы с сокетами надо линковать с libsocket, хочешь вызывать getnameinfo()/getaddrinfo() - вероятно, придется линковать с libresolv, а, может, с libnsl, а, может, вовсе с каким-нибудь libxnet. Более того, в некоторых версиях GCC (32-битных), если хочешь работать с атомиками, нужно линковаться с libatomic (а в некоторых - нет). В CMake определение этого всего требует некоторого количества бойлерплейта (вот здесь например зацените секс с определением библиотек, с которыми нужно линковаться для использования тех же getnameinfo() сотоварищи или getnetbyname_r()), в Autotools же подобные тесты доступны искаропки. Я использовал это для поиска C библиотек, потому что системные библиотеки только такие, плюсовые обычно не держат-с. Соответственно вопросов с манглингом имен или с ABI у меня не возникало.


                    1. Sazonov
                      14.12.2023 08:53

                      Да, теперь понимаю. У меня просто очень давно не было платформозависимых вещей. Спасибо.


  1. vladz
    14.12.2023 08:53

    В завершение, CMakePresets.json добавлять не думали?