Guarding Makefile targets
Daniel Brady
Posted on July 23, 2020
This may just be my favorite one liner of my own devising in recent years.
I love it because of a combination of its utility (🔧), the story behind how it works (💭), and its power to induce headaches to its readers (😅).
It belongs in a Makefile
:
## Returns true if the stem is a non-empty environment variable, or else raises an error.
guard-%:
@#$(or ${$*}, $(error $* is not set))
And has a simple function:
➜ make guard-FOO
Makefile:12: *** FOO is not set. Stop.
➜ FOO=42 make guard-FOO
➜
Deciphering from top to bottom, left to right:
- The
%
symbol used in the target declaration a “static pattern wildcard”, and in more familiar regex terms it translates toguard-(.*)
; the “stem” of the pattern, i.e. the value of the match group, is stored in an automatic variable called$*
. - The
@
prefix preventsmake
from printing out the command it is about to evaluate. (Hint: Take this away to actually see what’s happening.) - The
#
is your typical shell comment character: anything that comes after it will be interpreted by the shell (andmake
) as a comment, and thus ignored in a "truthy" way. - The
$(or …, …, ...)
is amake
short-circuiting conditional function: it expands each item until it finds one that expands to a non-empty string, at which point it stops processing and returns the expansion. (Note: It does not evaluate it.) - The
${…}
notation is themake
syntax for evaluating a variable. - The
$*
as mentioned previously, holds the value of the matched pattern in the target name. - The
$(error ...)
is amake
control-flow form, and raises an error as soon as it is evaluated, after allowing for its arguments to be expanded.
So, putting it all together: FOO=42 make guard-FOO
expands to:
#$(or ${FOO}, $(error FOO is not set))
which in turn expands to:
#$(or 42, $(error FOO is not set))
which, finally, expands to:
#42
which is evaluated by the shell as a comment and ignored, but in a truthy way.
However, make guard-FOO
expands to:
#$(or ${FOO}, $(error FOO is not set))
and then:
#$(or , $(error FOO is not set))
and then:
#$(error FOO is not set)
and then a fatal error.
The value of this is as a prerequisite for other targets, to easily guard against missing environment variables, e.g.
say-something: guard-SOMETHING
echo ${SOMETHING}
➜ make say-something
Makefile:12: *** SOMETHING is not set. Stop.
➜ SOMETHING='Hello, world!' make say-something
echo Hello, world!
Hello, world!
Posted on July 23, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.