Advent 2023: Makefile: guard targets
A couple days ago, I wrote about Makefile. Today, I'm going to show a quick tip for writing "guard" targets.
Guard targets?
Maybe you have a target that requires root to run.
Or maybe you have one that requires that a .env
file is present.
You could check for these conditions within your target:
deploy:
@$(call MK_INFO,"Checking for root")
@if [[ "$(shell id -u)" != 0 ]]; then $(call MK_ERROR,"This target requires root"); exit 1; fi
@$(call MK_INFO,"Checking for .env")
@if [[ ! -f .env ]]; then $(call MK_ERROR,".env file is missing"); exit 1; fi
What if you need to do these checks in multiple targets? You can't really make functions of these, so how can you prevent duplication?
The thing is, make
already supports these sorts of things, because any target can already specify its prerequisites, which are just other targets!
When I define a prerequisite target that only exists to ensure certain conditions are met as a prerequisite to other targets, I call it a guard target.
Let's refactor:
root:
@$(call MK_INFO,"Checking for root")
@if [[ "$(shell id -u)" != 0 ]]; then $(call MK_ERROR,"This target requires root"); exit 1; fi
deploy: root .env ## Deploy the app
# do the actual work...
Let's dissect this:
- The
root
target checks to see if the current user is root. If not, it spits out an error message (see my previous article on Makefile to understand that MK_ERROR usage works), and then exits with an error status. - The
deploy
target marksroot
and.env
as prerequisites; if either fails, it won't run. - Note that the
root
target does not have a##
comment; this means it won't show up in my usage messages (though I can still call it separately if I want).
Wait, what about .env
?
The default assumption of make
is that targets are files.
If the file exists, the target will not be executed, as the file already exists, so no work needs to be done.
By specifying .env
as a target, we're saying that deploy
can only run if .env
exists!
But if targets are files...
So, if targets are files, how does make
work at all for things like deployment?
Again, the assumption is if the file does not exist, then make
has work to do, and if that file is listed as a target, then it needs to execute that target.
Generally, the targets you create for things like web application deployment will not have corresponding files or directories, so make
will happily see that the target exists in your Makefile
, no filesystem entry exists, and execute it.
The fact that it doesn't actually generate the target file is of no matter.
What if the target does exist?
What if the target name does have a corresponding file in the filesystem?
Mark it as a "PHONY" target:
.PHONY: .env
If you do this, then even if the .env
file exists, the .env
target, if it exists, will still be run.
Final Thoughts
Guard targets are a great way to add preconditions to build targets.
They are re-usable and succinct, and can be used to provide useful error messages to guide usage.
Better, they leverage the native aspects of a Makefile
and make
, and with good naming conventions, it becomes easy to identify what the prerequisite is for a given target without even needing to see how it's defined.