Прекратите использовать Makefile для выполнения повторяющихся задач

TL;DR

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

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

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/acecilia/taskfile/master/start.sh)"

Как мы здесь оказались?

Когда программный проект растет, в большинстве случаев у вас появляется список повторяющихся задач, которые необходимо выполнять в верхней части репозитория. Например, несколько простых: build, test, _7 _...

Есть несколько альтернатив для организации кода, который реализует каждая из этих задач:

Но давайте сосредоточимся на одном из самых популярных и распространенных решений: определить каждую из повторяющихся задач как цель внутри Makefile

Плюсы и минусы Makefile

Использование Makefile для хранения и выполнения этих повторяющихся задач поначалу кажется хорошим решением:

  • Практически все знают, как запустить Makefile, когда его видят.
  • Инструмент командной строки make является мультиплатформенным и во многих случаях поставляется предварительно установленным в ОС, поэтому дополнительных действий по установке не требуется.
  • Синтаксис выглядит ясным: каждая повторяющаяся задача определяется как цель
  • Цели могут зависеть друг от друга: выполнение одной задачи может запускать любую другую задачу в определенном порядке.
build:
    bazel build ...
    
test: build
    bazel test ...

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

my_target1:
    for i in $$(ls); do \
        echo "This is a file: $$i"; \
    done
my_target2:
    $(eval MY_VAR := "this is a local variable")
    echo $(MY_VAR)
my_target3:
    echo "$$TMPDIR"
  • При выполнении задачи все команды, выполняемые внутри нее, по умолчанию распечатываются. Чтобы этого избежать, вам нужно поставить перед командой префикс @ или полностью избежать печати команд, добавив .SILENT в начало Makefile:
my_target4:
    @echo "hello world"

Вы можете понять, как все эти мелкие проблемы становятся основной проблемой, поскольку эти повторяющиеся задачи растут и усложняются. Проблема в том, что мы упускаем из виду Makefile: make - инструмент автоматизации сборки, который никогда не предназначался для использования в качестве альтернативы для повторного выполнения задач.

Альтернатива: файл задачи

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

#!/bin/zsh
set -euo pipefail # 1
name="Andres" # 2
say_hello() {
    surname="$1" # 3
    echo "Hello, I am $name $surname" # 4
}
# What this task does: Says hello and bye # 5
say_hello_and_bye() {
    say_hello $@ # 6
    echo "Bye!"
}
"$@" # 7

Давайте рассмотрим детали:

  1. set -euo pipefail включает своего рода строгий режим для запуска скрипта
  2. Можно определять глобальные переменные
  3. Можно определить локальные переменные
  4. Можно легко повторно использовать глобальные и локальные переменные
  5. Есть возможность добавлять комментарии и документировать свой код
  6. Некоторые задачи могут вызывать другие задачи.
  7. Вот что заставляет все работать: выполняет все параметры, переданные в скрипт, как если бы они были функцией

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

  • Запуск от имени ./taskfile_example.sh say_hello Cecilia напечатает:
Hello, I am Andres Cecilia
  • Запуск от имени ./taskfile_example.sh say_hello_and_bye Cecilia напечатает:
Hello, I am Andres Cecilia 
Bye!
  • Если вы хотите пройти лишнюю милю, вы можете переименовать скрипт в taskfile и добавить псевдоним alias task="./taskfile" в свою оболочку, так что вызов задач внутри файла задач станет еще короче. Некоторые примеры:
task say_hello Cecilia
task say_hello_and_bye Cecilia
task build
task test

Попробуйте прямо сейчас

Выполнение следующей команды с вашего терминала создаст базовый taskfile в рабочем каталоге:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/acecilia/taskfile/master/start.sh)"