C Project - How to manage the feature list?
I have several features that can be enabled or disabled at build time in a project.
The current implementation uses declarations such as #define FEATURE_FOO
. Whenever I need to do something about a particular feature, I use a preprocessor directive such as #ifdef
.
Function definitions are stored inside a global header file.
This approach has two drawbacks:
- This requires
#include
this global header in every file before any other header. - I cannot easily disable the C file:
This is not good:
// file: foo.c
#include <stdio.h>
#include "main_header.h"
#ifdef FEATURE_FOO
...
#endif
Because I prefer this:
// file: foo.c
#ifdef FEATURE_FOO
#include <stdio.h>
...
#endif
So, another approach to this problem is to declare all my functions at build time:
gcc -DFEATURE_FOO -c %< -o %@
What I don't like here is that I have to manually pass each function to my compiler.
A valid workaround would be to read a file features.list
that contains all the functions. In my Makefile I will have:
DEFINES=$(shell perl -ne 'print "-DFEATURE_$1 " if /(\w+)/' features.list)
%o: %c
gcc $(DEFINES) -c %< -o $@
What's the best alternative I can find?
source to share
I use the GNU make-based build process for most of my projects, and while this was not about features, I used methods that might help you here too.
First, the idea of ββhaving a config file is very good, but why not just use it in the syntax make
and include
it?
I am using something like this
# default configuration
CC := gcc
DEBUG := 0
GCC32 := 0
USELTO := 1
# read local configuration
-include defaults.mk
You can use this to have a list of functions, eg. indefaults.mk
FEATURES := foo bar baz
and then do something like
FEATUREDEFINES := $(addprefix -DFEATURE_, $(FEATURES))
There $(eval ...)
is a lot more black magic when using a function if you use a function $(eval ...)
- this can be a good alternative to exclude the source file from compilation entirely, depending on your settings. I am using this for platform implementation. For example, I included a Makefile to create a binary:
P:= src
T:= csnake
csnake_SOURCES:= csnake.c utils.c game.c board.c snake.c food.c screen.c
csnake_PLATFORMSOURCES:= ticker.c
csnake_LDFLAGS:= -lm
csnake_posix_LDFLAGS:= -lcurses
csnake_dos_LDFLAGS:= -Wl,-Bstatic -lpdcurses
csnake_win32_LDFLAGS:= -static-libgcc -Wl,-Bstatic -lpdcurses \
-Wl,-Bdynamic -lwinmm
csnake_win32_RES:= res$(PSEP)csnake.rc
$(eval $(BINRULES))
My P
is the current relative path in the source tree, T
is the target for the build, and PSEP
is just an auxiliary variable containing /
or \
to be compatible with windows. The rest should be obvious - for $(T)_PLATFORMSOURCES
, $(BINRULES)
looks in the relative path platform/$(PLATFORM)/
. It works like this:
define BINRULES
BINARIES += $$(BINDIR)$$(PSEP)$(T)$$(EXE)
$(T)_SOURCES_FULL := $$(addprefix $(P)$$(PSEP),$$($(T)_SOURCES))
ifneq ($$(strip $$($(T)_PLATFORMSOURCES)),)
$(T)_SOURCES_FULL += $$(addprefix \
$(P)$$(PSEP)platform$$(PSEP)$$(PLATFORM)$$(PSEP), \
$$($(T)_PLATFORMSOURCES))
endif
[...] (... further rules ... )
endef
All these double dollars exist because it $(eval ...)
will expand variables - which are desirable for $(T)
and $(P)
but not for everyone else, so they are protected by an additional dollar. I'm just quoting the part doing the magic to decide which files to compile here. If you decide to do something like this, see the complete example
source to share