Makefiles commonly contain "phony" targets ("goals") that run some task. Typical examples include make all, make test, or make clean. It is a good idea to document all phony targets: running make help should print out a summary, resulting in a "self-documenting" Makefile. Ideally, help is the default target, so that just running make without any arguments prints the documentation.
To achieve this, a short description of each target should be included on the same line as the target, separated by ##. For example:
.PHONY: help all clean test .DEFAULT_GOAL := help help: ## Show this help @echo "N/A" all: ## Compile all binaries # TODO clean: ## Remove all compilation artifacts # TODO test: ## Run the tests # TODO
All phony targets should be listed in .PHONY. The .DEFAULT_GOAL should be set to help (and the help target should be the first phony target, just for good measure).
Now, we'll embed a small script in the Makefile so that the Makefile processes itself, finds all lines of the form target: ## description and prints them in a nicely formatted table. This embedded script can be written in whatever language/tool is mostly likely to be available on the system. If the Makefile is for a Python project, it might be best to use a Python script.
The goal would be for make (or make help) to print
help Show this help all Compile all binaries clean Remove all compilation artifacts test Run the tests
Below are examples for how to achieve this with Python, Julia, and plain shell scripting.
Python
.PHONY: help
.DEFAULT_GOAL := help
define PRINT_HELP_PYSCRIPT
import re, sys
for line in sys.stdin:
match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line)
if match:
target, help = match.groups()
print("%-20s %s" % (target, help))
endef
export PRINT_HELP_PYSCRIPT
PYTHON ?= python3
help: ## Show this help
@$(PYTHON) -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)
The width of the padding (20) could be adjusted to the widths of the targets defined in the Makefile. Note that inside the PRINT_HELP_PYSCRIPT, lines are indented with 4 spaces (not tabs). However, the recipe for help (the line @$(PYTHON) -c…) must be indented with a single tab.
Julia
.PHONY: help
.DEFAULT_GOAL := help
define PRINT_HELP_JLSCRIPT
rx = r"^([a-z0-9A-Z_-]+):.*?##[ ]+(.*)$$"
for line in eachline()
m = match(rx, line)
if !isnothing(m)
target, help = m.captures
println("$$(rpad(target, 20)) $$help")
end
end
endef
export PRINT_HELP_JLSCRIPT
JULIA ?= julia
help: ## Show this help
@$(JULIA) -e "$$PRINT_HELP_JLSCRIPT" < $(MAKEFILE_LIST)
This is a direct translation of the above Python script.
Shell
.PHONY: help
.DEFAULT_GOAL := help
define PRINT_HELP_PROLOGUE
This Makefile should work in most shell environments.
Running just `make` should be equivalent to `make help`
endef
export PRINT_HELP_PROLOGUE
help: ## Show this help
@echo "$$PRINT_HELP_PROLOGUE"
@grep -E '^([a-zA-Z_-]+):.*## ' $(MAKEFILE_LIST) | awk -F ':.*## ' '{printf "%-20s %s\n", $$1, $$2}'
The above shell script version also includes a "prologue" of help text to be printed before the table of targets. Similar prologues (or epilogues) could also be included in the Python and Julia versions by adding appropriate print statements to the PRINT_HELP_PYSCRIPT or PRINT_HELP_JLSCRIPT.