In short. Think of a Makefile as a tool for compiling C programs and you miss most of the places it belongs. The Makefile in my home folder has no compilation in it at all. It has things like turning UAC off, adding a firewall rule, and registering a drive-lock menu entry. What make actually does is attach a bundle of commands to one name. Building is only one use for that.
Commands unrelated to building are fine
These are the kind of targets in my Makefile.
uac-off: ## Turn UAC off (requires a reboot)
@echo "would disable UAC"
ping-open: ## Add a firewall rule allowing ICMP
@echo "would add firewall rule"
These are commands I use maybe once a month. I cannot keep them in my head. Search for them again and the options differ from what I used last time. Bundle a command you found once under a name and next time it is just make uac-off.
You could write a shell script instead, and there is a reason to use make. A Makefile only applies in its own folder. An alias applies to the whole machine, so you have to watch out for name collisions. A script means remembering where you put it. You can keep one Makefile per project and standardise on make build everywhere. Step into a folder, type make, and you see what can be done in that project.
As the earlier post on aliases noted, commands inside a Makefile run in a shell called by a program. It is not a shell a person types in. So aliases cannot be used here; you write the command out in full or call a script file.
The target list becomes the help text
This is where the value comes from. Add ## description at the end of a target line and you can scrape that list into help output. One block at the top of the Makefile.
.DEFAULT_GOAL := help
.PHONY: help
help: ## List the available targets
@grep -hE '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
| awk 'BEGIN{FS=":.*?## "}{printf " \033[36m%-14s\033[0m %s\n", $$1, $$2}'
Thanks to .DEFAULT_GOAL := help, typing make with no arguments prints the help.
$ make
help List the available targets
uac-off Turn UAC off (requires a reboot)
ping-open Add a firewall rule allowing ICMP
grep picks out lines of the form name: … ## description and awk splits them on ## and aligns them. So adding a target grows the help by itself. There is no separate document to copy descriptions into, and no document to go stale. A target without ## does not appear in the list, so you can hide internal ones.
Three things that catch you on Windows
First, the indentation of a command line must be a tab. Use spaces and it stops like this.
Mbad:9: *** missing separator (did you mean TAB instead of 8 spaces?). Stop.
Make does tell you clearly. If your editor converts tabs to spaces you will keep hitting it. Make an exception for that folder. The same thing happens when you copy a Makefile from the web.
Second, non-ASCII text in an echo on a command line comes out mangled. The encoding is mismatched on the path make takes to the shell. My own Makefile is in Korean, and this is what a Korean echo produced.
$ make ko
?쒓? 異쒕젰 ?쒕룄
The same Korean in a ## description is fine. In the real help output, "Turn UAC off" is written in Korean in my file and printed correctly. That is because grep and awk read and emit the file bytes directly. In short, non-ASCII is fine in the description comments and should stay out of echo bodies.
Third, write $ as $$. That is what $$1, $$2 and .*$$ are doing in the help block above. With a single $, make swallows it as its own variable and awk receives an empty value.
Summary
| Item | Rule |
|---|---|
| Indentation | Tabs only. Spaces give missing separator |
| Non-ASCII | Fine in ## description, keep echo bodies ASCII |
$ | Doubled, as $$ |
| Default target | .DEFAULT_GOAL := help so plain make prints the list |
A Makefile is a list of what can be done in that folder. Put in the commands you use once a month. The ones that took a while to find, and the ones you have to explain to someone else.
Add the help block above to the project you are working on now. Turn one command you recently looked up into a target. The value shows immediately.
