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.