Help в Makefile

QOL

Мне нравится пользоваться Makefile и командами вроде make buuild/run/whatever. Их обычно ассоциируют с C++ сборками, но и для проектов на любых других языках они могут оказаться полезны.

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

Мне нужна помощь

CLI утилиты неудобны без help / --help. Любой инструмент командной строки должен иметь возможность подсказать, какие команды доступны. Проблема в том, что в Makefile нет такой возможности из коробки - но никто не мешает её добавить.

Название и описание команд

На такой вариант решения я натнулся в блоге Luciano Nooijen. Он позволяет получить простой список команд с их описанием:

.PHONY: help
help: ## Shows all commands
	@echo 'All Makefile commands:'
	@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

Этот код использует grep для поиска строк, соответствующих формату команды с комментарием, а затем форматирует их с помощью awk с цветной подсветкой.

Чтобы добавить описание к команде используется двойной #:

.PHONY: migrate
migrate:  ## Migrate database
	uv run python3 -m speechkit.infrastructure.database.migrations upgrade head

Результат будет выглядеть так:

Базовый вариант

Здорово, можно остановиться здесь, если вам этого достаточно. Но можно пойти чуть дальше.

Группировка команд

Если у вас много команд в Makefile, имеет смысл сгруппировать их по категориям. Вот более сложный вариант с использованием Perl:

HELP_FUN = \
	%help; while(<>){push@{$$help{$$2//'options'}},[$$1,$$3] \
	if/^([\w-_]+)\s*:.*\#\#(?:@(\w+))?\s(.*)$$/}; \
	print"$$_:\n", map"  $$_->[0]".(" "x(20-length($$_->[0])))."$$_->[1]\n",\
	@{$$help{$$_}},"\n" for keys %help; \

help: ##@Help Show this help
	@echo -e "Usage: make [target] ...\n"
	@perl -e '$(HELP_FUN)' $(MAKEFILE_LIST)

Для добавления команды в определенную группу используется специальный формат комментария:

.PHONY: migrate
migrate:  ##@Database Migrate database
	uv run alembic upgrade head

Здесь Database — название группы, в которую будет включена команда migrate.

Результат будет более структурированным:

Группы команд

Цветное выделение

Для лучшей визуальной организации можно добавить цветной вывод:

YELLOW := \033[33m
GREEN := \033[32m
CIAN := \033[36m
BOLD := \033[1m
RESET := \033[0m

HELP_FUN = \
	%help; while(<>){push@{$$help{$$2//'Others'}},[$$1,$$3] \
	if/^([\w-_]+)\s*:.*\#\#(?:@(\w+))?\s(.*)$$/}; \
	print"$(BOLD)$(YELLOW)$$_:$(RESET)\n", map"  $(GREEN)$$_->[0]$(RESET)".(" "x(20-length($$_->[0])))."$(CIAN)$$_->[1]$(RESET)\n",\
	@{$$help{$$_}},"\n" for keys %help; \

Теперь вывод команды make help станет более удобочитаемым:

Группы команд с цветами

Обработка некорректных целей

Важная часть хорошего CLI — понятные сообщения об ошибках. Добавим обработку случаев, когда пользователь ошибся в команде:

.DEFAULT:
	@echo "No such command (or you pass two or many targets to ). List of possible commands: make help"

Раз уж команда make help реализована, то не лишним будет о ней упомянуть в тексте сообщения об ошибке.

Можно пойти дальше и сделать команду help запускаемой по умолчанию (когда вызывается просто make без аргументов):

.DEFAULT_GOAL := help

Заключение

Хотя существуют более продвинутые инструменты для создания CLI, Makefile - хорошее место для старта. Надеюсь, пара советов выше поможет сделать ваши make-команды чуть более удобными и понятными.