Skip to content

Instantly share code, notes, and snippets.

@zanbaldwin
Last active June 8, 2023 10:24
Show Gist options
  • Save zanbaldwin/969bfce88213f2f5864cbaa909cda51e to your computer and use it in GitHub Desktop.
Save zanbaldwin/969bfce88213f2f5864cbaa909cda51e to your computer and use it in GitHub Desktop.
Automatic Makefile Usage Generator

Opinionated Defaults for Makefile Settings

Thanks to Jacob David-Hansson's blog post.

  • /bin/sh could be symlinked to any shell on foreign systems, so explicitly specify Bash instead (SHELL :=).
  • Enable strict mode in Bash (.SHELLFLAGS := -eu -o pipefail -c).
  • Specify that each rule should run in a single shell environment, rather than a shell per line (.ONESHELL:).
  • Delete (do not save) target/output files if the recipe/script results in an error (.DELETE_ON_ERROR:).
  • Show a warning if undefined Makefile variables are referenced (MAKEFLAGS += --warn-undefined-variables).
  • Disable the built-in rules for Make, as we don't want magic only what we explicitly specify (MAKEFLAGS += --no-builtin-rules).
  • Use > instead of tabs to avoid IDE's from messing up whitespace (.RECIPEPREFIX) and show an error if this is not supported.

Automatic Makefile Usage Generator

Add the following as the first rule of your Makefile:

usage:
> @grep -E '(^[a-zA-Z_-]+:\s*?##.*$$)|(^##)' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.?## "}; {printf "\033[32m %-30s\033[0m%s\n", $$1, $$2}' | sed -e 's/\[32m ## /[33m/'

Then add a line beginning with ##<space> for a section heading, and a line <command_name>: ##<space><command_comment> to describe command usage.

Example Output

Application Setup
 install                       Install project dependencies
Testing
 unit                          Run unit tests

Automatic Script Usage/Help Output

For bash scripts, not Makefiles

Thanks to Egor Kovetskiy's blog post.

Add the following to the top of your script:

usage() {
    sed -rn 's/^### ?//;T;p' "$0"
    exit 0
}

if [ $# -gt 0 ]; then
    for ARG in "$@"; do
        if [ "$ARG" = "--help" ] || [ "$ARG" = "-h" ]; then
            usage
        fi
    done
fi

Then add a file comment where each line is prepended with three hash symbols (###). The section will be ignored as a script comment, but will be parsed and printed by the usage() function.

SHELL := bash
.SHELLFLAGS := -eu -o pipefail -c
.ONESHELL:
.DELETE_ON_ERROR:
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules
ifeq ($(origin .RECIPEPREFIX), undefined)
$(error This Make does not support .RECIPEPREFIX; Please use GNU Make 4.0 or later)
endif
.RECIPEPREFIX = >
THIS_MAKEFILE_PATH:=$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
THIS_DIR:=$(shell cd $(dir $(THIS_MAKEFILE_PATH));pwd)
THIS_MAKEFILE:=$(notdir $(THIS_MAKEFILE_PATH))
usage:
> @grep -E '(^[a-zA-Z_-]+:\s*?##.*$$)|(^##)' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.?## "}; {printf "\033[32m %-30s\033[0m%s\n", $$1, $$2}' | sed -e 's/\[32m ## /[33m/'
.PHONY: usage
.SILENT: usage
## Section Title
command: ## Description of command.
command: vendor
> bin/command src
.PHONY: command
.SILENT: command
#!/bin/bash
###
### my-script — does one thing well
###
### Usage:
### my-script <input> <output>
###
### Options:
### <input> Input file to read.
### <output> Output file to write. Use '-' for stdout.
### --help Show this message.
usage() {
# Print any line in this file that begins with three hash symbols (###).
sed -rn 's/^### ?//;T;p' "$0"
exit 0
}
if [ $# -gt 0 ]; then
for ARG in "$@"; do
if [ "$ARG" = "--help" ] || [ "$ARG" = "-h" ]; then
usage
fi
done
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment