Я фанат Makefile и активно задействую его в большинстве своих текущих проектов. Возможно, Makefile попадался вам на GitHub в различных проектах с открытым ПО (пример). Наверняка вы задавались вопросом, что это за инструмент и что он делает.
По Makefile создано немалое количество обучающих материалов. Цель данного руководства — разжечь интерес к Makefile и научить им пользоваться буквально за 5 минут. В результате вы приобретаете навык работы с этим инструментом и можете продолжать самостоятельно его изучать.
При желании пропускайте вступительную часть и сразу переходите к Уровню 1 и Уровню 2.
Определение Make и Makefile
Если кратко, то Makefile — это файл специального формата с инструкциями для утилиты GNU Make, т.е. make, по выполнению команд, работающих в системах *nix. Как правило, Make применяется для компиляции, сборки и установки ПО.
Хотя Makefile обычно используется для компиляции C и C++, он НЕ ограничен каким-либо конкретным языком программирования. Makefile решает разные задачи:
- выполнение цепочки команд для настройки среды разработки;
- автоматическая сборка;
- запуск наборов тестов;
- развертывание.
Аргументы в пользу Makefile
Компиляция исходного кода может стать проблематичной, особенно при необходимости включать множество исходных файлов.
По сути, Makefile представляет собой утилиту для написания и выполнения набора инструкций командной строки для таких процедур, как компиляция кода, его тестирование, форматирование, запуск и т.д.
Он помогает автоматизировать рабочие процессы разработки в виде простых команд (make build, make test, make format и make run).
Дополнительные преимущества:
makeпредустановлен на большинстве существующих систем*nix;makeне зависит от языка программирования/фреймворка.
От слов переходим к делу!
Краткое руководство по Makefile
При последующем разборе уровней обучения рекомендую создать Makefile и самим выполнять практические задания.
Примечание. Файл всегда должен носить имя Makefile.
Уровень 1. “Расскажи все, что нужно знать”
На этом уровне изучаем основы Makefile. Скорее всего, этих знаний хватит для эффективной работы с ним.
Допустим, у нас есть проект, использующий Docker. Для многократной сборки и запуска приложения с помощью Docker-контейнера вы обычно совершаете следующие действия.
- Выполняете команду
docker build. - Убеждаетесь в отсутствии работающих контейнеров.
- Выполняете команду
docker run. - Повторяете процедуру.
Вот как мы это делаем самостоятельно:
docker build -t image-name . --build-arg ENV_VAR=foo
docker stop container-name || true && docker rm container-name || true
docker run -d -e ANOTHER_VAR=bar--name container-name image-name
Довольно напряжно! Столько всего нужно запомнить и напечатать. Кроме того, возрастает вероятность допустить глупые ошибки.
Конечно, можно просто выполнять все три команды при каждом внесении изменений. Такой прием сработает, но об эффективности можно забыть. Вместо этого напишем Makefile следующим образом:
all: build stop run # build -> stop -> run
build:
@docker build -t image-name . --build-arg ENV_VAR=foo
stop:
@docker stop container-name || true && docker rm container-name || true
run:
@docker run -d -e ANOTHER_VAR=bar--name container-name image-name
Теперь для сборки и запуска нового Docker-образа потребуется всего лишь одна команда make all. В вышеуказанном случае можно просто вызвать make, поскольку all является первым правилом. Обратите внимание, что первое правило выбирается по умолчанию.
Правило в Makefile обычно выглядит так:
# запуск `make <target>` для выполнения данного правила
<target>: <prerequisite 1> <prerequisite 2> <prerequisite N> # перед блоком комментариев ставится префикс "#"
<command 1>
<command 2>
<command N>
Ключевые понятия уровня 1:
<target>(цель) — любое имя файла.<command>— команды/шаги*nixдля выполнения<target>. Они должны начинаться с символа табуляцииTAB.<prerequisite>(пререквизиты) — необязательная часть. Указываетmakeна то, что перед запуском команд все пререквизиты должны быть в наличии. Исходя из этого, они выполняются в порядке от 1 до N, как показано в примере выше.- Назначение синтаксиса
@. Если командная строка начинается с@, вывод самой команды в консоль подавляется (ссылка). - Первая цель
<target>выбирается по умолчанию при запускеmake.
Переходим на следующий уровень и рассмотрим Makefile в действии!
Уровень 2. “Круто, но хочется большего”
Подстановка переменных довольно часто встречается во всех аспектах программирования. В этом плане Makefile — не исключение.
Так как же применять переменные среды (по умолчанию)?
Допустим, нужно организовать сборку приложения с разными аргументами, например ENV_VAR. В этом случае Makefile принимает следующий вид:
NAME := my-app
DOCKER := $(shell command -v docker 2> /dev/null)
ENV_VAR := $(shell echo $${ENV_VAR-development}) # Примечание: двойной символ $ для экранирования
.PHONY: build
build: ## сборка Docker-образа на основе shell ENV_VAR
@if [ -z $(DOCKER) ]; then echo "docker is missing."; exit 2; fi # tip
@docker build -t $(NAME) . --build-arg ENV_VAR=$(ENV_VAR)
Ключевые понятия уровня 2:
.PHONY. По умолчаниюmakeпринимает цели<target>за файлы. Если же они таковыми не являются, то пометьте их с помощью директивы.PHONY, особенно при совпадении имени файла и цели. Более подробная информация по ссылке.- Объявление переменной
Makefileс помощью синтаксиса=или:=(ссылка). :=означает однократное выполнение инструкции.=указывает на выполнение инструкции в каждом случае. Например, при необходимости в новом значенииdateпри каждом вызове функции.- С помощью переменной среды можно проверить факт существования команды, как показано в инструкции
if.
Дополнительная информация:
echo $${ENV_VAR-development}устанавливаетENV_VARна основе текущей среды shell со значением по умолчанию дляdevelopment.- Как вариант,
makeпозволяет передавать переменные и переменные среды из командной строки, напримерENV_VAR=development make build.
Теперь вы знаете достаточно, чтобы создавать Makefile для небольших и средних проектов.
Двигаемся дальше и расширяем наши возможности с Makefile!
Уровень 3. “Покажи, что-нибудь особенное”
Мы подошли к самой излюбленной части. Думаю, что всем по нраву содержательное сообщение help.
Создадим полезную цель help на тот случай, если пользователям потребуется помощь по использованию Make в проекте:
.DEFAULT_GOAL := help
.PHONY: help
help:
@echo "Welcome to $(NAME)!"
@echo "Use 'make <target>' where <target> is one of:"
@echo ""
@echo " all run build -> stop -> run"
@echo " build build docker image based on shell ENV_VAR"
@echo " stop stop docker container"
@echo " run run docker container"
@echo ""
@echo "Go forth and make something great!"
all: build stop run
.PHONY: build
build:
@docker build -t image-name . --build-arg ENV_VAR=foo
.PHONY: stop
stop:
@docker stop container-name || true && docker rm container-name || true
.PHONY: run
run:
@docker run -d -e ANOTHER_VAR=bar--name container-name image-name
.DEFAULT_GOAL. Ранее упоминалось о том, что при запускеmakeпервое правило выбирается по умолчанию. Инструкция.DEFAULT_GOALпозволяет отменить это действие и явно указать цель.- С помощью
.DEFAULT_GOALвы просто запускаетеmakeдля отображения сообщенияhelpкаждый новый раз.
Однако каждая новая цель в Makefile должна добавляться новой строкой echo.
Как вам идея о самодокументирующемся сообщении help?
Меняем цель help соответствующим образом:
.DEFAULT_GOAL := help
.PHONY: help
help: ## отображение данного сообщения help
@awk 'BEGIN {FS = ":.*##"; printf "\\nUsage:\\n make \\033[36m<target>\\033[0m\\n\\nTargets:\\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \\033[36m%-10s\\033[0m %s\\n", $$1, $$2 }' $(MAKEFILE_LIST)
Далее добавляем комментарии с помощью тега ##, чтобы вывести их как часть сообщения help:
all: build stop run ## run build -> stop -> run (запуск build -> stop -> run)
.PHONY: build
build: ## build docker image (сборка docker-образа)
@docker build -t image-name . --build-arg ENV_VAR=foo
.PHONY: stop
stop: ## stop running container (остановка запущенного контейнера)
@docker stop container-name || true && docker rm container-name || true
.PHONY: run
run: ## run docker container (запуск docker-контейнера)
@docker run -d -e ANOTHER_VAR=bar--name container-name image-name
В результате получаем отличное самодокументирующееся сообщение help:
$ make Usage: make <target> Targets: help display this help all run build -> stop -> run build build docker image stop stop running container run run docker container
Уровень 4. “Статья не включает детальный разбор темы!”
Этот уровень предполагает самостоятельное погружение в тему Makefile и расширение знаний на основе качественных обучающих руководств.
Заключение
Даже если вы пишите отличную документацию, Makefile наверняка окажется намного более надежным инструментом. Его преимущество заключается в строгом принципе документирования выполняемых действий.
Если вы участвуете в проектах с открытым ПО, то задействуйте Makefile для определения рабочих процессов разработки.
Послесловие. Текущее состояние Makefile
Makefile появился в 1976 году и с тех пор прошел через взлеты и падения.
Чаще всего Make критикуют за сложный синтаксис. Как следствие, программисты разделились на поклонников и противников Makefile.
Думаю, тут дело вкуса. У каждого есть свои предпочтения. Я вот не вижу смысла загружать еще одну новомодную программу/инструмент/фреймворк/библиотеку по многим другим причинам.
Ведь Makefile “работает”.
Читайте также:
- Как написать красивый и информативный README.md
- Слабо решить эти задачи по программированию?
- С этими советами у junior-разработчиков не возникнет проблем на их первой работе
Читайте нас в Telegram, VK и Дзен
Перевод статьи Jerry Ng: 4 Levels of How To Use Makefile





