Posted on March 17, 2021 by ire

What Makefile?

Make was created quite a long time ago, ca. 1976, almost as old as Unix itself. Being initially intended to facilitate managing C programmes by abstracting away dependencies and consolidating build steps, amongst other things. In the almost 45 years to follow, it’s never really phased out. And for good reason. It’s a great piece of software; both simple and extremelly useful.

Whilst many improvements were made in software development tooling, one thing remains consistent, to wit: dealing with them is annoying, repetitive, and error prone. Make and Makefiles can improve your quality of life. If you’re not already using it, you might have something else in place right now, between shell scripts, shells with auto-complete and IDEs with workflows, that help mitigate the issue. And yes, for some cases these options might be satisfactory. So why then use make?

Why makefiles?

Facilitate usage and installation of software you distribute

This is probably the most common use-case.[citation needed] As this is outside the scope of this article, I’ll refer to this blog post, that goes into a lot more detail, with good use cases.

Improve your development loop by automating away tasks

This is my main use-case. By automating common tasks, like starting local servers, running tests, building with specific flags or settings, migrating databases, starting ghcid (fantastic tool for Haskell development), linting or formatting a project, Makefile is my goto solution.

But even if you’re not a command-line surfer like me, there’s still much to be gained from it. No only in terms of automation, but in terms of consistency. Being able to go to any given project and knowing exactly what can be done without really caring about how is mind-freeing. Is one of those small things that once you’ve become used to it, not having it for a given project just feels absurd.

Now, I deal mostly in Haskell, Purescript and iOS apps nowadays. With that in mind, here a couple of things I’ve collected along the way. This is by no means meant as a tutorial or a comprehensive guide.

Help

The first thing I setup in a new Makefile, is the help command. With this little snippet, we can turn in-line documentation into a neat help function that outlines all possible commands and what they do:

help: ## Show this help.
 @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

If regular expressions is not your cup of tea (for shame!), this expects that after your make command definition some-term:, if there’s a comment line using ##, it’ll render that term as a command and the comment as it’s description in a neat help output.

Pro tip: putting it as the very first command in the Makefile makes it the default, so that running make without any arguments will give out the help function.

$ make

clean-run                      Cleans, builds and runs the server
deploy-front                   Builds the front-end with parcel.js for production on ./output
help                           Show this help.
server                         Runs the server locally
watcher-front                  Runs `parcel --watch` for the frontend
tests                          Runs tests on backend
tests-front                    Runs tests on the frontend
watcher                        Runs ghcid.

Neat? I think so.

Basic commands

The basic commands can be quite simple and straight-forward, since we’re not using it as a build tool, but rather a collection of common tasks and shell scripts, we’ll be going easy on it. Beware, make expects tabs.

As an example, I have a project folder with two folders (server, frontend) and a Makefile. We can chain commands by separating them with semi-colons as such:

repl: ## Runs stack repl inside the server folder
 cd server; stack repl

Or, as another example, we move to the frontend folder, build the app using spago (a Purescript package manager and build system) and once that’s done, use Parcel.js to bundle it up for production.

deploy-front: ## Builds the front-end with parcel.js for production
 cd frontend; spago bundle-app --to ./app/index.js && parcel build ./app/index.html --out-dir ../output/

Another thing to keep in mind is that if a command in the Makefile is named the same as file or folder in its directory, it might become confused and unable to disambiguate. To avoid that, you can mark the commands as phoneys. I know. So our repl command instead becomes:

.PHONY: repl
repl: ## Runs stack repl inside the server folder
 cd server; stack repl

By using the aforementioned ## commenting style that we’re parsing in the help command, we get free documentation on top of it.

I hope you find this useful. It’s certainly simplified my workflow signficantly.