Table of contents

Build flavors handling with Make

The goal of this paper isn’t to explain everything about Make, but to offer a way to handle build flavors. The technique presented here has been checked with GNU make, and depends on it for some parts.

What are flavors?

Flavors are a related set of variable values that can be chosen at compile time. For instance you could have a debug flavor with the following variables defined:

CPPFLAGS=
CXXFLAGS=-g

while the corresponding release flavor would use

CPPFLAGS=-DNDEBUG
CXXFLAGS=-O2

The choice of tool chain would be another example. We’ll simplify the presentation considering that we want the user to be able to chose the value of the variable VAR by specifying the value of the variable FLAVOR on the command line with the value name1 and name2.

A simple switch

This is the basic structure. We just use the possibility to build a variable from the value of another:

FLAVOR=name1
VAR_name1=value1
VAR_name2=value2
VAR=$(VAR_$(FLAVOR))

Defining a value for FLAVOR in the Makefile allows to give it a default which can be overritten in the command line.

Common value

The variables have often a common part. There are two ways of reducing the redundancy in the definition. The most efficient and drastic way is to define that common part in the definition of VAR, perhaps with an indirection:

FLAVOR=name1
VAR_COMMON=common part
VAR_name1=value1
VAR_name2=value2
VAR=$(COMMON_PART) $(VAR_$(FLAVOR))

An alternative is to use the variable defining the common part in the flavored definitions. This is more verbose but allows a flavor to avoid using the common part.

FLAVOR=name1
VAR_COMMON=common part
VAR_name1=$(VAR_COMMON) value1
VAR_name2=$(VAR_COMMON) value2
VAR=$(VAR_$(FLAVOR))

Defining default for unknown flavors

The origin function allows to know if a variable has been defined. Use of if and filter then allow to switch.

FLAVOR=name1
VAR_DEFAULT=valued
VAR_name1=value1
VAR_name2=value2
VAR=$($(if $(filter undefined,$(origin VAR_$(FLAVOR))),$(VAR_DEFAULT),$(VAR_$(FLAVOR))))

Automatic rebuild when the flavor changes

As flavor controls the way the build is make, one has to be careful to clean the build artefacts when changing flavor with the techniques presented until now if one desires to have an homogenous build. If one want to ensure the homogeneity of the build, a way is to define a file holding a timestamp for the last flavor build, ensuring that only one such file exist and to make all the actions which depend on the flavor depend on it. Redefining pattern rules can be used for that (but not suffix rules). For instance, one could do

FLAVOR=name1
VAR_name1=value1
VAR_name2=value2
VAR=$(VAR_$(FLAVOR))
FLAVOR_TIMESTAMP=.timestamp-for-flavor-$(FLAVOR)

%.o: %.c $(FLAVOR_TIMESTAMP)
        $(COMPILE.c) $(OUTPUT_OPTION) $<

$(FLAVOR_TIMESTAMP):
        -rm .timestamp-for-flavor-*
        touch $@

Persistent flavor

Once you have the mechanism in place for automatic rebuild, you can save the current rule and use it as a default:

FLAVOR:=$(shell [ -f .last-flavor ] && cat .last-flavor || echo name1)
VAR_name1=value1
VAR_name2=value2
VAR=$(VAR_$(FLAVOR))
FLAVOR_TIMESTAMP=.timestamp-for-flavor-$(FLAVOR)

%.o: %.c $(FLAVOR_TIMESTAMP)
        $(COMPILE.c) $(OUTPUT_OPTION) $<

$(FLAVOR_TIMESTAMP):
        -rm .timestamp-for-flavor-*
        echo $(FLAVOR) > .last-flavor
        touch $@

Note the use of := to define FLAVOR so that the shell is not reevaluated each time FLAVOR is used.

An alternative

An other way to handle flavors which can be used alone or combined with the one offered here is to use inclusion of makefile fragments which depend on the flavor desired. The simplest example would be to use something like:

include build-$(FLAVOR).mk

This offer a different set of trade-offs which will not be examined here.