Skip to content

Instantly share code, notes, and snippets.

@prwhite
Last active September 26, 2023 12:55
Star You must be signed in to star a gist
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
@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

@PenelopeFudd
Copy link

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

@alhirzel
Copy link

alhirzel commented Sep 22, 2023

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

image

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