-
-
Save prwhite/8168133 to your computer and use it in GitHub Desktop.
# Add the following 'help' target to your Makefile | |
# And add help text after each target name starting with '\#\#' | |
help: ## Show this help. | |
@fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//' | |
# Everything below is an example | |
target00: ## This message will show up when typing 'make help' | |
@echo does nothing | |
target01: ## This message will also show up when typing 'make help' | |
@echo does something | |
# Remember that targets can have multiple entries (if your target specifications are very long, etc.) | |
target02: ## This message will show up too!!! | |
target02: target00 target01 | |
@echo does even more |
@arvenil A bit of golfing and I got this to sort of work with the only deps being GNU make
and bash
SHELL:=/bin/bash
.PHONY: help help_target-funky+names.0k and_with_2_targets_and_spaces_like_bison
help_target-funky+names.0k and_with_2_targets_and_spaces_like_bison: ## Funky ones & bison dual target display ok
echo "bad - why are you not displaying?"
help: ## bash help
help: ## moar bash help
@RE='^[a-zA-Z0-9 ._+-]*:[a-zA-Z0-9 ._+-]*##' ; while read line ; do [[ "$$line" =~ $$RE ]] && echo "$$line" ; done <$(MAKEFILE_LIST) ; RE=''
Which for make help
outputs
$ make help
help_target-funky+names.0k and_with_2_targets_and_spaces_like_bison: ## Funky ones & bison dual target display ok
help: ## bash help
help: ## moar bash help
I thought I'd throw my hat into the ring:
## Usage: make <target>
## This makefile generates a WhatzIt report, suitable for pasting into Slack.
## It prints the report to the screen, and also puts it in the copy-paste buffer
##
## Possible Targets:
usage: ## Displays this message
@gawk -vG=$$(tput setaf 2) -vR=$$(tput sgr0) ' \
match($$0,"^(([^:]*[^ :]) *:)?([^#]*)##(.*)",a) { \
if (a[2]!="") {printf "%s%-36s%s %s\n",G,a[2],R,a[4];next}\
if (a[3]=="") {print a[4];next}\
printf "\n%-36s %s\n","",a[4]\
}\
' $(MAKEFILE_LIST)
example.txt : example.json ## Make the Example
Lines starting with ##
are printed as-is (the text after the ##
).
Lines starting with a target and ending with ##
get the target printed in green, and the text after the ##
printed starting in column 37.
A further refinement would be to send the output through column -t -s@
or similar, to align the columns efficiently.
Since it's the first target in the Makefile, it runs if make
is run with no arguments. I hate playing "guess the usage message flag". 🎏 😃
I'll look into Guile, didn't know about it, and I thought I'd read the make
docs enough times to have stumbled over it. 😃
Here's an iteration on @PenelopeFudd's version with a few small improvements:
- It can be added to the end of the
Makefile
because.DEFAULT_GOAL
is used - It ignores commented targets
- It ignores certain kinds of headers that can be useful to organize targets not mentioned in the help text
##
## These lines show up in the final output,
##including leading whitespace.
##
## They are useful for end-user documentation.
##
# Note that sections like the following are ignored in the help output; they are
# meant for developer documentation.
##
## generate target files
##
################################################################################
# File copies - easy stuff
################################################################################
target3.txt: target2.txt ## Generate target3.txt
cp $< $@
target2.txt: target1.txt ## Generate target2.txt
cp $< $@
################################################################################
# This one could be complicated or require further explanation to a developer.
################################################################################
target1.txt: # (this one is undocumented)
touch $@
##
## other stuff
##
file4.txt: ## Generate file4.txt
touch $@
##
## tools and help
##
lint: ## run linter
################################################################################
# Help target
################################################################################
help:: ## show this help text
@gawk -vG=$$(tput setaf 2) -vR=$$(tput sgr0) ' \
match($$0, "^(([^#:]*[^ :]) *:)?([^#]*)##([^#].+|)$$",a) { \
if (a[2] != "") { printf " make %s%-18s%s %s\n", G, a[2], R, a[4]; next }\
if (a[3] == "") { print a[4]; next }\
printf "\n%-36s %s\n","",a[4]\
}' $(MAKEFILE_LIST)
@echo -e "" # blank line at the end
.DEFAULT_GOAL := help
##@
##@ Clean build files commands
##@
kernel-%-clean: ##@ Clean kernel build files with specified architecture
##@ e.g. kernel-amd64-clean / kernel-arm64-clean
$(MAKE) -C ./arch/kernel/$* clean
rootfs-%-clean: ##@ Clean rootfs build files with specified architecture
##@ e.g. rootfs-amd64-clean / rootfs-arm64-clean
$(MAKE) -C ./arch/rootfs/$* clean
clean: ##@ Clean all build files
$(MAKE) kernel-amd64-clean
$(MAKE) kernel-arm64-clean
$(MAKE) rootfs-amd64-clean
$(MAKE) rootfs-arm64-clean
##@
##@ Misc commands
##@
help: ##@ (Default) Print listing of key targets with their descriptions
@printf "\nUsage: make <command>\n"
@grep -F -h "##@" $(MAKEFILE_LIST) | grep -F -v grep -F | sed -e 's/\\$$//' | awk 'BEGIN {FS = ":*[[:space:]]*##@[[:space:]]*"}; \
{ \
if($$2 == "") \
pass; \
else if($$0 ~ /^#/) \
printf "\n%s\n", $$2; \
else if($$1 == "") \
printf " %-20s%s\n", "", $$2; \
else \
printf "\n \033[34m%-20s\033[0m %s\n", $$1, $$2; \
}'
I tried @BlackHole1 's solution and got this :(
Usage: make <command>
awk: illegal statement
input record number 1, file
source line number 1```
I tried @BlackHole1 's solution and got this :(
Usage: make <command> awk: illegal statement input record number 1, file source line number 1```
@Windowsfreak Can you share the contents of your makefile? It works fine on my local machine.
@Windowsfreak I just reproduced this issue on macOS. you need to install gawk first (brew install gawk).
AWK := awk
ifeq ($(shell uname -s), Darwin)
AWK = gawk
ifeq (, $(shell which gawk 2> /dev/null))
$(error "gawk not found")
endif
endif
- @grep -F -h "##@" $(MAKEFILE_LIST) | grep -F -v grep -F | sed -e 's/\\$$//' | awk 'BEGIN {FS = ":*[[:space:]]*##@[[:space:]]*"}; \
+ @grep -F -h "##@" $(MAKEFILE_LIST) | grep -F -v grep -F | sed -e 's/\\$$//' | $(AWK) 'BEGIN {FS = ":*[[:space:]]*##@[[:space:]]*"}; \
Full Code:
AWK := awk
ifeq ($(shell uname -s), Darwin)
AWK = gawk
ifeq (, $(shell which gawk 2> /dev/null))
$(error "gawk not found")
endif
endif
##@
##@ Clean build files commands
##@
kernel-%-clean: ##@ Clean kernel build files with specified architecture
##@ e.g. kernel-amd64-clean / kernel-arm64-clean
$(MAKE) -C ./arch/kernel/$* clean
rootfs-%-clean: ##@ Clean rootfs build files with specified architecture
##@ e.g. rootfs-amd64-clean / rootfs-arm64-clean
$(MAKE) -C ./arch/rootfs/$* clean
clean: ##@ Clean all build files
$(MAKE) kernel-amd64-clean
$(MAKE) kernel-arm64-clean
$(MAKE) rootfs-amd64-clean
$(MAKE) rootfs-arm64-clean
##@
##@ Misc commands
##@
help: ##@ (Default) Print listing of key targets with their descriptions
@printf "\nUsage: make <command>\n"
@grep -F -h "##@" $(MAKEFILE_LIST) | grep -F -v grep -F | sed -e 's/\\$$//' | $(AWK) 'BEGIN {FS = ":*[[:space:]]*##@[[:space:]]*"}; \
{ \
if($$2 == "") \
pass; \
else if($$0 ~ /^#/) \
printf "\n%s\n", $$2; \
else if($$1 == "") \
printf " %-20s%s\n", "", $$2; \
else \
printf "\n \033[34m%-20s\033[0m %s\n", $$1, $$2; \
}'
@Windowsfreak @BlackHole1 no need for gawk
, just replace pass
with empty printf
and problem solved. BSD awk
does not know pass
command.
if($$2 == "") \
- pass; \
+ printf ""; \
$ PATH="/usr/bin:/usr/sbin:/bin:/sbin" make help
Usage: make <command>
Clean build files commands
kernel-%-clean Clean kernel build files with specified architecture
e.g. kernel-amd64-clean / kernel-arm64-clean
rootfs-%-clean Clean rootfs build files with specified architecture
e.g. rootfs-amd64-clean / rootfs-arm64-clean
clean Clean all build files
Misc commands
help (Default) Print listing of key targets with their descriptions
Many good suggestions.
I felt the help syntax a bit ugly when having several target dependancies.
99% of my targets are non-files, and therefor PHONY targets. So I put the check on the PHONY targets only.
This doesn't fit everyone, but maybe gives some one some thougths.
PHONY: help ## Show this help.
help:
@grep -he '^PHONY:.*##' $(MAKEFILE_LIST) | sed -e 's/ *##/:\t/' | sed -e 's/^PHONY: *//'
I think writing a "makefile-helper <makefile_list>" in perl will be my next approach. I got many Makefiles, and copy/paste any advanced script into each Makefile is just bad.
@kjellericson you could use include /absolute/or/relative/path/to/*.mk
instead of embedding it into each Makefile directly.
Hi, just discovered this gist/conversation from a google search. The solution by @BlackHole1 (Oct 26, 2023) meets my needs 90%.
But in some of our makefiles we have a dependency after the target, like this:
app: $(APP_FILE2).exe $(APP_FILE2).exe ##@ Build the applications
The items after the ":" are displayed in the help, and I really don't want this.
I am not conversant enough in awk/gawk to figure out how to suppress the display of the items after the ":"
Can anyone give me advice?
Thanks!
Hi @chrissv, you can repeat the target twice. Once for the help comment and the other one for the list of dependencies. With your example:
app: ##@ Build the applications
app: $(APP_FILE2).exe $(APP_FILE2).exe
Hi @chrissv, you can repeat the target twice. Once for the help comment and the other one for the list of dependencies. With your example:
app: ##@ Build the applications app: $(APP_FILE2).exe $(APP_FILE2).exe
That's a great suggestion, thanks!
One other note, since GNU Make is the most popular, it has a fairly powerful and underutilized optional integration of GNU Guile. GNU Guile is language in the Scheme & Lisp family of languages.
If Guile is a bridge too far, then consider bash's BASH_REMATCH facility.
It'll be a bit more code (file I/O, regexes, file parsing, etc) and you'll have to maintain it rather than depending on other well tested tooling like awk, sed, grep.