Comments 23
А какая разница как описать структуру проекта? Декларативно или в виде набора команд с флагами разными?
Вот тут я не соглашусь. Ну точней, если у вас уже есть какой-нибудь обширный «билд-фремворк» для redo в который вы прсто вставляете свою «структуру проекта». То наверно можно сказать что будет ± одинаково. Но если писать все с нуля, то на спецализированных системах/языках все будет гораздо компактней и скорее всего понятней.
К слову, CMake умеет такую штуку как INTERFACE_* свойства таргетов, которые подтягиваются из зависимостей. Умеет ли redo вытаскивание значений свойств из зависимостей, без замусоривания глобального контекста?
А какая разница как описать структуру проекта? Декларативно или в виде набора команд с флагами разными? Грубо говоря, количество информации которое вам, как разработчику, нужно ввести в компьютер, чтобы объяснить ему правила для сборки — и в том и в другом случае одинаковое
Ну конечно же нет, в том то и дело. Количество информации различается на порядки. Команды и флаги будут разными на разных системах, компиляторах, дистрибутивах и версиях одного дистрибутива. Например, чтобы подключить потоки может потребоваться -pthread
, -lpthread
, -lthr
только на BSD. -ldl
на FreeBSD не существует, а на Linux нужен. C++17 может включаться как -std=c++17
, -std=c++1z
, /std:c++17
, где-то вообще быть по умолчанию. Банальная сборка статической библиотеки это два ни разу не очевидных вызова ar/runlib только на *nix, в windows вообще по другому. У install есть несовместимости в аргументах между GNU и BSD версиями. Как и у sed, awk, grep и что вы там ещё будете звать из ваших скриптов.
В CMake каждая из этих операций делается ровно одной строкой, которая описывает сама себя и работает везде, даже на solaris и haiku, даже если вы о них первый раз слышите.
Немного оффтопик. Можете пояснить, почему все так любят ninja? Мне его много кто советовал.
Мой стек: windows/cmake/ninja/clang (CLion) не может собрать gRPC, валится с непонятными ошибками.
Clang откуда стандартную библиотеку берет? Microsoft VS? MinGW?
ninja все любят, потому что он быстро работает. Прямо никто файлы для ninja не пишет, подразумевается, что они будут генерироваться чем-то более высокоуровневым: Autotools(гипотетически), Meson, CMake и т.д.
В действительно крупных проектах — типа Chrome, для которого и разрабатывался ninja — разница по скорости сборки бывает на порядки.
Рядовым же проектам все равно :-)
Подход интересный, но в современном мире у make осталась слишком узкая ниша чтобы переживать из-за его проблем.
Если мы про сборку ПО, то уж извините, но никто кроме горстки радикалов которым не угодил размер CMake (на самом деле сборка — это сложно, и эта сложность так или иначе должна где-то обитать; очень недальновидно отказываться от замечательного инструмента который всю это сложность инкапсулирует и поддерживает за вас, тащить её себе в проект и потом нести бремя поддержки) не пишет руками россыпь императивных скриптов (и в особенности не парсит include sed'ом), когда можно декларативно написать "нужна библиотека A из исходников X и исполняемый файл B из исходников Y и этой библиотеки" на CMake, и из него получить сборку хоть на make, хоть на ninja, хоть на msbuild, хоть на том же redo (если кто-то напишет генератор) или проекты под любые IDE, и это будет нормально обрабатывать зависимости, работать с любыми компиляторами, уметь кросс-компиляцию, выполнять все требования к сборке (а не только те что вы не забыли) и ещё много много всего что вам придётся либо писать руками, либо вы написать забудете (сломав кому-то сборку), либо просто не сможете не продублировав весь набор скриптов и/или не превратив его в include лапшу. И это будет не требуя от вас вообще никаких затрат развиваться и поддерживать новые возможности существующего инструментария и новые инструменты.
Так что, вашими же словами, CMake настолько впечатлил life-changing простотой, гибкостью и куда лучшим выполнением задач сборки, что я во всех своих проектах им полностью заменил make (вместе с qmake, scons, autotools) и точно не поменяю на redo.
Помимо сборки софта, в простых случаях я однозначно предпочту make, только за то что его все знают (POLA) и за наглядность (один файл, удобное задание переменных для подстановки (=
, ?=
, +=
без лишних скобок и кавычек), далее все цели в одном месте). Для простых, повторюсь, случаев есть небольшое общее подмножество GNU/BSD синтаксисов с которым make по прежнему везде есть, и перечисленные вами минусы неактуальны, так что остаются только плюсы.
Вот примеры таких Makefile для наглядности:
https://github.com/repology/repology-logo/blob/master/Makefile
https://github.com/repology/repology-linkchecker/blob/master/Makefile
А вот прям сложные случаи сборки не-софта, с разветвлённым деревом зависимостей и разнообразными процессами, требующими вызова внешних утилит — тут да, можно и рассмотреть redo.
А теперь перейду к аргументам. Опишу ту ситуацию, с которой я сам столкнулся. Копипащу свой старый коммент:
Жрал я тут недавно плюсовую куку под названием CMake.
Из свежего, с чем уже 3 дня маюсь. Задача простая — собрать с BOOST'ом проект на кластере. Что я нашел недавно обнаружил в попытках понять, почему сборка ломается:
So my current understanding is:
For Boost ≤ 1.69, we can specify the location with BOOST_ROOT as a command line parameter, set(), or environment variable.
The world is :)
But from ≥ Boost 1.70, it will start preferring to use «config mode», meaning it will silently ignore the BOOST_ROOT if we specify it via the command line or set().
But it will continue heeding it if we specify it via an environment variable.
But the new config mode will find Boost directories in new places (eg /opt) and these will silently override our BOOST_ROOT, even if specified via an environment variable (that it otherwise heeds).
To revert to the previous, non «config mode» behaviour, we must specify Boost_NO_BOOST_CMAKE=ON
But this must be specified via a command line parameter or set(); if we specify it via an environment variable, it will be silently ignored.
To specify the location under «config mode», we can specify Boost_DIR
But this must be specified as an environment variable or set( Boost_DIR CACHE ); if it is specified via the command line or the a plain set( Boost_DIR <value ), it will be silently ignored.
There is further complexity with Boost_ROOT (the same as BOOST_ROOT?) and the version of cmake we're using and the version of cmake we've required.
The world is :(
Здесь детальнее о проблеме.
Я не знаю, кто виноват. Я, желающий в этом разобраться и починить, авторы CMake, авторы Буста? Не знаю. Но факт остается фактом. Я не могу нормально использовать одну из самых популярных библиотек на кластере, потому что CMake превращает весь процесс в цирк.
Был бы он так приятен и понятен, не пилилось бы ему тогда 100 альтернатив и не писались бы статьи на хабре о том, как его заменить.
По хорошему, вся эта магия с BOOST_ROOT и версиями boost должна быть реализована в модулях cmake.
CMake тут не в чем винить — у вас очень странная хотелка и я не знаю систем сборки которые позволяли бы для каждой зависимости индивидуально контролировать где её искать. Обычно разные окружения ставят целиком в разные префиксы и указывают CMAKE_FIND_ROOT_PATH, больше ничего не надо.
Был бы он так приятен и понятен, не пилилось бы ему тогда 100 альтернатив и не писались бы статьи на хабре о том, как его заменить.
Альтернатива ему пилится ровно одна — meson. От второй, qbs, сами авторы отказались в сторону CMake — не был бы он так приятен и понятен, этого бы не произошло. Но я не настаиваю — используйте любую другую высокоуровневую систему сборки. Посыл был прежде всего в том чтобы не писать скрипты вручную.
Поиск модулей у CMake странный местами, тут согласен. Могу только посоветовать попробовать ключики PATHS и HINTS у find_package.
Почти все крупные проекты, использующие make, пишут довольно увесистую систему на нём (например Kconfig). С таким подходом единственным преимуществом make остаётся только то, что я буду знать, что для сборки проекта надо выполнить команду «make», а со всем остальным придётся разбираться отдельно для каждого проекта.
Если единственное, что нужно от make — одна универсальная команда для сборки проекта, то Dockerfile выглядит ещё лучше, ведь он с собой ещё и весь комплект разработчика притащит, ничего не надо будет устанавливать и настраивать.
работы на трех устройствах с именами ci20, bt01 и dm64. Первые два устройства
ci20 и bt01 основаны на архитектуре MIPS, третье устройство dm64 построенно
на базе процессора ARM. Toolchain-ы для сборки программ, для простоты, назовем
mips и arm, соответственно.
Сценарий сборки исходной программы одинаков для всех трех устройств и написан
на языке GNU Make.
Если представить все комбинации вызовов команды Make, необходимые для сборки
программы на наши устройства, получим:
$ TOOLCHAIN=mips HARDWARE=ci20 make
$ TOOLCHAIN=mips HARDWARE=bt01 make
$ TOOLCHAIN=arm HARDWARE=dm64 make
или, при передаче имен устройств и Toolchain-ов в качестве аргументов:
$ make TOOLCHAIN=mips HARDWARE=ci20
$ make TOOLCHAIN=mips HARDWARE=bt01
$ make TOOLCHAIN=arm HARDWARE=dm64
Таким образом, система сборки должна принимать пары TOOLCHAIN-HARDWARE,
которые определяют какой именно Toolchain необходимо использовать для того
или иного устройства.
Рассмотрим теперь, как, на уровне системы сборки, организовать последовательность
вызовов утилиты Make для нашего сценария таким образом, чтобы пользователь мог
осуществить данные действия с помощью лишь одного вызова:
$ make
без задания дополнительных аргументов, отвечающих за выбор целевого устройства
и связанного с ним Toolchain-а.
Если включить в начало нашего сценария список допустимых целевых устройств,
например, следующим образом:
COMPONENT_TARGETS = $(HARDWARE_CI20)
COMPONENT_TARGETS += $(HARDWARE_BT01)
COMPONENT_TARGETS += $(HARDWARE_DM64)
то система сборки сможет автоматически построить список возможных, для данного
сценария, комбинаций TOOLCHAIN-HARDWARE, который будет выглядеть, например,
следующим образом:
targets = target_mips_ci20 target_mips_bt01 target_arm_dm64
Имея такой список, система сборки может восстановить аргументы, которые
необходимо передавать при каждом вызове утилиты Make, для нашего сценария.
Сделать это нетрудно, на языке GNU Make эти действия можно описать так:
target_%: TOOLCHAIN = $(shell echo $(word 2, $(subst _, , $@)))
target_%: HARDWARE = $(shell echo $(word 3, $(subst _, , $@)))
target_%:
$(MAKE) TOOLCHAIN=$(TOOLCHAIN) HARDWARE=$(HARDWARE)
Таким образом, при вызове команды Make без аргументов, переменные TOOLCHAIN и
HARDWARE будут не определены и, в этом случае, система сборки займется созданием
списка targets из числа допустимых комбинаций. Когда же список будет составлен,
система сборки сможет осуществить вызов
$(MAKE) TOOLCHAIN=$(TOOLCHAIN) HARDWARE=$(HARDWARE)
с действительными аргументами.
Когда же, при очередном вызове, система убедится в том, что переменные
TOOLCHAIN и HARDWARE определены, управление будет передано нашему сценарию
без дополнительных вычислений.
Описанный здесь механизм, напрямую вытекает из возможностей утилиты Make.
Все просто. Каждый выбирает средство исходя из поставленной задачи.
И еще. Современные системы сборки слишком перегружены, например тем, что пытаются анализировать флаги передаваемые через переменные окружения CFLAGS, CXXFLAGS,… вместо того, чтобы просто передать их компилятору. Посмотрите в какой ад превращалось портирование Qt на новую архитектуру, когда надо было редактировать *.pro файлы так, чтобы система сборки не калечила флаги, передаваемые компилятору code.qt.io/cgit/qt/qtwebengine.git/tree/src/core/gyp_run.pro?h=5.7.
Make на мыло, redo сила