C 项目 - 如何管理功能列表?
C Project - How to manage a feature list?
我有多个功能可以在项目构建时启用或禁用。
当前实现使用 #define FEATURE_FOO
等声明。每当我需要做一些与特定功能相关的事情时,我都会使用 pre-processor 指令,例如 #ifdef
。
功能定义存储在全局 header 文件中。
这种方法有两个缺点:
- 它要求在每个文件中
#include
这个全局 header,在任何其他 header 之前。
- 我无法轻易禁用 C 文件:
这不是很好:
// file: foo.c
#include <stdio.h>
#include "main_header.h"
#ifdef FEATURE_FOO
...
#endif
因为我更喜欢这个:
// file: foo.c
#ifdef FEATURE_FOO
#include <stdio.h>
...
#endif
因此,解决此问题的另一种方法是在构建时声明我的所有功能:
gcc -DFEATURE_FOO -c %< -o %@
这里我不喜欢的是我需要手动将每个功能传递给我的编译器。
可接受的解决方法是读取包含所有功能的 features.list
文件。在我的 Makefile 中,我将拥有:
DEFINES=$(shell perl -ne 'print "-DFEATURE_ " if /(\w+)/' features.list)
%o: %c
gcc $(DEFINES) -c %< -o $@
我能找到什么更好的选择?
您可以使用 gcc 的选项 -include myheader.h
。
它将 myheader.h
的内容添加到当前翻译单元源的最开头。
我的大部分项目都使用基于 GNU make 的构建过程,虽然到目前为止它与功能无关,但我使用的技术也可以帮助您。
首先,拥有一个配置文件的想法非常好,但为什么不在 make
语法和 include
中拥有它呢?
我用的是这样的
# default configuration
CC := gcc
DEBUG := 0
GCC32 := 0
USELTO := 1
# read local configuration
-include defaults.mk
您可以使用它来获得功能列表,例如在你的 defaults.mk
FEATURES := foo bar baz
然后做类似
的事情
FEATUREDEFINES := $(addprefix -DFEATURE_, $(FEATURES))
当您使用 $(eval ...)
函数时,GNU make 有更多的 黑魔法 可能——这可能是一个很好的替代方法,可以从中完全排除源文件编译取决于您的设置。我将其用于特定于平台的实现。例如,我有这个包含用于构建二进制文件的 Makefile:
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))
我的 P
是源代码树中的当前相对路径,T
是要构建的目标而 PSEP
只是一个包含 /
或 \
以便与 windows 兼容。其余部分应该是不言自明的——对于 $(T)_PLATFORMSOURCES
,$(BINRULES)
在相对路径 platform/$(PLATFORM)/
中查找。它是这样工作的:
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
所有这些双倍美元都在那里,因为 $(eval ...)
将扩展变量——这是 $(T)
和 $(P)
所需要的,但不是所有其他变量所需要的,因此它们受到保护额外的美元。我只是引用了决定在这里编译哪些文件的神奇部分。如果您考虑这样做,see the full example
我有多个功能可以在项目构建时启用或禁用。
当前实现使用 #define FEATURE_FOO
等声明。每当我需要做一些与特定功能相关的事情时,我都会使用 pre-processor 指令,例如 #ifdef
。
功能定义存储在全局 header 文件中。
这种方法有两个缺点:
- 它要求在每个文件中
#include
这个全局 header,在任何其他 header 之前。 - 我无法轻易禁用 C 文件:
这不是很好:
// file: foo.c
#include <stdio.h>
#include "main_header.h"
#ifdef FEATURE_FOO
...
#endif
因为我更喜欢这个:
// file: foo.c
#ifdef FEATURE_FOO
#include <stdio.h>
...
#endif
因此,解决此问题的另一种方法是在构建时声明我的所有功能:
gcc -DFEATURE_FOO -c %< -o %@
这里我不喜欢的是我需要手动将每个功能传递给我的编译器。
可接受的解决方法是读取包含所有功能的 features.list
文件。在我的 Makefile 中,我将拥有:
DEFINES=$(shell perl -ne 'print "-DFEATURE_ " if /(\w+)/' features.list)
%o: %c
gcc $(DEFINES) -c %< -o $@
我能找到什么更好的选择?
您可以使用 gcc 的选项 -include myheader.h
。
它将 myheader.h
的内容添加到当前翻译单元源的最开头。
我的大部分项目都使用基于 GNU make 的构建过程,虽然到目前为止它与功能无关,但我使用的技术也可以帮助您。
首先,拥有一个配置文件的想法非常好,但为什么不在 make
语法和 include
中拥有它呢?
我用的是这样的
# default configuration
CC := gcc
DEBUG := 0
GCC32 := 0
USELTO := 1
# read local configuration
-include defaults.mk
您可以使用它来获得功能列表,例如在你的 defaults.mk
FEATURES := foo bar baz
然后做类似
的事情FEATUREDEFINES := $(addprefix -DFEATURE_, $(FEATURES))
当您使用 $(eval ...)
函数时,GNU make 有更多的 黑魔法 可能——这可能是一个很好的替代方法,可以从中完全排除源文件编译取决于您的设置。我将其用于特定于平台的实现。例如,我有这个包含用于构建二进制文件的 Makefile:
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))
我的 P
是源代码树中的当前相对路径,T
是要构建的目标而 PSEP
只是一个包含 /
或 \
以便与 windows 兼容。其余部分应该是不言自明的——对于 $(T)_PLATFORMSOURCES
,$(BINRULES)
在相对路径 platform/$(PLATFORM)/
中查找。它是这样工作的:
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
所有这些双倍美元都在那里,因为 $(eval ...)
将扩展变量——这是 $(T)
和 $(P)
所需要的,但不是所有其他变量所需要的,因此它们受到保护额外的美元。我只是引用了决定在这里编译哪些文件的神奇部分。如果您考虑这样做,see the full example