Back

BSD make — malformed conditional

This is a reprise of an e-mail I sent to the freebsd-ports@freebsd.org mailing list last January.

When hacking on Makefiles, should you wish to match an item in a list, you might write something like this:

.for item in ${LIST}
.if ${item} == ${THING}  # Ooops!
  THING_FOUND=1
.endif
.endfor

This however is a snare and a delusion, and will lead to much weeping and wailing, and error messages like so:

% make
"Makefile", line 7: Malformed conditional (foo == ${THING})
"Makefile", line 9: if-less endif
"Makefile", line 7: Malformed conditional (bar == ${THING})
"Makefile", line 9: if-less endif
"Makefile", line 7: Malformed conditional (baz == ${THING})
"Makefile", line 9: if-less endif
"Makefile", line 7: Malformed conditional (blurfl == ${THING})
"Makefile", line 9: if-less endif
make: fatal errors encountered -- cannot continue

Instead you should write your loops like this:

.for item in ${LIST}
.if ${THING} == ${item}
THING_FOUND=    1
.endif
.endfor

As the make(1) manual page says on the subject of string comparisons using == or !=:

An expression may also be a numeric or string comparison: in this case, the left-hand side must be a variable expansion, whereas the right-hand side can be a constant or a variable expansion.

So it seems that despite appearing and behaving almost exactly like one, the iterator in a .for loop is not actually a variable as such. It also means that to match a constant string, you can't just write:

.for item in ${LIST}
.if ${item} == this  # Ooops
THIS_FOUND=1
.endif
.endfor

but have to assign the text "this" to a variable somewhere, and use the second form.

Yes, you can (and preferrably should) use ${LIST:Mthis} instead, but using this construct only works when matching constant text:

% cat Makefile

  LIST=foo bar baz blurfl

  THING=baz

all:
  @echo "OK     \$${LIST:Mfoo} = ${LIST:Mfoo}"
  @echo "Not OK \$${LIST:M\$${THING}} = ${LIST:M${THING}}"
% make
OK     ${LIST:Mfoo} = foo
Not OK ${LIST:M${THING}} = }

Also note: as a matter of style, when matching a constant string, don't enclose that string in "quote marks" when it isn't necessary:

.if ${item} == "this"  # Wrong!