loading...

Guarding Makefile targets

daniel13rady profile image Daniel Brady ・2 min read

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 to guard-(.*) ; the “stem” of the pattern, i.e. the value of the match group, is stored in an automatic variable called $*.
  • The @ prefix prevents make 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 (and make) as a comment, and thus ignored in a "truthy" way.
  • The $(or …, …, ...) is a make 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 the make syntax for evaluating a variable.
  • The $* as mentioned previously, holds the value of the matched pattern in the target name.
  • The $(error ...) is a make 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 by:

daniel13rady profile

Daniel Brady

@daniel13rady

Success is something you tell your friends about, but failure is what you learn from.

Discussion

pic
Editor guide