Pull to refresh

Comments 50

Хотелось бы увидеть несколько более подробное объяснение, почему не подошёл make, кроме того, что он является «очень олдскульным build tool со многими вытекающими "особенностями"»

  1. Из коробки отсутствует возможность сделать листинг доступных целей

  2. По умолчанию цели не .PHONY, а значит такой кейс не будет работать как хотелось бы

test:
	./test --all

Потому что

make: `test' is up to date.
  1. Неудобно использовать многострочные шелл-скрипты в качестве тела целей

  2. Необходимость использовать $$ вместо $ для переменных окружения

  3. Обязательные табы

  4. Несовместимость разных реализаций make

много лет наблюдаю за появлением разных альтернатив make, но пока не видел более удобной.

5. Обязательные табы

А что тут такого?

С++ плох из за обязательных фигурных скобок!

LISP плох из за обязательных круглых скобок!

Makefile плох из за обязательных Табов!

PS: Например Linux kernel код стайл декларировал (как сейчас не знаю, скорее всего так же) обязательность Табов. И это хорошо (ц)

а cmake это же не замена make, а надстройка/расширение

Ну тем не менее это полноценная замена, лишь одна из имплементаций cmake под nix основана make, но вам, как разработчику, все равно: вы получаете все возможности, не трогая Makefile руками. Можно переключиться и на ninja, к примеру. cmake работает на платформах где make нет и в помине, поэтому я бы не называл cmake надстройкой над make..

1. Есть довольно известный однострочник, который можно вставить, например в таргет help, для того чтобы полистить таргеты и описать что они делают из комментариев.
2. И это хорошо. Потому что иначе у вас начинаются коллиззии между целями для сборки и вспомогательными целями.
3. Как правило, поставить ; \ в конце строки — не проблема. А во многих случаях это и вовсе не нужно.
4. Небольшой коллапс в синтаксисах makefile/shell, но в целом ничего страшного в этом нет.
5. Настройте себе текстовый редактор. Это никогда не было аргументом ни за, ни против какого-то инструмента.
6. Вот это реальная проблема. Однако многие фичи поддерживаются всеми реализациями, которые вы в здравом уме можете встретить (мы же не говорим о поддержке nmake?), а в большинстве случаев можно достаточно смело делать предположение о том, что будет использоваться исключительно gnu make (да, мне тоже это не нравится).

Гораздо более важной проблемой make, на самом деле является то, что там очень нетривиально сделать так, чтобы изменение команды в Makefile приводило к тому чтобы инвалидировались и пересобирались таргеты, которые собираются этой командой. Ну и производительность так себе. Поэтому все больше и больше вместо make используют связку ninja+генератор (например cmake). Использование отдельного генератора убирает проблемы с совместимостью и синтаксисом, а использование ninja, очень положительно влияет на производительность, решает проблему инвалидации таргетов при модификации команды, а также убирает проблемы совместимости (имплементаций ninja гораздо меньше, и фич там тоже меньше, гораздо сложнее сделать что-то несовместимое).

Гораздо более важной проблемой make, на самом деле является то, что там
очень нетривиально сделать так, чтобы изменение команды в Makefile
приводило к тому чтобы инвалидировались и пересобирались таргеты,

Я обычно ставлю зависимость на все цели от Makefile, тогда при внесении в него исправлений всё пересобирается.

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

Ninja решает эту проблему тем, что записывает хэш от команды в специальный файлик вместе с другой информацией. Если хеш не совпадает — команда изменилась и надо пересобрать конкретный таргет и то, что от него зависит.

Так а с чем Вы, собственно, спорите? Что на make можно сделать все то же самое? Естественно, можно. Полагаю, Вас сбил с толку слишком кликбейтный заголовок моей статьи, виноват.

Я утверждаю, что makesure удобнее именно для целей task runner, то есть когда вам нужно иметь простой набор утилитных команд для проекта. То есть я позиционирую этот инструмент как более удобную и универсальную альтернативу npm run scripts. Например в этой презентации https://www.youtube.com/watch?v=a6gcWjj5MQc докладчик описывает практику применения make для Ruby on Rails проекта. Это именно тот случай, где makesure мог бы стать лучшей альтернативой.

Подчеркиваю, это не build tool (как make, ninja, cmake и т.д.) и он не заменяет эти инструменты там где вам нужна специфичная логика сборки, характерная для C/C++.

Makefile вполне себе удобен, как task runner.
Необходимость объявлять таргеты, как .PHONY, ну мне это не мешает. Про однострочник хелпер выше написали уже.
Его преимущество в том, что make есть по умолчанию везде, не нужно ничего дополнительно устанавливать в систему.

Makesure тоже можно сказать не нужно: это маленький (~20 KB) самодостаточный POSIX-shell скрипт, который хранится в репозитории вместе с проектом и сразу доступен любому разработчику. То есть он всегда локален проекту и не нужно устанавливать глобально.

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

Я не очень понимаю идею делать отдельную программу таск-раннер. В CI/CD скорее всего понадобится несколько более сложный скрипт, который должен выполнять действия в необходимом порядке. Для запуска сервисов таск-раннеры обычно гораздо более сложные — там либо начинается упаковка в контейнеры, либо много конфигурации про то, как именно софт должен быть запущен, перезапущен, остановлен, etc. (то-есть это уже всякие конфигурационные скрипты для init что-то вроде Procman/supervisord). В сборке нужна полноценная система, отслеживающая зависимости, запускающая команды параллельно, и пересобирающая то, что необходимо, когда необходимо. В обычной работе полезнее сделать так, чтобы необходимые задачи можно было спокойно запускать без вороха дополнительных опций и тогда сложность запуска чего-то вручную или с помощью таск-раннера становится одинаковой.

Я не очень понимаю идею делать отдельную программу таск-раннер

Предположим для простоты вы хотите иметь три задачи на проекте: test, build, deploy. Имеем вопрос: написать три разных скрипта test.sh, build.sh, deploy.sh или один скрипт run.sh (будет запускаться как ./run.sh command). Рассмотрим эти варианты подробнее.

Несколько разных скриптов замусоривают проект. Надо все документировать в README. А deploy.sh может быть ./deploy.sh dev и ./deploy.sh prod. Тоже все в README описать. В случае таск-раннера в README достаточно добавить одну строку Запустите ./makesure -l.

В случае run.sh подхода вам надо писать некий необходимый бойлерплейт для разбора аргументов и роутинга на команды, вывод списка доступных команд, и т.д. И повторять эту работу для каждого нового проекта. Однако, если подумать run.sh это уже и есть такой себе таск-раннер только гораздо менее фичастый. Ну собственно и вот. Подход внешнего таск-раннера позволяет разделить декларативное описание бизнес-задач и движка их исполнения. Плюс, разные встроенные возможности, скажем замер времени исполнения каждой цели, настройки трейсинга и т.п. 

Ещё интереснее дело, если учесть зависимости между задачами. Скажем, чтобы протестировать проект, надо его сперва собрать, т.е. надо в вызов test добавить build. So far so good. Теперь, чтобы развернуть приложение, надо его собрать и протестировать. Добавляем в deploy вызовы на build и test. Упс! Теперь у нас на деплой билд будут запускаться дважды! Понимаю, что пример утрирован, но и реальный набор задач и зависимостей между ними может быть сложнее. А таск-раннер обеспечивает run-only-once семантику для зависимостей. 

Вы пишете разумные вещи, и вполне аргументировано. Но, тем не менее, всё далеко не настолько очевидно и на самом деле сильно зависит от проекта.

Вот Вам конкретный пример. Есть проект на Go, в котором 6 скриптов:

  • Задачи Makefile в плане сборки/зависимостей разных исходных файлов традиционно решает сам Go.

  • Задача зависимости деплоя от тестов традиционно решается не на уровне скрипта deploy или Makefile, а в конфиге CI/CD (ну потому что деплой должен быть всегда автоматизирован, и скрипта deploy для ручного запуска не должно быть в принципе).

  • scripts/build принимает опциональный список параметров, которые передаёт go build as is ("$@"). Это не требует особого запоминания и документирования в README просто потому, что по сути этот скрипт и есть обёртка над go build.

  • scripts/cover принимает опциональный список параметров, которые передаёт go test as is ("$@"). Аналогично не требует документирования поскольку это обёртка над go test с дополнительным расчётом покрытия.

  • scripts/postgres-setup интересен тем, что предназначен для настройки постгреса и обычно вообще запускается внутри отдельного контейнера docker-compose, в который кроме этого скрипта другие файлы вообще не прокидываются… как это элегантно совместить с концепцией "всё в таск-раннере" без костылей и изоленты мне не очень ясно. Параметров нет, документировать нечего - названия скрипта вполне хватает.

  • scripts/stat выводит статистику по коду. Параметров нет, документировать нечего.

  • scripts/test принимает опциональный список параметров, которые передаёт go test as is ("$@"). Аналогично не требует документирования поскольку это обёртка над go test.

  • scripts/test-ci-circle гоняет тесты "как на CI". Параметров нет, документировать нечего.

  • Общего кода у этих скриптов нет.

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

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

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

Учитывать зависимости в таких вещах, имхо, излишне. Либо у вас скрипт, который последовательно делает полную сброрку и тестирование, либо скрипт для тестирования должен просто падать если что-то необходимое еще не было собрано. Какую задачу вы пытаетесь решить скрывая эти зависимости от разработчика?

Зависимости как-раз не скрываются а вполне даже декларативно описываются (пример).

У инструмента также есть опция соответствующая для показа зависимостей любой цели:

 -d,--resolved   list resolved dependencies to reach given goals

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

Зачастую все проблемы идут из-за неправильно выбранных аналогий и абстракций.

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

Make задумывался как сборщик программ. И его цель (результат работы, target) - как правило файл. Если мы чётко прописываем, как получить какой-то файл и какие зависимости нужны, то make за нас это всё делает, строит зависимости, умеет собирать "параллельно на нескольких ядрах" и т.п.

В вашем случае, начинаем думать про файлы. Мы хотим что?

`make a-test-report.html an-integration-test-report.xml` - мы хотим получить отчёт по тестам.

`make -j 8 binary-program` - сделай мне нужный бинарник, используй 8 потоков, что бы там ни было. И актуальность make за тебя проверит, и соберёт только то, что нужно.

Да, хочется иметь удобные цели `make check` или `make test`. И это скорее как исключение. Если наша "несуществующая цель, которая как бы есть, но её нет" потенциально совпадает с файлом, мы помечаем её как .PHONY (https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html).

Ниже даже упомянули про трюк с touch. Когда "правило" не производит никакого результата (make upload) мы можем в конце сделать touch file.uploaded и зацепиться за него как за маркер.

Я верю, что Вы хорошо знаете make и понимаете зачем он нужен. Перечень пунктов выше показывает НЕ то что make зло вообще, а то что make неудобен если использовать его как простой task runner а не как полный build tool (его основная функция). В этом случае некоторые его сильные стороны в качестве build tool становятся "неудобствами".

Я лично под именем Taskfile в статье ожидал увидеть немного не ту альтернативу. Помимо указанного в статье есть ещё https://taskfile.dev/ (тоже портабельный т.к. написан на Go и довольно простой).

Также можно вспомнить just (на Rust) и Invoke (на Python).

Лучший велосипед - тот, на котором умеешь ездить. Или самосборный. Но на bash лисапед в 20к строк я бы не собирал.

Ну если уж вдаваться в детали, там под капотом AWK, кстати гораздо приятнее Bash-а в качестве ЯП 🙂

В качестве ЯП ещё приятнее Python что угодно другое ;) Многопоточность для task runner невредно уметь. И нормальный человеческий отладчик тоже удобно.

У чего угодно другого с портабельностью туго. По моему разумению для такого тула это очень важный показатель. Многопоточность - неплохо но это вотчина именно билд тулов. Подробнее https://github.com/xonixx/makesure#omitted-features

с портабельностью туго

Существует архитектура, где есть bash и awk, но принципиально нет python? ;)

Второй или третий? (шутка)

Питон, конечно, доступен везде. Однако известно, что его инфраструктура это сущий ад https://xkcd.com/1987/. Сделать кросс-платформенный самодостаточный дистрибутив программы, написанной на Питоне, особенно если там используется пара-тройка специфичных пакетов, просто ну чудовищно сложно.

Легко разрабатывать, легко поддерживать, легко использовать. Выберите что-то одно ;) Возможно, маленький исходник на С, который очень просто собирается под целевую архитектуру — ещё более удобное решение.

В точку! Я выбрал вариант который, да, труднее разрабатывать но очень просто распространять. Вообще я считаю что для такого инструмента языки с рантаймами (питон/перл/нода/...) исключены. Только компиляторы, производящие статический бинарник или вот так.

Исключение: если task runner на питоне заточен под питон-проекты.

труднее разрабатывать но очень просто распространять


Мне приходит в голову только одна модель использования, где это критично
Заголовок спойлера
Дроппер для майнера/шифровальщика/подобного софта ;)

Для сборки проекта обычно всё равно нужно готовить окружение (устанавливать компилятор(ы), библиотеки, etc).И с кросплатформенностью тут уже сложно.
Но если цель не сборка — то что?

Дроппер для майнера

Исполняемый файл представлен исходным кодом https://github.com/xonixx/makesure/blob/main/makesure - поэтому как минимум можно убедиться визуально в отсутствии закладок.

готовить окружение

Ну так это как раз можно (и даже нужно) автоматизировать через task runner.

Дроппер — это всего лишь полностью автоматический инсталлятор.

готовить окружение

Ну так это как раз можно (и даже нужно) автоматизировать через task runner.


Хотелось бы увидеть пример использования. Потому что там где это действительно необходимо, там
всё сложно
вот например для сборки chromium глубоко внутри нужен gperf. Как установить gperf, чтобы это работало однообразно на Windows, FreeBSD, debian, centos?

Конечно, автоматизировать сборку Хромиума таким образом будет close to unreal. Но что-то попроще очень даже можно. Например, в моем случае прогонка тестов зависит от установки инструмента тестирования https://github.com/xonixx/makesure/blob/main/Makesurefile#L14. Удобно, когда ты up and running сразу после скачивания проекта из репы.

Upd. Также инсталляция различных AWK для прогонки тестов на них https://github.com/xonixx/makesure/blob/main/Makesurefile#L206.

Удобно, когда ты up and running сразу после скачивания проекта из репы.


Наверное да. А что такое gh release create?
gh

Command 'gh' not found


Проект на bash всё равно тянет за собой зависимости, которые нужно бы отследить.

Ну в крайности впадать не надо, да? Вы попросили пример подготовки окружения - я дал его.

Публиковать релизы надо всем разработчикам на проекте?

То что удобно автоматизировать - надо автоматизировать. То что не удобно - не надо.

Легковесный инструмент позволяющий делать базовую подготовку окружения удобнее тяжеловесного с зависимостями, разве это не очевидно?

Совершенно неочевидно, в чем имеет смысл измерять легковесность. Не в байтах, так точно. Этот скрипт скачает при первом запуске значительно больше, чем весит. Если система подходящая, да ;)

В данном случае подразумеваемся легкость установки инструмента. Напоминаю, что в makesure O(N) разработчикам на проекте вообще не придётся ничего ставить.

Этот скрипт скачает при первом запуске значительно больше, чем весит.

А это плохо? Вы же сами написали что дело не в байтах.

Ну, ок. Что не нравится мне:
1. Проект получился достаточно громоздкий. Вносить изменения в bash скрипты такого размера я категорически не люблю, например ;)
2. Зависимости у него всё равно есть.
3. Самобытный синтаксис файлов — его кто-то из редакторов поддерживает?
4. Киллер-фичи, трудно достижимой другими путями, нет. Всё то же можно сделать любым знакомым способом (make, bash, python..)

Ну это Вы смотрите с точки зрения разработчика инструмента. Попробуйте взглянуть с точки зрения пользователя. Тем не менее по пунктам:

  1. Справедливости ради, разработка ведётся на AWK в соответствующем файле, а конечный shell-скрипт собирается в момент релиза. Что же касается разработки на AWK, то так уж получилось, что ваш покорный слуга написал и поддерживает соответствующий плагин https://github.com/xonixx/intellij-awk  под IntelliJ IDEA и другие IDE JetBrains. Поэтому разрабатывать вполне себе приятно.

  2. Из зависимостей только POSIX окружение, которое доступно везде, даже на Win. Так, инструмент регулярно тестируется под Linux, Win, macOS и даже FreeBSD: https://github.com/xonixx/makesure/actions/runs/1646901221

  3. Синтаксис является подмножеством синтаксиса shell (хоть и с другой семантикой). Поэтому подсветка shell для Makesurefile должна работать в любой IDE. Например GitHub валидно подсвечивает синтаксис: https://github.com/xonixx/makesure/blob/main/Makesurefile. Да это не идеал, но вполне приятно.

  4. Возможно и так. Хотя по поводу директивы @reached_if https://github.com/xonixx/makesure#reached_if  я бы поспорил. Однако, сила этого инструмента не в какой-то конкретной киллер-фиче, а в той, как мне кажется удачной и минималистичной комбинации вроде бы простых фич, которую удалось найти.

Минимализм присутствует, согласен. Но является ли минимализм преимуществом? Это же софт для программистов/девопсов, а у них есть возможность установки и другого софта кроме bash. КМК, если взять, например, cmake (кросплатформенность), раскурить его add_custom_target/add_custom_command - можно получить за те же деньги все то же + многопоточность + готовую build систему + 100500 доступных фич, которыми можно и не пользоваться.

Но является ли минимализм преимуществом?

Естественно, является.

КМК, если взять, например, cmake 

Ну пожалуйста, берите. Ваш выбор. Опять таки, я в который раз не понимаю какую мысль Вы пытаетесь донести. Что мой инструмент не нужен, потому что написан на баш (нет), а не на Питон, потому что в нем есть вещи, которые Вам не нравятся, потому что он очень легковесен, а это НЕ является преимуществом, и что можно на cmake сделать всё то же самое?

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

Если нет, то просьба уточнить предмет Вашего несогласия 🙂

;) Я доношу мысль, что легковесность и минимализм (для подобных инструментов, которые запускаются не на микроконтроллере), не так важны, как кажется. Это же не однострочник. Более важно уметь в универсальность и расширяемость. Но переписывать ваши примеры на питоне или cmake, пожалуй, воздержусь ;) Ваша задача уже решена, второе решение не нужно.

Я разделяю противоположный подход и опираюсь при этом на философию Unix, и принципы "Less is More" и "Worse is Better". Не вижу, почему их нельзя применить и для подобных инструментов.

Дроппер для майнера

...

как минимум можно убедиться визуально в отсутствии закладок

Мне кажется, что это скорее вопрос психологии, и технически его решить будет непросто (если вообще возможно).

Я не могу это объяснить, но вообще к бинарным релизам доверие намного выше, чем к инструкции "сделайте http запрос и отправьте результат себе в шелл для исполнения".

Да, конкретно к makesure эта проблема вроде бы не относится, но чтобы это понять надо вчитаться в предлагаемую для установки шелл-команду… а она не настолько короткая, чтобы сразу это сделать, поэтому, чисто психологически, первая реакция - закрыть страничку с возмущением "вот когда научатся нормально релизы делать, тогда и посмотрю". Ещё раз уточню - это чисто психологическая реакция, причём самая первая, и она определённо некорректна в отношении конкретно makesure.

Более того, с технической стороны выкладывать именно так, как сделано в makesure - более безопасно, чем делать релиз на гитхабе (просто потому, что по репо видно что там, есть история коммитов, etc., а что упаковали в архив с релизом и из каких исходников он собран если он бинарный - никто не знает). Но, ещё раз, проблема тут психологическая, и релизы почему-то воспринимаются более надёжно.

Поэтому я бы рекомендовал предоставить дополнительно альтернативный способ установки через релизы. Не в виде как это по умолчанию делает гитхаб (тупо архив всего репо), а конкретно тот файл или архив, который надо устанавливать, и добавить это как альтернативный способ установки в README.

Спасибо, статья интересная. К своему стыду не знал про makesure.

Я старый олдфаг с опытом еще из 1990х... Как освоил когда-то make, так и использую постоянно, хотя ребята из команды на меня поругиваются :-)

Никакого стыда!) Инструмент-то молодой, чуть старше года.

Sign up to leave a comment.

Articles