make help - Well documented Makefiles
Background
The primary intention of this post is to document some tricks that can be used in a Makefile, so that documentation can be added for each target in the Makefile itself, and is available to view as a make target (eg. make help
)
Having solid documentation in your repositories is a great thing, and it's even better when they don't go stale. It's common to document each Make target in a top-level Readme.md, or something similar. While this is a great first step, it's quite common that the Makefiles get updated, but not the documentation, thus making them rather useless.
To tackle the problem mentioned, I've found myself adding this little snippet to most of the Makefiles I work with. Sharing it here, so that I can look it up later.
Goal
The end goal is to be able to run something like follows, all based on comments within the Makefile.
> make
Usage:
make <target>
Targets:
help Display this help
deps Check dependencies
clean Cleanup the project folders
build Build the project
watch Watch file changes and build
For complex Makefiles with a lot of targets, we can also group them together.
> make
Usage:
make <target>
Dependencies
deps Check dependencies
Cleanup
clean Cleanup the project folders
Building
build Build the project
watch Watch file changes and build
Helpers
help Display this help
Let's continue to see how it's implemented.
Requirements
- Make
- and, Awk
are the only requirements. This should work both on macOS(BSD) as well as Linux(GNU) versions.
Implementation
As shown in the example above, we can implement them for two different cases.
Simple Makefile
For Makefiles with reasonably few targets, we can list all of them without any grouping.
First, add a help target.
help: ## Display this help
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n\nTargets:\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-10s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST)
Then, add comments with a syntax specified in the above help
target. In this example, we will use ##
as the tag for printable comments.
deps: ## Check dependencies
$(info Checking and getting dependencies)
clean: ## Cleanup the project folders
$(info Cleaning up things)
build: clean deps ## Build the project
$(info Building the project)
watch: clean deps ## Watch file changes and build
$(info Watching and building the project)
Optionally, add the default goal to be help
, preferably at the top of Makefile
.DEFAULT_GOAL:=help
Optionally, adjust the width between the target and comments printed out via make help
.
Within the help
target mentioned above, replace the number 10
to the charcter width you prefer.
Full example
.DEFAULT_GOAL:=help
SHELL:=/bin/bash
.PHONY: help deps clean build watch
help: ## Display this help
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n\nTargets:\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-10s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST)
deps: ## Check dependencies
$(info Checking and getting dependencies)
clean: ## Cleanup the project folders
$(info Cleaning up things)
build: clean deps ## Build the project
$(info Building the project)
watch: clean deps ## Watch file changes and build
$(info Watching and building the project)
Grouped Makefile
Adding grouping is very similar to the one above. We add one more comment format to differentiate grouping comments from target comments, and tweak the help
target a bit to accomodate the change.
Add a help
target (Make sure you copy/convert the tab character properly)
help: ## Display this help
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\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)
Add comments with a syntax specified in the above help
target. In this example, we will use ##
as the tag for printable comments, and ##@
for grouping comments
##@ Dependencies
deps: ## Check dependencies
$(info Checking and getting dependencies)
##@ Cleanup
clean: ## Cleanup the project folders
$(info Cleaning up things)
##@ Building
build: clean deps ## Build the project
$(info Building the project)
watch: clean deps ## Watch file changes and build
$(info Watching and building the project)
Optionally, provide a default goal and adjust the character width between target and comments printed out, as explained in the Simple Makefile section above
Full example
.DEFAULT_GOAL:=help
SHELL:=/bin/bash
##@ Dependencies
.PHONY: deps
deps: ## Check dependencies
$(info Checking and getting dependencies)
##@ Cleanup
.PHONY: clean
clean: ## Cleanup the project folders
$(info Cleaning up things)
##@ Building
.PHONY: build watch
build: clean deps ## Build the project
$(info Building the project)
watch: clean deps ## Watch file changes and build
$(info Watching and building the project)
##@ Helpers
.PHONY: help
help: ## Display this help
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\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)
Wrap up
Using these two tricks, one can go pretty far with documented Makefiles. I hope that this has been useful to you. Thanks to the authors below who took the time to share their knowledge, without whom this writing would not have been possible.