“Make” as a command interface to your repository

November 21, 2020
devops make

Back in 1976, when Make first entered the scene, it solved the problem of automating dependency aware software builds. By defining what sources a build target operates on, it could automatically determine what should be compiled based on what files had changed, thus saving on computing time while guaranteeing correct builds.

Command vs Automatic Targets

The ability to define build targets that could be invoked from the command line as make arguments, quickly lead to Make being used for other tasks than just building software, think of make install for example.

This “extracurricular” usage of the Make build tool proves useful to this day. A Makefile at the root of your repository provides a quick way to define commands for; bootstrapping the repository, running code-generation, executing tests, kicking off deployment and last but not least, if your project requires it, building executables. I have been thinking about these targets as “commands”, they are the main entry points into the functionality of your Makefile.

A well equipped makefile becomes a command line tool for your repository.

The beauty of Make is its dependency system. By defining prerequisites (what targets should run before your target, and/or did any of the files-your- target-depends-on change, thus requiring your target to run), you can ensure for example; that code is built before running tests, or that tests re-execute with updated coverage tracking configuration, before opening the test-coverage report in a browser. Let’s call targets in these dependency chains “Automatic” targets. They are like building blocks. You’d rarely execute them individually, but together they enable powerful functionality.

In recent projects, I’ve used Makefiles in this exact way. The Makefile offers a command line interface to the repository. It can be used to:

All that, performed with required prerequisite work, by just running make with a simple argument. It has been a joy to use. But as so often in life, you can’t have a silver lining without a cloud. This abundance of functionality in a Makefile brings with it two problems. 1) Silo-ing of potentially re-usable functionality and, 2), poor user experience, due to lack of target discoverability and an epic case of cognitive overload when trying to read through them.

Make help

Most Makefiles contain a boat load of targets, alphabetically-ish sorted if you’re lucky. There is no good way to distinguish command targets from automatic targets, let alone to get a quick idea of what you can do with a Makefile.

Despite that shortcoming, I still thought it was worth writing about this particular way of Makefile usage. In past employment and projects, I’ve seen countless examples of important commands having to be copied from documentation, and I don’t know how I would have done my job without relying heavily on bash history. Makefiles with a variety of command targets would have been very valuable to me in those days.

Then, while doing some research for this post, I came across how to create a self-documenting Makefile, by Victoria Drake. In that article, she not only outlines my repository command line tool use-case for Makefiles, it goes on to introduce the exact thing I had missed in my Makefiles, a quick way to get an overview of the commands contained within them 🤯 🎉.

.PHONY: help
help: ## Show this help
	@egrep -h '\s##\s' $(MAKEFILE_LIST) | \
	sort | \
	awk 'BEGIN {FS = ":.*?## "}; \
		{printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'

Some more Googling revealed that variants of this help target have been presented a few numerous times over the years, ultimately evolving to the version published by Victoria. Here’s how it works:

With the help target defined in the Makefile, and “command” targets annotated with ## help text comments, executing make help will now print out a quick overview of what you can do with the Makefile. Brilliant.

A nice side effect of ## help text comments on command targets is that it visually separates them from their “automatic” siblings when reading through the Makefile.

Makefile target re-use

When using Makefiles as a command interface for your repository, it will likely contain targets that can be shared with other repositories in your organization or other projects you work on. The help target presented above is a prime example. Most Makefiles are self-contained. Their targets are specific to the repository and project they exist in. But Makefiles can include other Makefiles, which offers a way to bring in common targets.

-include common.mk

Makefile includes can live in a different repository, a git submodule, a symlinked directory, etc. The possibilities are endless. The Makefile could even contain bootstrap logic to fetch the included files. If that is the case, ensure the include statements are prefixed with “-”. This will ignore import errors and allow targets in the Makefile to run without the include files being present, thus preventing a bootstrap / include catch-22 situation.

Over the past year, I have created Makefiles for various repositories based on the practices outlined above. Shared logic is brought in through git submodules and using make help, command targets are discoverable and documented. As an added bonus, maintaining and improving this toolkit is a great way to “get in the zone”. I’ll be using this wherever appropriate and I hope you will find it useful too. If so, drop me a line!

-@niels

btw, here are some resources for writing Makefiles I found helpful:

.PHONY: lint-make

lint-make: ## Run Checkmake on Makefile (requires docker)
	@docker run --rm -v "$$(pwd):/data" cytopia/checkmake Makefile

Automating npm install

May 18, 2021
I’ve previously written about using Make as a command interface for your repository and the other day, I ran into a situation where using Makefiles takes away a real-life annoyance for teams working on NodeJS and other Javascript or TypeScript based projects. When growing teams work in repositories that use NPM for Yarn for package management, dependency updates will enter the local workspace frequently when the main branch is pulled. Unless you’re religious about running npm install after every git pull, updating local dependencies of often omitted and sometimes leads to confusing red herring bugs or in the very least, lost time...

devops make npm

Rendering Mermaid graphs in Markdown on GitHub

January 20, 2021
When writing documentation for software, sooner or later you’re going to hit the point at which a picture will explain in a glance what you would have a hard time describing in the proverbial “thousand words”. While documenting something technical, this usually means diving into the drawing section of whatever editor you’re using and wrangling with lines and boxes until you’re satisfied with your masterpiece or you’ve exhausted your patience with the tool...

make mermaid markdown github