# 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 |
@o5, simple regex for makefile eval is not enough, you must use recurrent makefile with --print-data-base
to extract evaluated recipes.
See here: https://stackoverflow.com/questions/4219255/how-do-you-get-the-list-of-targets-in-a-makefile/26339924#26339924
This gist totally "triggered" me and kickstarted all my gizmos quite some time back - thank you so much for it! Also thanks to @lordnynex and @HarasimowiczKamil for your comments. Sorry it took me some years to comment here but better later than never ;-)
I took the idea a bit further and puzzled together some other bits and pieces (e.g. including variables) - have a look at https://github.com/25th-floor/docker-make-stub ... Feedback is highly appreciated!
First go for a single line version.. fully awk.. place this at the end of your make file. it will print all recipes.
## print help
help:
@printf "\nusage : make <commands> \n\nthe following commands are available : \n\n"
@cat Makefile | awk '1;/help:/{exit}' | awk '/##/ { print; getline; print; }' | awk '{ getline x; print x; }1' | awk '{ key=$$0; getline; print key "\t\t\t\t " $$0;}'
@printf "\n"
to yield
usage : make <commands>
the following commands are available :
help: ## print help
some issues in removing the ":" and "##" as well as trying to get colours for the menu item. otherwise its quite usable. this way is much faster as it does not loop and try to modify each line.
You don't need this whole pipeline. One sed is enough:
help: ##- Show this help.
@sed -e '/#\{2\}-/!d; s/\\$$//; s/:[^#\t]*/:/; s/#\{2\}- *//' $(MAKEFILE_LIST)
Comments on this alternative:
- sed can accept multiple commands delimited with
;
. - sed can skip lines not matching a pattern with the
/pattern/!d
command. This saves us from having to use grep. - Lines consisting entirely of
#
are often used as horizontal rules in Makefiles. Using##-
as the delimiter prevents them from being printed. s/:[^#\t]*/:/
is used to eliminate the need to add an additional entry for the target help message./#\{2\}-/!d
is used instead of the equivalent/##-/!d
so that the line containing the sed command doesn't match.
Golfing here. One awk
might be enough
.PHONY: help help_target-funky+names.0k and_with_2_targets_and_spaces_like_bison phony targets
help: ## Show this help.
help: phony targets ## Show this help here too.
@awk '/^[a-zA-Z0-9\-_+. ]*:[a-zA-Z0-9\-_+. ]*#{2}/ { print; }' $(MAKEFILE_LIST)
help_target-funky+names.0k and_with_2_targets_and_spaces_like_bison: ## Show this help too.
It works nicely. You might want to make a minor improvement:
@fgrep -h "##" $(MAKEFILE_LIST) | sed -e 's/\(\:.*\#\#\)/\:\ /' | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//'
This will print only the targets without their dependencies.
all: Runs the tests and builds the binary deliverable of the project using cython.
clean: Cleans all the intermediate files and folders previously generated.
compile-proto: Compiles the models protocol buffer files and gRPC stubs.
prereqs: Installs all the prerequisites for this project.
test: Runs all the available tests for this project.
cythonize: Builds a binary deliverable using Cython.
Single line with formating
help: ## Show this help
@egrep '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
Example from here https://stackoverflow.com/questions/35730218/how-to-automatically-generate-a-makefile-help-command
Can someone here help me get a awk
working with this Makefile syntax?
##
## Section
##
## Start container
start:
@echo "Start container"
@muuvmuuv have a look if you can get something going here: https://awk.js.org/
@muuvmuuv If you look at the link @WarFox posted (https://stackoverflow.com/questions/35730218/how-to-automatically-generate-a-makefile-help-command), there's an example there that might work for you.
.PHONY: help
# Show this help
help:
@awk '/^#/{c=substr($$0,3);next}c&&/^[[:alpha:]][[:alnum:]_-]+:/{print substr($$1,1,index($$1,":")),c}1{c=0}' $(MAKEFILE_LIST) | column -s: -t
Adjusted for the AWK web REPL - https://awk.js.org/?gist=238f6158b3673074ee92edc93eb84a91
... produces when using the Makefile
as input
help: Show this help.
I used the suggestion from @o5 with my makefile and it looks awesome to me
##@ Utility
help: ## Display this help
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
clean: ## Tidy up local environment
find . -name \*.pyc -delete
find . -name __pycache__ -delete
I used the suggestion from @o5 with my makefile and it looks awesome to me
##@ Utility help: ## Display this help @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) clean: ## Tidy up local environment find . -name \*.pyc -delete find . -name __pycache__ -delete
Small buglet. Need to add 0-9
to your [a-zA-Z_-]
expression to capture targets with numbers in the name. And probably a space also, as I see it doesn't capture targets like foobar : ## This is a foobar test
Is it crazy enough to show the Makefile help via Docker? This is exactly what I did.
# Show this help
help:
@cat $(MAKEFILE_LIST) | docker run --rm -i xanders/make-help
Source: https://github.com/Xanders/make-help
Screenshot:
A slight improvement on a few preceding:
help: ## show help message
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[$$()% a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
test: ## plain
$dollar: ## leading dollar
percent%: ## percent included
(paren): ## parenthesis
$(both): ## both
space : ## space before colon
➜ make
Usage:
make
help show help message
test plain
$dollar leading dollar
percent% percent included
(paren) parenthesis
$(both) both
space space before colon
note: Not shown here: the targets are colored.
This updated expression still fails to find targets with numbers in the name. I think you need 0-9
in front of the a-zA-Z
.
.PHONY: jflash
jflash: ## Program xxx using last J-Link specified (600112147 if none)
ifeq (,$(wildcard $(LAST_SEGGER_FILE)))
@echo "600112147" > $(LAST_SEGGER_FILE)
endif
$(JFLASH) -min -openprjsxxx_l`cat $(LAST_CPU_MODEL_FILE)`.jflash -open$(COMBINEDHEX) -usb`cat $(LAST_SEGGER_FILE)` -eliminate -auto -startapp -exit
.PHONY: jflash_147
jflash_147: ## Program xxx using J-Link 600112147
echo "600112147" > $(LAST_SEGGER_FILE)
$(JFLASH) -min -openprjsxxx_l`cat $(LAST_CPU_MODEL_FILE)`.jflash -open$(COMBINEDHEX) -usb`cat $(LAST_SEGGER_FILE)` -eliminate -auto -startapp -exit
.PHONY: jflash_977
jflash_977: ## Program xxx using J-Link 600108977
echo "600108977" > $(LAST_SEGGER_FILE)
$(JFLASH) -min -openprjxxx_l`cat $(LAST_CPU_MODEL_FILE)`.jflash -open$(COMBINEDHEX) -usb`cat $(LAST_SEGGER_FILE)` -eliminate -auto -startapp -exit
.PHONY: jflash_756
jflash_756: ## Program xxx using J-Link 600103756
echo "600103756" > $(LAST_SEGGER_FILE)
$(JFLASH) -min -openprjsxxx_l`cat $(LAST_CPU_MODEL_FILE)`.jflash -open$(COMBINEDHEX) -usb`cat $(LAST_SEGGER_FILE)` -eliminate -auto -startapp -exit
.PHONY: fix_crlf
fix_crlf: ## Convert all .c, .h, and .s files to Unix EOL, set 0x644 permissions
chown -R $(USERNAME):$(GROUPNAME) *
find . -type f -name \*.c -exec chmod 644 {} \;
find . -type f -name \*.h -exec chmod 644 {} \;
find . -type f -name \*.c -exec dos2unix -q {} \;
find . -type f -name \*.h -exec dos2unix -q {} \;
find . -type f -name \*.s -exec dos2unix -q {} \;
$ make help
Usage:
make
jflash Program xxx using last J-Link specified (600112147 if none)
fix_crlf Convert all .c, .h, and .s files to Unix EOL, set 0x644 permissions
This updated expression still fails to find targets with numbers in the name. I think you need
0-9
in front of thea-zA-Z
.
Yeah, this works:
help: ## show help message
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[$$()% 0-9a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
$ make help
Usage:
make
help show help message
jflash Program xxx using last J-Link specified (600112147 if none)
jflash_147 Program xxx using J-Link 600112147
jflash_977 Program xxx using J-Link 600108977
jflash_756 Program xxx using J-Link 600103756
fix_crlf Convert all .c, .h, and .s files to Unix EOL, set 0x644 permissions
I had a case where my targets names contain semicolons (escaped with a backslash), to create "namespaces":
sw\:provision: ## Provision machine "SW"
@vagrant provision SW
$ make help
Usage:
make
help Show help message
provision Provision all machines
s:provision Provision machine "S"
sw:provision Provision machine "SW"
Here is the modified version to make it work:
help: ## Show help message
@awk 'BEGIN {FS = ": .*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[$$()% 0-9a-zA-Z_-]+(\\:[$$()% 0-9a-zA-Z_-]+)*:.*?##/ { gsub(/\\:/,":", $$1); printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
And what I changed (based on the latest version just above):
Here is how cargo-quickinstall does it in it's current Makefile
.PHONY: help
help: ## Display this help screen
@grep -E '^[a-z.A-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
Simple and efficient, here is the result
Here is how cargo-quickinstall does it in it's current Makefile
.PHONY: help help: ## Display this help screen @grep -E '^[a-z.A-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'Simple and efficient, here is the result
If your makefile has something like:
-include .env
your proposal will display:
Makefile Display this help screen
Here is how cargo-quickinstall does it in it's current Makefile
.PHONY: help help: ## Display this help screen @grep -E '^[a-z.A-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'If your makefile has something like:
-include .env
your proposal will display:
Makefile Display this help screen
really?
But... assuming you write your own stuff and no try to retrofit someone else's code... why wouldnt you just use (per example....)
.INCLUDEDIRS : /usr/blah/blah
.INCLUDE : somefile
.INCLUDE .IGNORE : another_file /etc/yetanotherfile
Wouldn't NOT using the double ##
hashtags make them be ignored by the grep rule?
Can you post an example so i can replicate? i guess its also good practice when making such affirmatiin... i mean, it helps everyone now and in the future be able to replicate and understand fast, too
Thanks!
Here is how cargo-quickinstall does it in it's current Makefile
.PHONY: help help: ## Display this help screen @grep -E '^[a-z.A-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'If your makefile has something like:
-include .env
your proposal will display:
Makefile Display this help screen
Here's my fix for that:
.PHONY: help
help: ## Show this help.
@grep -hE '^[A-Za-z0-9_ \-]*?:.*##.*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
Added -h
to hide the file name and tweaked the regex to include numbers and possible whitespace before the target's :
, although it still doesn't cover all of the possible characters in a target identifier.
To golf a little bit more, the grep expression of the previous post can be shrinked down to grep -hP '^[\w \-]*?:.*##.*$$'
with the perl regex flag and \w
that is an alias for [a-zA-Z0-9_]
.
just to add another variant to the pile..
- uses only
sed
for pre-processing the makefile - uses
tput
to detect if we should use colour - uses
column
for what it does best
help:
@sed \
-e '/^[a-zA-Z0-9_\-]*:.*##/!d' \
-e 's/:.*##\s*/:/' \
-e 's/^\(.\+\):\(.*\)/$(shell tput setaf 6)\1$(shell tput sgr0):\2/' \
$(MAKEFILE_LIST) | column -c2 -t -s :
What's the most cross-platform solution, without colors and which displays targets even if they don't have comment?
@arvenil i might be wrong but i think that a makefile works using the same system, across all platforms? Please correct me if i am wrong, i am not very cross platform myself... As for color, i guess the terminal will be responsible for displaying it (or not) depending on the context. Many tools have the ability to force the suppression of color, too.
@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.
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'm using version from here, it works great but I would like to support also templates. I tried to modify
[a-zA-Z_-]
but I stuck :/Makefile
Current output
Needed output
Is there someone who can help?
Thank you! :)