Skip to content

Instantly share code, notes, and snippets.

@prwhite
Last active April 16, 2023 19:38
Embed
What would you like to do?
Add a help target to a Makefile that will allow all targets to be self documenting
# 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
@jcwren
Copy link

jcwren commented Sep 16, 2020

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

@Xanders
Copy link

Xanders commented Dec 17, 2020

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:

Screenshot with this project help output

@theherk
Copy link

theherk commented Feb 8, 2021

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.

@jcwren
Copy link

jcwren commented Feb 9, 2021

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

@nothub
Copy link

nothub commented Jun 18, 2021

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.

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

@alexandregv
Copy link

alexandregv commented May 8, 2022

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):
image

@mathieu-aubin
Copy link

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

Resulting_help_output

@maxixcom
Copy link

maxixcom commented Aug 6, 2022

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

Resulting_help_output

If your makefile has something like:

-include .env

your proposal will display:

Makefile                       Display this help screen

@mathieu-aubin
Copy link

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
Resulting_help_output

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!

@prwhite
Copy link
Author

prwhite commented Aug 9, 2022

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
Resulting_help_output

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.

@nothub
Copy link

nothub commented Aug 14, 2022

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_].

@p-sherratt
Copy link

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 :

@arvenil
Copy link

arvenil commented Dec 1, 2022

What's the most cross-platform solution, without colors and which displays targets even if they don't have comment?

@mathieu-aubin
Copy link

mathieu-aubin commented Dec 7, 2022

@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.

@lpsantil
Copy link

lpsantil commented Dec 7, 2022

@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.

@lpsantil
Copy link

lpsantil commented Dec 7, 2022

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.

@lpsantil
Copy link

lpsantil commented Dec 8, 2022

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment