Skip to content

Instantly share code, notes, and snippets.

@prwhite
Last active Jun 10, 2022
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
@HarasimowiczKamil
Copy link

HarasimowiczKamil commented Mar 24, 2016

@caylorme you need to modify fragment [a-zA-Z\-] to like this [a-zA-Z\-\$\(\)]

@sjparkinson
Copy link

sjparkinson commented Mar 30, 2016

I'm starting to use the following:

.PHONY: help
help: ## Show this help message.
    echo 'usage: make [target] ...'
    echo
    echo 'targets:'
    egrep '^(.+)\:\ ##\ (.+)' ${MAKEFILE_LIST} | column -t -c 2 -s ':#'

@Nachi-M
Copy link

Nachi-M commented Jan 19, 2017

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

@benjick
Copy link

benjick commented Aug 15, 2017

@nowox (20 - length $$_->[0]) always return 20 for me

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

@eyarng
Copy link

eyarng 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

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