Don't 'Make' me repeat myself
tldr: click here for the Rust Makefile
When working in a terminal youāre often typing the same commands, over and over. This is fine if theyāre short, but when passing arguments things can get clunky fast. At best you hit Ctrl-r (reverse-i-search), attempting to remember part of the command and crossing your fingers itās still in your history. At worst you have to search the internet each time your shellās history expired.
To combat this I used to write separate shell scripts for each command I didnāt want to lose. Then a colleague introduced me to (GNU) Make and now I often think: āHow did I get anything done without a Makefile?!ā Mind you I was very late to this party because according to Wikipedia, Make first appeared inā¦ April 1976!
What is Make?
In short: Make is used to run shell commands which you define by name, in a Makefile.
What does a Makefile look like?
Below is a Makefile. Thereās a little syntax going on but Iāll explain that shortly. :
Letās run it:
Now let me explain the Make specific syntax thatās present.
The
@
suppresses the command itself in the output. If we left it out, the output would be:When you want to use the
$(...)
form for command substitution, you instead write$(shell ...)
(for more info: the shell function)
Wrapping up
Abstracting away commands is handy, especially if you switch between programming languages or build-tools a lot. When these repetitive tasks arenāt broken, you usually donāt care which tools are being used in the background. So, to make my life easier I use the same names for my Make targets regardless of which programming language the project is written in. This way I can just run make build
in any language, because I set it up once.
Thereās a lot of nice features in Make I havenāt discussed because I rarely use them. The one I do want to mention now is āchaining targetsā. With very little syntax you can tell Make that your target depends on another targetās success. Imagine something like: first build, then if that succeeds test, then if that succeeds make a release etc. Hereās an arbitrary example:
build:
@cargo build
log-build: build
@echo Build success with commit hash \
$(shell git log -1 --format="%H") \
on $(shell date). >> ./build.log
The only line of interest here is log-build: build
. The syntax is short and clear. We define that when we run make log-build
we first run the ābuildā target, and only if that succeeds do we run ālog-buildā.
Rust Makefile
Without further ado hereās the Makefile I start with when creating a new Rust project. Itās little more than a thin wrapper around cargo, but the āformatā and ādocsā commands contain just enough syntax to make me forget or mistype them.