Skip to content

Instantly share code, notes, and snippets.

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
@crifan
Copy link

crifan commented Dec 7, 2017

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:
colorfule make help

for latest makefile pls refer my: Makefile

@TediCreations
Copy link

TediCreations commented Dec 11, 2017

Thanks for these tips on make help

alt text

Here is my Makefile for C/C++ development.

@lavaldi
Copy link

lavaldi commented Dec 12, 2017

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?

@martinwalsh
Copy link

martinwalsh commented May 24, 2018

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); \

@hans-d
Copy link

hans-d commented Sep 8, 2018

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 }'
	

@mbarkhau
Copy link

mbarkhau commented Nov 2, 2018

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

@o5
Copy link

o5 commented Feb 10, 2019

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

@amerlyq
Copy link

amerlyq commented Feb 11, 2019

@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

@adepretis
Copy link

adepretis commented Feb 23, 2019

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!

@mirageglobe
Copy link

mirageglobe commented Feb 27, 2019

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.

@m000
Copy link

m000 commented Nov 29, 2019

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.

@lpsantil
Copy link

lpsantil commented Dec 18, 2019

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.

@rcosnita
Copy link

rcosnita commented Mar 12, 2020

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.

@WarFox
Copy link

WarFox commented May 5, 2020

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

@muuvmuuv
Copy link

muuvmuuv commented Aug 26, 2020

Can someone here help me get a awk working with this Makefile syntax?

##
## Section
##

## Start container
start:
  @echo "Start container"

@mbarkhau
Copy link

mbarkhau commented Aug 26, 2020

@muuvmuuv have a look if you can get something going here: https://awk.js.org/

@lpsantil
Copy link

lpsantil commented Aug 27, 2020

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

@eyanq
Copy link

eyanq 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

@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

mathieu-aubin commented May 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

@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

mathieu-aubin commented Aug 7, 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

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

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