Многие ли из вас используют всевозможные вспомогательные shell-скрипты в своих проектах? Это также могут быть Python или Perl скрипты. Обычно такие скрипты используются на этапе сборки или для других задач автоматизации проекта.


Примерами таких задач могут служить:


  • вспомогательные скрипты для Git,
  • запуск тестов/линтеров,
  • запуск необходимых докер контейнеров,
  • запуск БД-миграций,
  • собственно, сборка проекта,
  • генерация документации,
  • автоматизация публикации релизов,
  • развертывание и т.д.

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


Make — пожалуй, наиболее известный из подобных инструментов.


Похожий функционал известен разработчикам nodejs и любим ими в виде скриптов в package.json (npm run-scripts). Ветераны Java вспомнят Ant.


Но nodejs/Ant требуют установки, make хоть и способен выполнять функции task runner довольно неудобен в этой роли, будучи на самом деле очень олдскульным build tool со многими вытекающими "особенностями".


А shell-скрипты требуют некоторой системы и неизбежной рутины в написании (обработка аргументов, help-сообщения и т.д.).


Хотя, например, Taskfile представляет прекрасный шаблон для подобных скриптов.


Так и родился makesure.


Что это? Это инструмент, который может работать с файлом Makesurefile такого вида:


@goal downloaded
@doc downloads code archive
@reached_if [[ -f code.tar.gz ]]
  wget http://domain/code.tar.gz

@goal extracted
@depends_on downloaded
  tar xzf code.tar.gz 

@goal built
@doc builds the project
@depends_on extracted
  npm install
  npm run build

@goal deployed
@doc deploys the built project
@depends_on built
  scp -C -r build/* user@domain:~/www

@goal default
@depends_on deployed

По сути это поименованные кусочки shell (называемые целями), объединённые в одном файле. Это позволяет легко перечислить цели (с пояснительным текстом):


$ ./makesure -l
Available goals:
  downloaded : downloads code archive
  extracted
  built      : builds the project
  deployed   : deploys the built project
  default

и вызвать любую из них по имени:


$ ./makesure deployed
$ ./makesure                  # по умолчанию будет исполнена цель с именем default

Да, вот так просто.


Впрочем, это ещё не все. Цели могут декларировать зависимости от других целей и makesure будет учитывать это при выполнении. Это поведение весьма близко к оригинальному make. Цель также может декларировать условие того что она уже достигнута. В этом случае тело цели (соответствующий shell скрипт) уже не будет исполняться. Этот простой механизм позволяет очень удобно и декларативно выражать идемпотентную логику работы, а, проще говоря, ускорять сборку, так как то, что уже выполнено, не будет выполняться повторно. Эта фича уже была навеяна идеями из Ansible.


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


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


Ну, что-то типа


@define VERSION='1.2.3'
@goal built
  echo "Building $VERSION ..."
@goal tested
  echo "Testing $VERSION ..."

Изначально я хотел спроектировать эту часть в наиболее общем виде. Так, я решил, что в инструменте будет понятие prelude — это скрипт, идущий перед всеми декларациями @goal. Целью этого скрипта будет инициализация глобальных переменных. Ну какой-нибудь такой гипотетический пример


# prelude starts
if [ -f version.txt ]
then
  @define VERSION=`cat version.txt`
else
  @define VERSION='0.0.1'
fi
# prelude ends
@goal built
  echo "Building $VERSION ..."

Идея была в том чтоб где-то приблизиться по функционалу к make, не вводя при этом отдельный язык программирования, а использовать знакомый shell.


Пару отягчающих моментов. Во-первых, следует учесть, что под капотом каждый из @goal-скриптов выполняется в отдельном процессе shell. Сделано это намеренно, чтобы исключить возможность зависимостей через глобальные переменные между целями, что может сделать логику исполнения более императивной и запутанной. make в этом смысле ведет себя подобным образом, а точнее еще "хуже" — там каждая строка исполняется в отдельном shell.


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


Очевидно, скрипт инициализации может быть ресурсоёмкий, скажем


@define VERSION="$(curl -s http://domain/version.txt)"

В-третьих, должна иметься возможность переопределить значение переменной в момент запуска, например так


./makesure built -D VERSION=0.0.2 

Первый и второй моменты несколько плохо сочетаются, исключая простую возможность подмешивания скрипта prelude в начале каждого @goal-скрипта как модели исполнения.


В результате решение было все-же найдено. Каждое вхождение defile VAR=val под капотом заменялось на что-то типа VAR=val; echo "VAR='$VAR'" >> /tmp/makesure_values, а в начало каждого @goal-скрипта неявно добавлялось . /tmp/makesure_values.


Были некие дополнительные нюансы, связанные с реализацией третьего пункта, но они не слишком существенны для упоминания.


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


По поводу скорости, на системах где присутствует /dev/shm (все современные Линуксы?) он был использован вместо /tmp. macOS — ☹️ — там это не поддерживается.


По поводу гарантии подчистки временных файлов — набор тестов был доработан таким образом, чтоб падать, если вдруг по какой-то причине мусор не был удалён.


Как это обычно бывает, свежий взгляд со стороны от человека, ранее не вовлеченного в дело, может быть весьма ценен.


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


Дело в том, что по моему замыслу должна была быть возможность делать так


A=Hello                # invisible to goals
@define B="$A world"   # visible to goals

По моей задумке, достигается это тем, что в файл /tmp/makesure_values попадают уже "вычисленные" @define-значения.


И это принципиально не работает в предложенном участником способе.


Каково же было моё удивление, когда я обнаружил, что этот кейс не работает и с моей имплементацией!


Первым моим побуждением было устранить эту проблему и покрыть этот случай недостающими тестами.


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


А что если вообще удалить концепцию prelude как произвольного скрипта перед целями? Оставить только @define?
Почему нет? Ведь less is more, а worse is better.


Вот несколько мыслей, которыми я руководствовался:


  • Эта фича нешироко используется (или вообще не используется) и имеет баги реализации
  • Мы еще не знаем как правильно использовать этот функционал. Возможно его неправильное использование/злоупотребление им.
  • Вносит неопределенность. Если необходима такая сложная логика инициализации, почему бы не использовать для этого отдельную цель @goal initialized?
  • Усложняет реализацию и делает её менее производительной за счет использования временных файлов.

Да и вообще при разработке продукта или библиотеки очень важно реализовывать минимально возможный функционал, и именно тот который необходим пользователям сейчас. Весьма часто разработчики поддаются соблазну добавить какие-то очевидные улучшения и функции, которые не критически важны и/или избыточны, просто потому что это кажется простым. Более того, по этой же причине часто полезно явно исключить определенные фичи/сценарии использования. Потому что вы всегда можете их добавить позже, если будет явный запрос от пользователей. Удалить же какую-то неудачную фичу может быть гораздо более проблематично.


Решено. Урезаем концепцию prelude, оставляем лишь возможность @define.


Однако на этом вопросы не заканчиваются.


  • Возможно также есть смысл переработать синтаксис:
    • @define VAR='hello' (как сейчас) vs
    • @define VAR 'hello' (более консистентно с синтаксисом остальных директив)
    • Разрешить или запретить строки с двойными кавычками? Другими словами, хотим ли мы поддерживать подстановку переменных:
      • @define W=world
      • @define HW="Hello $W!"
  • Реализация
    • pass-through в shell (как сейчас)
      • Гибкость с подстановкой переменных, но труднее с валидацией
    • или ручной парсинг
      • Сложнее в реализации, но больший контроль в валидации неинициализированных переменных; если нужно, можем запретить функции shell, например @define A="$(curl https://google.com)"

Дело в том, что текущая реализация, как уже говорилось выше, основана на, буквально, передаче всего что идёт после слова @define на исполнение в shell. А это значит, что можно написать


@define echo 'Hello'

и оно не выдаст ошибку, но сделает какую-то несанкционированную ерунду.


Если попытаться добавить простенькую регулярку на соответствие VARNAME=, то и это легко обойти


@define A=aaa echo 'Hello' # будет вызвана команда echo у которой задана переменная окружения A

Естественно, хотелось бы запретить такие "возможности".


Имеем дилемму. Либо же отказываемся от передачи в shell и добавляем ad-hoc парсер этой директивы или же имеем что имеем.


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


Знаете ли вы сколькими способами в Bash можно определить переменную со значением hello world?


H=hello\ world
H='hello world'
H=$'hello world'
H="hello world"
W=world
H="hello $W"
H=hello\ $W
H='hello '$W
H='hello'\ $W
H=$'hello '"world"
H='hello'$' world'
H=$'hello'\ $' world'
H='hello'$' '"world"
H='hello world';
H="hello world" # with comment
H=$'hello world'     ;            # with semicolon, spaces and comment
# и т.д.

И это еще далеко не все варианты!


Почему дополнительная сложность реализации неприемлема? Потому что один из фундаментальных принципов, которые я положил в основу этого инструмента, это worse is better. Это значит что простота реализации и минимальный размер утилиты более предпочтительны, чем богатая функциональность.


Вы можете спросить: а зачем вообще полагаться на синтаксис bash? Почему бы не ввести свой ограниченный синтаксис, скажем, как-нибудь так:


@define W  'world'
@define HW 'hello {{W}}'

Идея заманчива, но не лишена недостатков, так как привносит усложнение ментальной сложности инструмента.


Дело в том, что инструмент спроектирован таким образом, что его синтаксис полностью укладывается в синтаксис shell. Это чрезвычайно удобно, так как вы можете выбрать в своей IDE подсветку shell для Makesurefile и это будет работать! Но это также значит, что необходимо, чтобы все синтаксические конструкции несли тот же смысл, что и в shell. Очевидно, что логика подстановки значений в гипотетическом облегченном синтаксисе не соответствует модели shell и это придется дополнительно знать пользователю.


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


Результатом тягостных раздумий явилось компромиссное решение. Мы по-прежнему передаём строку на исполнение в shell, но перед этим валидируем её бережно написанной регуляркой. Да, я знаю, что парсить регулярками нельзя. Но мы и не парсим! Мы только отсекаем невалидные варианты, а парсит shell. Интересный момент. На самом деле, эта регулярка более строгая, чем парсер shell:


@define VERSION=1.2.3    # makesure won't accept
@define VERSION='1.2.3'  # OK

@define HW=${HELLO}world    # makesure won't accept  
@define HW="${HELLO}world"  # OK  

Что я нахожу даже плюсом, т.к. это более консистентно.


В остальном эта директива хорошо покрыта тестами — как то, что должно парситься, так и то, что не должно.


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


На этом, пожалуй, стоит остановить свое повествование.


Заинтересовавшихся приглашаю опробовать утилиту makesure в своих проектах.
Тем более, что она не требует инсталляции (как это?) и хорошо портабельна.

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


  1. petropavel
    02.01.2022 22:36
    +17

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


    1. xonix Автор
      02.01.2022 23:09

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

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

      test:
      	./test --all

      Потому что

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

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

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

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


      1. git-merge
        02.01.2022 23:50
        +4

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

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

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

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

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

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

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


        1. robert_ayrapetyan
          03.01.2022 19:12

          cmake?


          1. git-merge
            03.01.2022 20:19

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


            1. robert_ayrapetyan
              03.01.2022 22:47
              -1

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


      1. alexac
        03.01.2022 00:01
        +4

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

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


        1. git-merge
          03.01.2022 00:57
          +3

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

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


          1. alexac
            04.01.2022 16:31

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

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


        1. xonix Автор
          03.01.2022 02:07
          -1

          Так а с чем Вы, собственно, спорите? Что на 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++.


          1. Bonio
            03.01.2022 10:42
            +1

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


            1. xonix Автор
              03.01.2022 12:59
              -1

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


              1. alexac
                04.01.2022 16:43
                +1

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

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


                1. xonix Автор
                  04.01.2022 20:23

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

                  Предположим для простоты вы хотите иметь три задачи на проекте: 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 семантику для зависимостей. 


                  1. powerman
                    05.01.2022 19:16

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

                    Вот Вам конкретный пример. Есть проект на 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 этого репо часть этих скриптов упомянута, но это просто потому, что это репо - шаблон для новых проектов, и как любой шаблон требует более тщательного документирования нежели обычные проекты.)

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


        1. mc2
          03.01.2022 03:50

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


      1. jsirex
        03.01.2022 17:21
        +2

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

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

        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 и зацепиться за него как за маркер.


        1. xonix Автор
          03.01.2022 18:05
          -1

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


  1. powerman
    02.01.2022 23:56
    +2

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


    1. oji
      03.01.2022 10:51
      +1

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


      1. xonix Автор
        03.01.2022 12:52

        Еще списочек аналогов https://github.com/xonixx/makesure#similar-tools


        1. unsignedchar
          03.01.2022 14:24

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


          1. xonix Автор
            03.01.2022 18:25

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


            1. unsignedchar
              03.01.2022 20:50
              +1

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


              1. xonix Автор
                03.01.2022 21:00

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


                1. unsignedchar
                  03.01.2022 22:46

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

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


                  1. xonix Автор
                    03.01.2022 23:38

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

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


                    1. unsignedchar
                      04.01.2022 00:07

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


                      1. xonix Автор
                        04.01.2022 00:11
                        +1

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

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


                      1. unsignedchar
                        04.01.2022 10:20

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


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

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


                      1. xonix Автор
                        04.01.2022 12:13

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

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

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

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


                      1. unsignedchar
                        04.01.2022 12:40

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

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

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


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


                      1. xonix Автор
                        04.01.2022 12:57

                        Конечно, автоматизировать сборку Хромиума таким образом будет 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.


                      1. unsignedchar
                        04.01.2022 13:40

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


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


                      1. xonix Автор
                        04.01.2022 13:48

                        Автоматизация создания релизов на GitHub: https://github.com/xonixx/makesure/releases


                      1. unsignedchar
                        04.01.2022 13:58

                        gh

                        Command 'gh' not found


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


                      1. xonix Автор
                        04.01.2022 14:19

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

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

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

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


                      1. unsignedchar
                        04.01.2022 14:33

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


                      1. xonix Автор
                        04.01.2022 14:43

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

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

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


                      1. unsignedchar
                        04.01.2022 17:25

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


                      1. xonix Автор
                        04.01.2022 18:15

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

                        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  я бы поспорил. Однако, сила этого инструмента не в какой-то конкретной киллер-фиче, а в той, как мне кажется удачной и минималистичной комбинации вроде бы простых фич, которую удалось найти.


                      1. unsignedchar
                        04.01.2022 23:57

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


                      1. xonix Автор
                        05.01.2022 00:43

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

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

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

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

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

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


                      1. unsignedchar
                        05.01.2022 10:12

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


                      1. xonix Автор
                        05.01.2022 10:55

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


                      1. powerman
                        04.01.2022 18:55

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

                        ...

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

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

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

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

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

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


  1. pupsegadm
    03.01.2022 18:24
    -1

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

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


    1. xonix Автор
      03.01.2022 18:29
      -1

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