Skip to content

Instantly share code, notes, and snippets.

@ruittenb
Last active October 9, 2025 07:48
Show Gist options
  • Select an option

  • Save ruittenb/5d2d281237385276f49652b9b9f6d5a1 to your computer and use it in GitHub Desktop.

Select an option

Save ruittenb/5d2d281237385276f49652b9b9f6d5a1 to your computer and use it in GitHub Desktop.
Makefile target for automatically generating help
############################################################################
#
# makefile-autohelp
#
# This file is at : https://tinyurl.com/makefile-autohelp
# also known as : https://gist.github.com/ruittenb/5d2d281237385276f49652b9b9f6d5a1
############################################################################
#
# DESCRIPTION
#
# This code adds a 'help' target to your Makefile, that automatically
# creates a table of your Makefile's targets and short descriptions.
#
############################################################################
#
# INSTALLATION
#
# Copy this block over to your own Makefile:
.DEFAULT_GOAL:=help
.PHONY: help # See https://tinyurl.com/makefile-autohelp
help: ## Print help for each target
@awk -v tab=12 'BEGIN { FS = ":.*## "; buffer = ""; color = "\033[36m"; nocolor = "\033[0m"; indent = " "; hang = 2; header(); } function trim(str) { gsub(/[ \t]+$$/, "", str); gsub(/^[ \t]+/, "", str); return str; } function spout(target, desc) { split(trim(target), fields, " "); for (j in fields) printf "%s%s%-" tab "s%s%s\n", indent, color, trim(fields[j]), nocolor, desc; } function header() { printf "\nUsage:\n%smake %s<target>%s\n\nRecognized targets:\n", indent, color, nocolor; } /\\$$/ { gsub(/\\$$/, ""); buffer = buffer $$0; next; } buffer { $$0 = buffer $$0; buffer = ""; } /^[-a-zA-Z0-9*/%_. ]+:.*## / { pad = sprintf("\n%" (tab + hang) "s" indent, ""); gsub(/\\n/, pad); if ($$1 !~ /%/ || $$2 !~ /^%:/) { spout($$1, $$2); } else { n = split($$2, parts, /%:|:% */); for (i = 2; i < n; i += 2) { target = $$1; sub(/%/, parts[i], target); spout(target, parts[i + 1]); } } } /^##@ / { gsub(/\\n/, "\n"); printf "\n%s\n", substr($$0, 5); } END { print ""; }' $(MAKEFILE_LIST) # v1.57
############################################################################
#
# USAGE
#
# The awk option '-v tab=12' can be changed to change the indentation of the output.
#
# Descriptions for each target should be added in the Makefile itself:
#
# '##' after a target is used for target descriptions
# '##@' at the start of a line is used for header lines.
############################################################################
#
# EXAMPLES
#
##@ This is heading text, which is shown on a separate line.
##@ Heading text may \
span multiple lines.
##@ Heading text may contain\n- newlines\n- by specifying these with\n- backslash-n\n
# This is just any comment and its continuation. \
It will not be parsed by 'make help'.
##@ Examples of targets:
show1: ## Description for show1, no prerequisites
show2: prereq1 prereq2 ## Description for show2, with compact prerequisites
show3: prereq1 \
prereq2 ## Description for show3, with prerequisites spanning lines
show4: ## Description for show4 \
and its continuation, no prerequisites
show5: prereq1 prereq2 ## Description for show5 \
and its continuation, with compact prerequisites
show6: prereq1 \
prereq2 ## Description for show6 \
and its continuation, with prerequisites spanning lines
double: prereq ## This will not be shown ## Sequential comments only show the latter
newline: ## Comments may\ncontain newlines,\nentered as "backslash-n"
target1 target2: ## Multiple targets will be split across lines
goal1 \
goal2: ## Multiple targets spanning lines
target-%: ## %:one:% Wildcard targets may be... %:two:% ...specified with individual descriptions
copy-%: ## %:here:% Copy things hither %:there:% Copy things thither
##@ Nothing should be printed from the lines below:
noshow1: # This is just any comment, which will not be parsed by 'make help'
noshow2: prereq1 prereq2 # This is just any comment, which will not be parsed by 'make help'
noshow3: prereq1 \
prereq2 # This is just any comment, which will not be parsed by 'make help'
noshow4: # This is just any comment and its continuation. \
It will not be parsed by 'make help'.
noshow5: prereq1 prereq2 # This is just any comment and its continuation. \
It will not be parsed by 'make help'.
noshow6: prereq1 \
prereq2 # This is just any comment and its continuation. \
It will not be parsed by 'make help'.
############################################################################
#
# Readable version of code, with '$' replaced with makefile-style '$$', but no trailing backslashes:
#
# @awk -v tab=12 '
# BEGIN {
# FS = ":.*## ";
# buffer = "";
# color = "\033[36m";
# nocolor = "\033[0m";
# indent = " ";
# hang = 2;
# header();
# }
# function trim(str) {
# gsub(/[ \t]+$$/, "", str);
# gsub(/^[ \t]+/, "", str);
# return str;
# }
# function spout(target, desc) {
# split(trim(target), fields, " ");
# for (j in fields) printf "%s%s%-" tab "s%s%s\n", indent, color, trim(fields[j]), nocolor, desc;
# }
# function header() {
# printf "\nUsage:\n%smake %s<target>%s\n\nRecognized targets:\n", indent, color, nocolor;
# }
# /\\$$/ {
# gsub(/\\$$/, "");
# buffer = buffer $$0;
# next;
# }
# buffer {
# $$0 = buffer $$0;
# buffer = "";
# }
# /^[-a-zA-Z0-9*/%_. ]+:.*## / {
# pad = sprintf("\n%" (tab + hang) "s" indent, "");
# gsub(/\\n/, pad);
# if ($$1 !~ /%/ || $$2 !~ /^%:/) {
# spout($$1, $$2);
# } else {
# n = split($$2, parts, /%:|:% */);
# for (i = 2; i < n; i += 2) {
# target = $$1;
# sub(/%/, parts[i], target);
# spout(target, parts[i + 1]);
# }
# }
# }
# /^##@ / {
# gsub(/\\n/, "\n");
# printf "\n%s\n", substr($$0, 5);
# }
# END {
# print "";
# }
# ' $(MAKEFILE_LIST) # v1.57
#
#!/usr/bin/env bash
#
# This script may be used to minimize the 'help' recipe in the Makefile.
# Just feed the recipe line, target or the entire Makefile through this script.
perl -pe '
if (/^\t\@awk/) {
# remove whitespace around operators, statements and blocks
s/ ([+=<>~]|\+=|-=|!~|&&|\|\|) /$1/g;
s/([;{}(),]) (?!#)/$1/g;
s/ ([{}()])/$1/g;
s/;}/}/g;
# shorten variable names
s/\bhang\b/a/g;
s/\bbuffer\b/b/g;
s/\bcolor\b/c/g;
s/\bdesc\b/d/g;
s/\bfields\b/f/g;
s/\btarget\b(?!>)/g/g;
s/\bheader\b/h/g;
s/\bnocolor\b/m/g;
s/\bpad\b/p/g;
s/\bparts\b/q/g;
s/\bstr\b/s/g;
s/\btrim\b/t/g;
s/\bspout\b/u/g;
s/\bindent\b/y/g;
# leftover corrections
s/(printf?) /$1/g;
s/ (tab) /$1/g;
s/" y/"y/g;
s/b \$/b\$/g;
s/(BEGIN{[^{}]+;)b="";/$1/g;
}
'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment