A re-usable help command for Makefiles

Supporting 'help' as a target within Makefiles for self-documentation

Table Of Contents

Today I Explained

Makefiles are a solid approach for having a lightweight task runner within a repository. Other tools for task runners exist that can be used for running common actions or useful scripts, but Makefiles have an appealing labyrinthine simplicity to them. One of these benefits is just familiarity with the syntax:

action:
  # do an action
  rm .env

One of the rough points about using Makefiles is making accessible the list of tasks that can be run. As make doesn’t support any make help or built-in docgen, it results in various patterns of parsing the Makefile to generate a help document from comments within the Makefile. The approach that I tend to adopt within repositories is a single line comment following the rule declaration (init: # Initialize the repositoriy resources).

The make help rule is then left up to the development environment to be defined. The Makefile of the repository is just responsible for loading in any global makefile rules from the directory GLOBAL_MAKEFILE_LIBRARY, defined at the root of the Makefile like so:

ifdef GLOBAL_MAKEFILE_LIBRARY
  include $(wildcard $(GLOBAL_MAKEFILE_LIBRARY)/*.mk)
endif

The definition of the help rule is then a file named help.mk located within a GLOBAL_MAKEFILE_LIBRARY directory typically encoded into a development container, like such:

.DEFAULT_GOAL := help
NC = \033[0m
ERR = \033[31;1m
TAB := '%-20s'

clr_cmd := $(shell tput setaf 202)
clr_header := $(shell tput setaf 250)
clr_make := $(shell tput setaf 240)
clr_desc := $(shell tput setaf 250)
bold := $(shell tput bold)
underline := $(shell tput smul)
reset := $(shell tput sgr0)

.PHONY: help
help:
	@printf '$(clr_header)$(shell cat docs/title.txt)$(reset)\n'
	@printf '$(clr_desc)  > $(shell cat docs/tagline.txt)$(reset)\n\n'
	@printf '    $(underline)$(clr_header)Commands:$(reset)\n\n'

	@grep -E '^([a-zA-Z0-9_-]+\.?)+:.+#.+$$' $(MAKEFILE_LIST) \
		| cut -d':' -f2- \
		| grep -v '^check-' \
		| grep -v '^env-' \
		| grep -v '^arg-' \
		| sed 's/:.*#/: #/g' \
		| awk 'BEGIN {FS = "[: ]+#[ ]+"}; \
		{printf " $(clr_make)   make $(reset)$(clr_cmd)$(bold)$(TAB) $(reset)$(clr_desc)# %s$(reset)\n", \
			$$1, $$2}'

	@printf '\n'
	@grep -E '^([a-zA-Z0-9_-]+\.?)+:(\s+|\w+)*$$' $(MAKEFILE_LIST) \
		| cut -d':' -f2- \
		| (grep -v help || true) \
		| awk 'BEGIN {FS = ":"}; \
		{printf " $(clr_make)   make $(reset)$(clr_cmd)$(bold)$(TAB)$(reset)\n", \
			$$1}'

A Makefile rule definition for help that emits the contents of the files docs/title.txt, docs/tagline.txt, then emits a list of commands in the Makefile excluding any check-, env-, or arg- prefixed rules

If a GLOBAL_MAKEFILE_LIBRARY doesn’t exist, then the Makefile behaves just as any other Makefile without a built-in make help, while if the GLOBAL_MAKEFILE_LIBRARY exists with a defined help rule, it will be generally customized to the standards of the development containers in-use.