Portable conditionals in makefiles
- July 2, 2013
If you are writing software that works on many variants of Unix, you are confronted to the problem of Makefiles: some systems use GNU make and others use BSD make and these two are compatible, but only on a very restricted subset. The most problematic restriction is that they don’t have a common syntax for writing conditionals.
So how do you write conditionals that work on both variants? GNU make and BSD make share so few of their “new” features (features that were not in UNIX make) that it looks like an impossible task.
Let’s say you have a variable FOO
that contains either
true
or false
, and you want your target
A
to depend on B C D
in the first case, and on
E F G
in the second. I know two ways of doing that.
First solution: substitution
The first solution is simple but not fully general. It uses substitution in variable references:
DEPS1 = ${FOO:true=B C D}
DEPS2 = ${DEPS1:false=E F G}
A : ${DEPS2}
echo ${DEPS2}
This is quite ugly, it doesn’t work if false
appears in
the value of the true
case (here, B C D
).
Also, I don’t think there is any way to extend it to handle a default
case. But it’s simple and relatively easy to understand.
Second solution: indirection
The second solution works by applying David Wheeler’s aphorism:
All problems in computer science can be solved by another level of indirection.
We’ll use indirect variable references. This is a feature that, surprisingly, works the same in both flavours of make:
FOO = BAR
BAR = toto
all:
echo ${${FOO}}
This will echo toto
.
Now, we have FOO
that is either true
or
false
, so we’ll do this:
true = B C D
false = E F G
DEPS := ${${FOO}}
A : ${DEPS}
echo ${DEPS}
Note how the definition of DEPS
uses :=
rather than =
. This is because we want to be able to reuse
the variables true
and false
for another
conditional. If we don’t use :=
, the assignment is lazy and
DEPS
gets a value based on the last values assigned to
true
and false
(which may be further down in
the makefile).
Advanced solution: more indirection
The above obviously generalizes to more than two cases, but what
about having a default case? Suppose that we want to do as above, but
make A
depend on H I J
when FOO
is set to anything else that true
or false
(or
unset, which is the same as set to ""
). How do we do
that?
It’s easy, just apply Wheeler’s aphorism one more time:
unlikely_true = aaaa
unlikely_false = bbbb
xxx_aaaa = B C D
xxx_bbbb = E F G
xxx_ = H I J
DEPS := ${xxx_${unlikely_${FOO}}}
A : ${DEPS}
echo ${DEPS}
This is getting ugly but it works. As long as you don’t have any
clash with some random variable whose name starts with
unlikely_
. And if you have several conditionals in your
makefile, you probably should reset all these variables after using
them:
unlikely_true = aaaa
unlikely_false = bbbb
xxx_aaaa = B C D
xxx_bbbb = E F G
xxx_ = H I J
DEPS := ${xxx_${unlikely_${FOO}}}
unlikely_true =
unlikely_false =
xxx_aaaa =
xxx_bbbb =
xxx_ =
A : ${DEPS}
echo ${DEPS}
This makefile has a nice property: you can use environment variables not only to override some cases, but also to add some cases without touching the makefile. For example:
make A unlikely_filenotfound=cccc xxx_cccc='X Y Z' FOO=filenotfound
This will use X Y Z
for the dependencies of A.
Obvious solution: use gmake
Of course, the obvious solution is to use GNUmake on BSD as well as Linux and MacOS, and write your makefiles for GNUmake only. That looks more reasonable than the above. Unless you have to deal with some BSD fanatics who don’t want to install GNU make on their machine.
Anyway, I started with the explicit (but maybe unreasonable) constraint of making portable makefiles. Over the years I’ve heard a number of very smart people claim that it was impossible, and I set out to prove them wrong. Mission accomplished.