C A surprising thing about BSD make: Suffix rules

Hi all,

BSD make provides suffix (inference) rules in order to generically instruct how .o files are generated from .c (or cpp) files.

One issue with suffix rules vs GNU make's pattern rules or nmake's extensions is that you can't use a different recipe per directory. For example, the following (make/suffix, nmake/extension, gmake/pattern respectively) will not work:

Code:
# POSIX suffix rule
.c.o:
    cc -c -g -o$@ $<

# nmake extension rule
{src/prog/}.c{src/prog/}.o:
    cc -c -O2 -o$@ $<

# gmake pattern rule
src/common/%.o: src/common/%.c
    cc -c -O3 -o$@ $<

A different recipe per directory is commonly required to pass different compiler flags (i.e additional include directories, optimization flags, etc) when building different parts of the project. You can see I have done that in the above example via the use of different optimization flags (-O2, -O3, etc). This gives rise to recursive Makefiles being the only solution, which has its own sets of problems.

The following however can almost be used as a replacement in BSD make:

Code:
$(PROG_OBJ):
    cc -c -O2 -o$@ $<

$(COMMON_OBJ):
    cc -c -O3 -o$@ $<

Expanded out of variables, this will look like:

Code:
src/prog/main.c src/prog/other.c:
    cc -c -O2 -o$@ $<

src/common/utils.c:
    cc -c -O3 -o$@ $<

However, you will be greeted with "Using $< in a non-suffix rule context is a GNUmake idiom (Makefile: lineno)". Because indeed, what we have created is no longer a suffix rule, it is a normal target rule and thus the $< doesn't make much sense.

HOWEVER! Deep in the make(1) manpages, it mentions that this is still fine to use if there is an inference rule that matches the current target and prerequisite. So we just need to keep going and treat it as a suffix rule. So consider the following more complete example:

Code:
PROG_OBJ=src/prog/main.o \
  src/prog/other.o

COMMON_OBJ=src/common/utils.o

.POSIX:
.SUFFIXES:
.SUFFIXES: .o .c

all: $(PROG_OBJ) $(COMMON_OBJ)

$(PROG_OBJ):
    cc -c -O2 -o$@ $<

$(COMMON_OBJ):
    cc -c -O3 -o$@ $<

# Will never be executed. Above rules take priority. Can use as a fallback if you like.
#.c.o:
#    cc -c -g -o$@ $<

The trick is that the make util will look at i.e src/prog/main.o and realize that if src/prog/main.c exists, it will allow the use of $<. However one slight side effect is that if that .c doesn't exist, it will complain about invalid GNU extensions rather than a slightly more meaningful output.

BSD Make was the last one remaining for me that was a little fiddly and this finding solves the last 1% bringing it in line with nmake (jom), wmake, gmake. This post is mostly to personally record my findings but also for those that are frustrated with the seemingly limited suffix rules that BSD make apparently only has access to. Ironically no other make utils provide this "extension" to target rules relating to suffix rules. However, luckily they have their own workarounds.
 
Might need some extra processing if one of the variables contains more than just one file though 😉

Yeah, expanded it would look something like:

Code:
{src/prog/main.o src/prog/other.o,src/prog/main.c src/prog/other.c}:

Which I believe is a little more problematic; hence the fact that BSD make can still infer the src required when presented with an obj is actually pretty cool (and not nearly documented enough).

Alongside:

OBJ=$(SRC:.c=.o)

and you actually end up with a fairly nice to maintain setup.
 
Back
Top