ed and a self documenting Makefile

Makefile is a powerful way to declare a chain of interdependent commands that create targets. In this article, we will explain how to create an auto-documenting Makefile that is able to print a help message when invoked without target.

The idea of a self-documenting Makefile comes from this almost perfect stackoverflow answer, if it wasn’t for some peculiarities of the unfortunately different version of macOS’s sed, compared to GNU.

ed as a compatible sed replacement

ed, one of the first three key elements of Unix according to Wikipedia, is the ancestor of sed.

Its legacy is an advantage for us because its interface hasn’t changed since ages, and thus remains a very interesting way of writing multi-platform scripts.

We will use ex, as its proximity with vi helps iterating quickly on the different options.

TL;DR, the Makefile

Here is an example Makefile that prints some help when invoked without target. We signify documentation with regular comments by doubling the hashtags trailing after some targets. For good measure, we also use the strict makefile headers. Continue reading for more details.

SHELL := bash
.ONESHELL:
.SHELLFLAGS := -eu -o pipefail -c
.DELETE_ON_ERROR:
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules

.PHONY:help
help:  ## print these help instructions
	@ex -s  '+%g!/^[\\.A-Za-z_-]\+:[^#]\+##.*$$/d _' '+%s/^\([\\.A-Za-z_-]\+\):.*## \+\(.*\)$$/\"\1\" \"\2\"/' -c'%p|q!'  <(cat ${MAKEFILE_LIST}) | xargs printf "make %-20s# %s\n"

a:
	echo $@ > $@

b: # some internal help, not to be printed
	echo $@ > $@

c.txt: a b  ## this target is what you want to generate probably
	cat $? > $@
	echo "Generation complete"

Crafting the ed command

Conflict with existing vimrc files

Nowadays, most ed binaries are just vi binaries starting in ex mode. We need to instruct ed/vi to avoid loading any existing vimrc files, and suppress all diagnostics.

-s

Remove uninteresting lines

We want to keep lines that define a target, and also contains a double hashtag comment. We do so by executing an inverse match delete into the black hole “_” register. (note: searching for help about ed is quite consuming because most search engine only start indexing words with more letters than 2 apparently).

'+%g!/^[\\.A-Za-z_-]\+:[^#]\+##.*$$/d _'

Filter interesting lines

Now that we only have the interesting lines, we want to keep only the target name and the comment. We do so with a substitute command, exactly like sed would do.

'+%s/^\([\\.A-Za-z_-]\+\):.*## \+\(.*\)$$/\"\1\" \"\2\"/'

Print the result and exit, read from stdin

ed is an interactive program. To make it behave like sed, we add this segment.

-c'%p|q!'  <(cat ${MAKEFILE_LIST})

1 thought on “ed and a self documenting Makefile”

Leave a Comment