Showing posts with label Linux Journal. Show all posts
Showing posts with label Linux Journal. Show all posts

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 MakefileOptions.inc 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.

#CONFIG_OPTIONS=\
#    OPTION_A \
#    -OPTION_B \
#    -OPTION_C

SET_CONFIG_OPTIONS=$(filter-out -%,$(CONFIG_OPTIONS))
UNSET_CONFIG_OPTIONS=$(patsubst -%,%,$(filter -%,$(CONFIG_OPTIONS)))
ALL_CONFIG_OPTIONS=$(SET_CONFIG_OPTIONS) $(UNSET_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:


include MakefileOptions.inc

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 -*-
#----------------------------------------------------------------------------
#
TARGET=Widget1
F_CPU=4000000
MCU=atxmega128a1

# 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:
CONFIG_OPTIONS=\
-ENABLE_CHIRP \
ENABLE_LEDS \
HAVE_RADIO \
USE_CRC_FLASH_CHECK \
-USE_DEBUGGING \
-USE_MOTION_SETTING_SCREEN

# 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 ----------------------------------*/
#if( USE_CRC_FLASH_CHECK > 0 )
extern uint16_t __data_load_end[1]; /* Defined by the linker script.  Set to address of last byte of .text+.data section */
#include 
static __inline__ uint16_t crc_flash_check_has_error( void );
static __inline__ uint16_t crc_flash_check_has_error( void )
{
...
}
#endif
/*---------------------- Cut Line ----------------------------------*/