How to handle debug and release compiler flags in BSD make

47 Views Asked by At

Using BSD make, I wish to use different compiler flags for the debug and release versions of my program. The version of the program to build should be defined via a MODE variable. I would like to only run the compiler when necessary, meaning that if I build the debug version and then build the release version, I would like to recompile all objects, even though they are technically up-to-date according to make.

A simplified version of my makefile would look like this:

MODE ?= DEBUG # DEBUG or RELEASE

CFLAGS = -std=c99 -Werror -Wall -Wextra

.if $(MODE) == DEBUG
CFLAGS += -g -fsanitize=undefined,address
.endif

a.o: a.c a.h
    cc -c $(CFLAGS) -o $(.TARGET) $(.ALLSRC:M*.c)

b.o: b.c a.h b.h
    cc -c $(CFLAGS) -o $(.TARGET) $(.ALLSRC:M*.c)

c.o: c.c c.h
    cc -c $(CFLAGS) -o $(.TARGET) $(.ALLSRC:M*.c)

program: a.o b.o c.o
    cc $(CFLAGS) -o $(.TARGET) $(.ALLSRC)

This example would build fine the first time, but if I change MODE to RELEASE, make would inform me that there is nothing to build, as everything is already up-to-date.

The only way I found to achieve the result I'm looking for is to print the MODE variable into a plain text file at the end of the build process, check for equality with the current MODE value, and force a rebuild on all "rebuildable" targets if MODE has changed.

MODE ?= DEBUG # DEBUG or RELEASE
LAST_MODE != cat mode

CFLAGS = -std=c99 -Werror -Wall -Wextra

.if $(MODE) == DEBUG
CFLAGS += -g -fsanitize=undefined,address
.endif

.if $(MODE) != $(LAST_MODE)
rebuildable: .PHONY

.END:
    printf "$(MODE)" > $(EXPORTS)/mode
.else
rebuildable: .USE
.endif

a.o: a.c a.h rebuildable
    cc -c $(CFLAGS) -o $(.TARGET) $(.ALLSRC:M*.c)

b.o: b.c a.h b.h rebuildable
    cc -c $(CFLAGS) -o $(.TARGET) $(.ALLSRC:M*.c)

c.o: c.c c.h rebuildable
    cc -c $(CFLAGS) -o $(.TARGET) $(.ALLSRC:M*.c)

program: a.o b.o c.o rebuildable
    cc $(CFLAGS) -o $(.TARGET) $(.ALLSRC:M*.o)

Is there a more idiomatic, less hacky way of doing this?

1

There are 1 best solutions below

2
On

Is there a more idiomatic, less hacky way of doing this?

You have to have some way to memorialize in what mode targets have been built. I would suggest, though, that a more natural perspective on the problem is that Target-A-Debug is a logically different target from Target-A-Release, even if they are ultimately built from the same sources. It follows that you should have different rules for them.

That might work out something like this (but please forgive me if I mistake any bmake-specific syntax):

MODE ?= DEBUG # DEBUG or RELEASE

CFLAGS_RELEASE = -std=c99 -Werror -Wall -Wextra
CFLAGS_DEBUG = $(CFLAGS_RELEASE) -g -fsanitize=undefined,address

OBJS_BASE = a.o b.o c.o

.if $(MODE) == DEBUG
MODE_SUFFIX = -debug
.else
MODE_SUFFIX = -release
.endif

all: program$(MODE_SUFFIX) .PHONY
    cp $(.ALLSRC[1]) program

a-debug.o: a.c a.h
    cc -c $(CFLAGS_DEBUG) -o $(.TARGET) $(.ALLSRC:M*.c)

b-debug.o: b.c a.h b.h
    cc -c $(CFLAGS_DEBUG) -o $(.TARGET) $(.ALLSRC:M*.c)

c-debug.o: c.c c.h
    cc -c $(CFLAGS_DEBUG) -o $(.TARGET) $(.ALLSRC:M*.c)

program-debug: $(OBJS_BASE:S/.o$/-debug.o/)
    cc $(CFLAGS) -o $(.TARGET) $(.ALLSRC:M*.o)

a-release.o: a.c a.h
    cc -c $(CFLAGS_RELEASE) -o $(.TARGET) $(.ALLSRC:M*.c)

b-release.o: b.c a.h b.h
    cc -c $(CFLAGS_RELEASE) -o $(.TARGET) $(.ALLSRC:M*.c)

c-release.o: c.c c.h
    cc -c $(CFLAGS_RELEASE) -o $(.TARGET) $(.ALLSRC:M*.c)

program-release: $(OBJS_BASE:S/.o$/-release.o/)
    cc $(CFLAGS_RELEASE) -o $(.TARGET) $(.ALLSRC:M*.o)

I imagine it's possible to apply a bmake-ism or two to merge those two separate lists of rules into one -- this is left as an exercise :-)