-
-
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 |
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.
@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!
@arvenil Depends on what you want to depend on? You have choices. awk, sed, grep, bash, sh, zsh, GNU Make, BSD Make, Linux, MacOS, BSD, WSL, RHEL/Fedora, Debian, Ubuntu, others. For widest cross compatibility, GNU Make and bash are fairly safe. But bash has been losing install base and mind share with MacOS and other smaller Linux distros changing their default shells.