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.