Skip to content

Instantly share code, notes, and snippets.

@klmr
Last active November 11, 2024 18:39
Show Gist options
  • Save klmr/575726c7e05d8780505a to your computer and use it in GitHub Desktop.
Save klmr/575726c7e05d8780505a to your computer and use it in GitHub Desktop.
Self-documenting makefiles

What is it?

A “simple” make rule that allows pretty-printing short documentation for the rules inside a Makefile:

screenshot

How does it work?

Easy: simply copy everything starting at .DEFAULT_GOAL := show-help to the end of your own Makefile (or include show-help-minified.make, and copy that file into your project). Then document any rules by adding a single line starting with ## immediately before the rule. E.g.:

## Run unit tests
test:
    ./run-tests

Displaying the documentation is done by simply executing make. This overrides any previously set default command — you may not wish to do so; in that case, simply remove the line that sets the .DEFAULT_GOAL. You can then display the help via make show-help. This makes it less discoverable, of course.

Thanks

Based on an idea by @marmelab.

# Example makefile with some dummy rules
.PHONY: all
## Make ALL the things; this includes: building the target, testing it, and
## deploying to server.
all: test deploy
.PHONY: build
# No documentation; target will be omitted from help display
build:
${MAKE} -C build all
.PHONY: test
## Run unit tests
test: build
./run-tests .
.PHONY: deply
## Deploy to production server
deploy: build
./upload-to-server . $$SERVER_NAME
.PHONY: clean
## Remove temporary build files
clean:
${MAKE} -C build clean
# Plonk the following at the end of your Makefile
.DEFAULT_GOAL := show-help
# Inspired by <http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html>
# sed script explained:
# /^##/:
# * save line in hold space
# * purge line
# * Loop:
# * append newline + line to hold space
# * go to next line
# * if line starts with doc comment, strip comment character off and loop
# * remove target prerequisites
# * append hold space (+ newline) to line
# * replace newline plus comments by `---`
# * print line
# Separate expressions are necessary because labels cannot be delimited by
# semicolon; see <http://stackoverflow.com/a/11799865/1968>
.PHONY: show-help
show-help:
@echo "$$(tput bold)Available rules:$$(tput sgr0)"
@echo
@sed -n -e "/^## / { \
h; \
s/.*//; \
:doc" \
-e "H; \
n; \
s/^## //; \
t doc" \
-e "s/:.*//; \
G; \
s/\\n## /---/; \
s/\\n/ /g; \
p; \
}" ${MAKEFILE_LIST} \
| LC_ALL='C' sort --ignore-case \
| awk -F '---' \
-v ncol=$$(tput cols) \
-v indent=19 \
-v col_on="$$(tput setaf 6)" \
-v col_off="$$(tput sgr0)" \
'{ \
printf "%s%*s%s ", col_on, -indent, $$1, col_off; \
n = split($$2, words, " "); \
line_length = ncol - indent; \
for (i = 1; i <= n; i++) { \
line_length -= length(words[i]) + 1; \
if (line_length <= 0) { \
line_length = ncol - indent - length(words[i]) - 1; \
printf "\n%*s ", -indent, " "; \
} \
printf "%s ", words[i]; \
} \
printf "\n"; \
}' \
| more $(shell test $(shell uname) == Darwin && echo '--no-init --raw-control-chars')
.DEFAULT_GOAL := show-help
# See <https://gist.github.com/klmr/575726c7e05d8780505a> for explanation.
.PHONY: show-help
show-help:
@echo "$$(tput bold)Available rules:$$(tput sgr0)";echo;sed -ne"/^## /{h;s/.*//;:d" -e"H;n;s/^## //;td" -e"s/:.*//;G;s/\\n## /---/;s/\\n/ /g;p;}" ${MAKEFILE_LIST}|LC_ALL='C' sort -f|awk -F --- -v n=$$(tput cols) -v i=19 -v a="$$(tput setaf 6)" -v z="$$(tput sgr0)" '{printf"%s%*s%s ",a,-i,$$1,z;m=split($$2,w," ");l=n-i;for(j=1;j<=m;j++){l-=length(w[j])+1;if(l<= 0){l=n-i-length(w[j])-1;printf"\n%*s ",-i," ";}printf"%s ",w[j];}printf"\n";}'|more $(shell test $(shell uname) == Darwin && echo '-Xr')
@bukowa
Copy link

bukowa commented Feb 15, 2023

@hilnius
To handle export VAR ?= something this works, i guess?

		-e "s|^\(export *\)\($(variable_regex)\)$(variable_assignment_regex)\($(value_regex)\)|$(global_variable_tag_start)\2$(global_variable_tag_end)$(value_tag_start)\3$(value_tag_end)|;" \

i also found improved: https://github.com/cert-manager/cert-manager/blob/7ce1f9cffb70eae4d3dd3572564a90f2553d3b52/make/help.mk

@takuyahara
Copy link

Inspired by deno task:

@echo "$$(tput setaf 2)Available rules:$$(tput sgr0)";sed -ne"/^## /{h;s/.*//;:d" -e"H;n;s/^## /---/;td" -e"s/:.*//;G;s/\\n## /===/;s/\\n//g;p;}" ${MAKEFILE_LIST}|awk -F === -v n=$$(tput cols) -v i=4 -v a="$$(tput setaf 6)" -v z="$$(tput sgr0)" '{printf"- %s%s%s\n",a,$$1,z;m=split($$2,w,"---");l=n-i;for(j=1;j<=m;j++){l-=length(w[j])+1;if(l<= 0){l=n-i-length(w[j])-1;}printf"%*s%s\n",-i," ",w[j];}}'

Screenshot 2023-05-09 at 12 59 03

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment