Makefile ToC

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

Передо мной встала задача по составлению справки по целям сборки в текущем Makefile проекта. Из коробки древняя утилита не позволяет получить список целей сборки и их описание, которого не предполагалось.

Поискав готовые решения и убедившись, что большая часть из них не сработает на macOS из-за её родства с BSD-системами, я принял решение подумать над собственным вариантом генерации оглавления.

Итак, перед нам две задачи:

  • Вызов команды make без аргументов выводит список целей сборки и их описание;
  • Сбор целей и их описаний во всех текущих Makefile-ах проекта.

Первая задача решается кросс-системно и довольно легко. Достаточно указать переменную .DEFAULT_GOAL в самом начале Makefile:

.DEFAULT_GOAL := help

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

.DEFAULT_GOAL := help

.PHONY: help
help:
	@echo This is help target

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

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

.PHONY: foo
foo: ## do some foo
	@echo foo

Здесь, после двух знаков ## следует комментарий, который будет выводиться как справочная информация. Имя цели — описание цели, всё рядом и достаточно удобно.

Теперь необходимо “оживить” команду make help, чтобы справочная информация генерировалась динамически.

Вот моё решение:

.PHONY: help
help: ## shows this help message
	@grep --fixed-strings --no-filename "##" $(MAKEFILE_LIST) \
		| grep -v 'grep --fixed-strings' \
		| sed -e 's/:/	/' \
		| awk 'BEGIN { FS="#"; } { print $$1 $$3 }' \
		| column -t -s $$'\t'

Что здесь происходит? Всё достаточно просто:

  • Встроенная переменная MAKEFILE_LIST содержит список путей ко всем файлам Makefile проекта;
  • С помощью команды grep находятся все строки, содержащие в себе два символа ##, которые будут использоваться для добалвения комментария к цели сборки;
  • Далее, sed подменяет символ : на символ табуляции;
  • Самая главная роль отводится команде awk, которая принимает на себя вывод используя разделитель полей # (переменная FS) и выводит имена целей сборки и комментарии к ним последовательно;
  • За аккуратный вывод в две ровные колонки отвечает команда column.

Ознакомьтесь с демонстрационным примером такого Makefile:

.DEFAULT_GOAL := help

.PHONY: help
help: ## shows this help message
	@grep --fixed-strings --no-filename "##" $(MAKEFILE_LIST) \
		| grep -v 'grep --fixed-strings' \
		| sed -e 's/:/	/' \
		| awk 'BEGIN { FS="#"; } { print $$1 $$3 }' \
		| column -t -s $$'\t'

.PHONY: foo
foo: ## do some foo
	@echo foo

.PHONY: bar
bar: ## do some bar
	@echo bar

.PHONY: some-long-named-makefile-target
some-long-named-makefile-target: ## this is some-long-named-makefile-target
	@echo some-long-named-makefile-target

.PHONY: ignored
ignored:
	@echo ignored

А вот вывод команды make без аргументов:

$ make
help                               shows this help message
foo                                do some foo
bar                                do some bar
some-long-named-makefile-target    this is some-long-named-makefile-target

Аккуратно, не правда ли?

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