-
-
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 |
Would somebody help on how to make the entire help that is in multiple lines show up aligned with first line of help associated with the target?
Example: For below make file content
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!!!
## This help message is way too long so it is in multiple line
target02: target00 target01
@echo does even more
Desired output:
help: This help dialog.
target00: This message will show up when typing 'make help'
target01: This message will also show up when typing 'make help'
target02: This message will show up too!!!
This help message is way too long so it is in multiple line
@nowox (20 - length $$_->[0])
always return 20 for me
reference above and others, updated to Colorful and usable syntax:
# COLORS
GREEN := $(shell tput -Txterm setaf 2)
YELLOW := $(shell tput -Txterm setaf 3)
WHITE := $(shell tput -Txterm setaf 7)
RESET := $(shell tput -Txterm sgr0)
TARGET_MAX_CHAR_NUM=20
## Show help
help:
@echo ''
@echo 'Usage:'
@echo ' ${YELLOW}make${RESET} ${GREEN}<target>${RESET}'
@echo ''
@echo '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); \
printf " ${YELLOW}%-$(TARGET_MAX_CHAR_NUM)s${RESET} ${GREEN}%s${RESET}\n", helpCommand, helpMessage; \
} \
} \
{ lastLine = $$0 }' $(MAKEFILE_LIST)
then above target use ## target description
, example:
## Generate Mobi file
mobi: clean_mobi create_foler_mobi
gitbook mobi $(CURRENT_DIR_NOSLASH) $(MOBI_FULLNAME)
## Generate all files: website/pdf/epub/mobi
all: website pdf epub mobi
will generate colorful
help info:
for latest makefile pls refer my: Makefile
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 Linux
Is 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/!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. 😋 You're welcome:
# 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 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!
I'm starting to use the following: