Beautiful Makefiles with Wildcards
18 May 2020
Every single project we build includes a Makefile as a task runner. Every. Single. One.
Why? Because it allows someone to jump into a codebase and start working with the same set of tools and commands as everyone else.
Want to know how to install, build, test, deploy, and see what else you can do?
Just open Makefile
.
This may not be so important if you spend all your time on one monolith, but if you're jumping around to many services or clients, it's a lifesaver.
It's also so, so nice for open source projects, I'm surprised GitHub doesn't suggest it.
In this post and future ones, we'll go over some of the tricks we've learned building Makefiles in several dozen separate projects.
Today's lesson: Wildcards (%)
Dynamic Arguments with Wildcards (%)
Many popular languages include some (Make-inspired) script runner, so most commands look something like this:
migrate:
bundle exec rake migrate
install:
yarn install
runserver:
python ./manage.py runserver
That's nice, but what about when you want to supply arguments to one of those CLI commands.
For example, how can you run bundle exec rake migrate[50]
to migrate to version 50, rather than latest?
Wildcards, that's how! For migrations, we can add a new command to migrate to a specific version:
migrate-to-%:
bundle exec rake migrate[$(*)]
Now if you want to migrate to a specific version, you can run:
$ make migrate-to-50
Migrating to version 50...
Well, we think that's pretty cool, but what else can wildcards do?
Declarative Argument Dependencies
The other way to configure Makefiles is with environment variables.
Let's say we use a consistent STACK
environment variable for
working with deployed Cloudformation stacks (as we actually do).
It can be pretty annoying to debug the errors if you forget to set STACK:
logs:
awslogs get -w /ecs/$(STACK)_MyService
That can be invoked (correctly) as awslogs get -w /ecs/development_MyService
or (incorrectly) as awslogs get -w /ecs/_MyService
.
If it's missing, your error will be about some unknown service
or wrong file path, rather than about a missing parameter.
Well, we have a special command we've been using for years that we'll share with you now:
guard-%:
if [ -z '${${*}}' ]; then echo 'ERROR: variable $* not set' && exit 1; fi
It's maybe our favorite two lines of code in the world.
Now, you can rewrite the logs
target like this, which is also more declarative and clearer about its dependencies:
guard-%:
if [ -z '${${*}}' ]; then echo 'ERROR: variable $* not set' && exit 1; fi
logs: guard-STACK
awslogs get -w /ecs/$(STACK)_MyService
Finally, if you invoke logs
without STACK
set, you get a nice error:
$ make logs
ERROR: variable STACK not set
make: *** [guard-STACK] Error 1
Wait, there's more!
There are a couple other wildcard-based tricks we still have in store, as well as some more Makefile goodness.
Find this post useful, or interested in learning how we can help you make your next project? Please get in touch!.