-
Star
(337)
You must be signed in to star a gist -
Fork
(48)
You must be signed in to fork a gist
-
-
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 |
Hi @muhmi, I try your solution in Ubuntu but I get this error 😞
'/bin/sh: 2: Syntax error: "(" unexpected
Makefile:144: fallo en las instrucciones para el objetivo 'help'
make: *** [help] Error 2'
you know what could be the reason?
Wow, these are great 🎉 Thanks for sharing!
I did something similar in ludicrous-makefiles (here and here), which handles multiple lines of help text for each target and also generate a prologue from any comments not directly associated with a target (I used #> as the marker instead of ##). No colors, but that's a nice touch so I'll probably add them!
@crifan I particularly like the readability and conciseness of your awk! For some reason it truncates targets by 1 character for me on ubuntu:18.04 (using mawk 1.3.3), but this change seems to help ...
19c19
< helpCommand = substr($$1, 0, index($$1, ":")-1); \
---
> helpCommand = $$1; sub(/:$$/, "", helpCommand); \no perl, grouping per @label_with_some_description
(sorting only on @Label, skipping sorting the targets)
default: | help-prefix help-targets
help-prefix:
@echo Whatever help info you want here, eg
@echo ' make <target>'
@echo ' make <target> <VAR>=<value>'
@echo ''
## @info help
test:
## @abc help
test2:
## @info help2
a:
# --- helper
HELP_TARGET_MAX_CHAR_NUM = 20
help-targets:
@awk '/^[a-zA-Z\-\_0-9]+:/ \
{ \
helpMessage = match(lastLine, /^## (.*)/); \
if (helpMessage) { \
helpCommand = substr($$1, 0, index($$1, ":")-1); \
helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \
helpGroup = match(helpMessage, /^@([^ ]*)/); \
if (helpGroup) { \
helpGroup = substr(helpMessage, RSTART + 1, index(helpMessage, " ")-2); \
helpMessage = substr(helpMessage, index(helpMessage, " ")+1); \
} \
printf "%s| %-$(HELP_TARGET_MAX_CHAR_NUM)s %s\n", \
helpGroup, helpCommand, helpMessage; \
} \
} \
{ lastLine = $$0 }' \
$(MAKEFILE_LIST) \
| sort -t'|' -sk1,1 \
| awk -F '|' ' \
{ \
cat = $$1; \
if (cat != lastCat || lastCat == "") { \
if ( cat == "0" ) { \
print "Targets:" \
} else { \
gsub("_", " ", cat); \
printf "Targets %s:\n", cat; \
} \
} \
print $$2 \
} \
{ lastCat = $$1 }'
I'm now using the following based on the post of @hans-d. It doesn't have the grouping feature, but does have multi-line doc strings.
Features
- only awk (works cross platform)
- multi-line doc strings
- doc strings aligned to right
- section delimiters
.DEFAULT_GOAL := help
## -- Section Delimiter --
## This help message
## Which can also be multiline
.PHONY: help
help:
@printf "Usage\n";
@awk '{ \
if ($$0 ~ /^.PHONY: [a-zA-Z\-\_0-9]+$$/) { \
helpCommand = substr($$0, index($$0, ":") + 2); \
if (helpMessage) { \
printf "\033[36m%-20s\033[0m %s\n", \
helpCommand, helpMessage; \
helpMessage = ""; \
} \
} else if ($$0 ~ /^[a-zA-Z\-\_0-9.]+:/) { \
helpCommand = substr($$0, 0, index($$0, ":")); \
if (helpMessage) { \
printf "\033[36m%-20s\033[0m %s\n", \
helpCommand, helpMessage; \
helpMessage = ""; \
} \
} else if ($$0 ~ /^##/) { \
if (helpMessage) { \
helpMessage = helpMessage"\n "substr($$0, 3); \
} else { \
helpMessage = substr($$0, 3); \
} \
} else { \
if (helpMessage) { \
print "\n "helpMessage"\n" \
} \
helpMessage = ""; \
} \
}' \
$(MAKEFILE_LIST)
## -- QA Task Runners --
## Run linter
.PHONY: lint
lint:
exit 0
## Run unit and integration tests
.PHONY: test
test:
exit 0
Output
$ make help
-- Section Delimiter --
help This help message
Which can also be multiline
-- QA Task Runners --
lint Run linter
test Run unit and integration tests
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
.DEFAULT_GOAL:=help
##@ Build
.PHONY: build
build: ## build something
@echo "Bulding..."
##@ Clean
.PHONY: clean
cache: ## clean something
@echo "Cleaning..."
define template =
##@ Section $(2)
.PHONY: $(1)-foo
$(1)-foo: ## Foo $(2)
@echo "Doing $(2)-foo..."
.PHONY: $(1)-bar
$(1)-bar: ## Bar $(2)
@echo "Doing $(2)-bar..."
endef
$(eval $(call template,a,Apple))
$(eval $(call template,l,Linux))
.PHONY: help
help:
@awk 'BEGIN {FS = ":.*##"; printf "Usage: make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-10s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)Current output
Usage: make <target>
Build
build build something
Clean
cache clean something
Section $(2)Needed output
Usage: make <target>
Build
build build something
Clean
cache clean something
Section Apple
a-foo Foo Apple
a-bar Bar Apple
Section Linux
l-foo Foo Linux
l-bar Bar LinuxIs there someone who can help?
Thank you! :)
@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/!dcommand. 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\}-/!dis used instead of the equivalent/##-/!dso 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. 😋 You're welcome:
# Show this help
help:
@cat $(MAKEFILE_LIST) | docker run --rm -i xanders/make-helpSource: 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-9in 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 .envyour 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/yetanotherfileWouldn'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 .envyour 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_].



reference above and others, updated to Colorful and usable syntax:
then above target use
## target description, example:will generate

colorfulhelp info:for latest makefile pls refer my: Makefile