Sunday, August 22, 2010

Building product variations without duplication of code and configuration. Makefile Tip #1

Mitch Frazier published a How To, Turn Make Options into Tool Flags, in The Linux Journal a couple of years ago, that I have found very useful in my Embedded development projects.

Using Mitch's technique I can have several different top level Makefiles to define options, source locations, and target output directories, that then includes a common Makefile called Makefile.mak. This allows me to build several variations of a product from the same set of sources, while keeping their options distinct and outputs separate.

Mitch's original technique is not MISRA friendly, as it relies on using 'ifdef', that is a missing option is considered a disabled option. MISRA does not allow this because the missing option may have been an oversight. Here is my modified MISRA friendly version:

Create a file called with the following code:

#---------------------- Cut Line ----------------------------------#
# Option names that start with a minus sign are disabled by default,
# option names without a minus sign are enabled by default.

#    OPTION_A \
#    -OPTION_B \
#    -OPTION_C

UNSET_CONFIG_OPTIONS=$(patsubst -%,%,$(filter -%,$(CONFIG_OPTIONS)))
#$(info Set: $(SET_CONFIG_OPTIONS))
#$(info Unset: $(UNSET_CONFIG_OPTIONS))

# Turn config options into make variables.
$(foreach cfg,$(SET_CONFIG_OPTIONS),$(eval $(cfg)=1))
$(foreach cfg,$(UNSET_CONFIG_OPTIONS),$(eval $(cfg)=))

# Make sure none of the options are set to anything except 1 or blank.
# Using "make OPTION=0" doesn't work, since "0" is set, you need "make OPTION=".
$(foreach cfg,$(ALL_CONFIG_OPTIONS), \
    $(if $(patsubst %1,%,$(value $(cfg))), \
        $(error Use "$(cfg)=1" OR "$(cfg)=" not "$(cfg)=$(value $(cfg))")))

# Turn them into tool flags (-D).
#                                                   if   cfg > 0       True:      False:
TOOL_DEFINES+=$(foreach cfg,$(ALL_CONFIG_OPTIONS),$(if $(value $(cfg)),-D$(cfg)=1,-D$(cfg)=0))
#$(info $(TOOL_DEFINES))
#---------------------- Cut Line ----------------------------------#

A the top of your normal Makefile 'include' the above file like so:


The for a typical project I'll have a project uniquely named makefile, such as 'widget1.mak', that goes along these lines:

#---------------------- Cut Line ----------------------------------#
# Hey Emacs, this is a -*- makefile -*-

# Option names that start with a minus sign are disabled by default,
# option names without a minus sign are enabled by default.
# Values on the command line override these:

# List C source files here. (C dependencies are automatically generated.)
SRC = Accel/accel.c \
MENU_WIDGET1/menu.c \

ASRC = HARDWARE_XMega/sp_ReadFuseByte.S HARDWARE_XMega/sp_commoncmd.S ...

EXTRAINCDIRS = MENU_WIDGET1 HARDWARE Accel Alarm Backlight Battery CRC ...

include Makefile.mak

#---------------------- Cut Line ----------------------------------#

Each project variation gets a similar file that is invoked with 'make -f widget1.mak'.

An example C usage:

/*---------------------- Cut Line ----------------------------------*/
extern uint16_t __data_load_end[1]; /* Defined by the linker script.  Set to address of last byte of section */
static __inline__ uint16_t crc_flash_check_has_error( void );
static __inline__ uint16_t crc_flash_check_has_error( void )
/*---------------------- Cut Line ----------------------------------*/