假设我们有一组相互依赖的C模块,我们想为几个不同的构建(例如,单元测试,用户工具,多个版本)为单独编译创建一个GNU Makefile。
每个模块虽然对整个应用程序至关重要,但可以单独使用,也可以与其他模块以任何合理的组合使用 - 始终公开最具特色的API,这些API是由为特定构建选择的其他模块提供的组件的可用性产生的
为了最小和完整的例子,我们假设我们的程序有三个模块(红色,绿色和蓝色),所有可能的条件功能都通过条件编译切换。每个模块具有两个这样的条件块,每个条件块由两个可能的邻居之一的存在来启用。这为我们提供了三种可能的单一构建(红色,绿色,蓝色),三种双重构建(青色,洋红色,黄色)和一种三重构建(白色) - 每个构建都包含一个专用的主程序(核心),构建在一组构建的特征。
期望的情况
图1显示了三个模块(mod_red.c
,mod_green.c
和mod_blue.c
«RGB»);交叉模块功能的三个区域(青色,品红色和黄色«CMY»)在相邻模块中实现;和三个核心(白色,具有物理依赖性«RGB»在大的,锐化的顶部和逻辑依赖性«CMY»在小顶部)。每个方向(六个中的一个)表示功能方面,因此CMY顶部指向主三角形表明协同作用可能提供额外的功能。
期望的Makefile应该为所有可能的构建提供配方,因此使用三个模块和七个不同核心中的每一个的四个版本。它应该足够智能,以避免残酷的解决方案(每个配方的gcc
命令的完整块)并保持单独编译的优势。
没有单独的编译,问题很容易(至少对于单边依赖):主程序包括必要的源,并且依赖块由预处理器标志启用,例如,由其他模块设置的那些包括警卫。但是,通过单独编译,编译器不知道包含特定构建的模块集。
手动方法
可以使用下面列出的shell命令手动实现所需的情况。
# Single objects:
gcc -c -o mod_green.o mod_green.c
# Double objects
gcc -c -include mod_blue.h -o mod_red+B.o mod_red.c
gcc -c -include mod_red.h -o mod_blue+R.o mod_blue.c
# Triple objects
gcc -c -include mod_green.h -include mod_blue.h -o mod_red+G+B.o mod_red.c
gcc -c -include mod_red.h -include mod_blue.h -o mod_green+R+B.o mod_green.c
gcc -c -include mod_red.h -include mod_green.h -o mod_blue+R+G.o mod_blue.c
# Builds
gcc -o green green.c mod_green.o
gcc -o magenta magenta.c mod_red+B.o mod_blue+R.o
gcc -o white white.c mod_red+G+B.o mod_green+R+B.o mod_blue+R+G.o
至于所需的情况,此示例仅显示三个代表性构建:绿色,洋红色和白色。其他类似的形成。
经典方法
使用经典的Makefile解决方案,Green构建保持不变,但其他两个缺少逻辑依赖性(即CMY提供的符号)。之所以如此,是因为建筑过程目前(通常)定义如下:
white: white.c mod_red.o mod_green.o mod_blue.o
gcc -o $@ $^
magenta: magenta.c mod_blue.o mod_red.o
gcc -o $@ $^
green: green.c mod_green.o
gcc -o $@ $^
%.o: %.c
gcc -c -o $@ $<
这里明显暴露了问题:最后一条规则没有区分特定的构建 - 上下文丢失了。此外,我需要最终得到每个模块的不同二进制版本,以满足不同的构建。这样做的正确方法是什么?
答案 0 :(得分:1)
这应该有效。虽然没试过。我认为从这一点开始制定其他规则会很容易。
BINARY = build
CC = gcc
SOURCES_RGB = rgb.c mod_red.c mod_green.c mod_blue.c
OBJECTS_RGB = $(SOURCES_RGB:.c=_rgb.o)
BINARY_RGB = $(addprefix RGB-,$(BINARY))
CFLAGS_RGB = -include mod_rgb.h
$(BINARY_RGB): $(OBJECTS_RGB)
$(CC) -o $@ $^
%_rgb.o: %.c
$(CC) -c $(CFLAGS_RGB) -o $@ $<
答案 1 :(得分:1)
使用GNU Make 3.82,可以 define
罐装食谱 - 在参数化后 - 可以用作双/三个对象的模板'建立规范。然后可以使用Make的唯一$(call var,par,...)
函数对模板进行实例化,并使用从3.8版开始提供的非常特殊的$(eval code)
进行评估。这个技巧使我们可以避免大量的样板代码,并使Makefile能够自动适应即将到来的项目增长。
虽然可以使用所有可能的模块构建规则,但我们离目标只有一步之遥;然而,这是一个相当棘手的步骤。我们需要重建每个构建的先决条件以显式公开每个模块间依赖关系。它是通过用周围邻域产生的特殊情况替换单个模块对象来完成的(例如,表面mod_red.o mod_green.o
被显式mod_red+G.o mod_green+R.o
代替)。替换由discover
宏处理每个先决条件列表并由xdepend
全局变量驱动,该变量指定了交叉依赖性 - 实际上,只有少数模块将相互依赖。
# Cross dependency specification (dependent+dependency)
xdepend := blue+red red+blue
# Test for string equality and set membership
equals = $(if $(subst $1,,$2),$(empty),true)
exists = $(if $(filter $1,$2),true,$(empty))
# Extract of the first and second member of a dependency token
pred = $(firstword $(subst +, ,$1))
succ = $(lastword $(subst +, ,$1))
# Rebuild prerequisites to expose modules' interdependencies
discover = $(strip $(foreach mod,$(basename $1),\
$(subst $(space),,$(obj)/$(mod)\
$(foreach dep,$(xdepend),\
$(and\
$(call equals,$(call pred,$(dep)),$(mod)),\
$(call exists,$(call succ,$(dep)),$(basename $1)),\
$(lnk)$(call succ,$(dep))\
)\
).o)\
))
# Create compilation rules for interdependent module objects
define rule
$(obj)/$1$(lnk)$2.o: $(src)/$1.c $(inc)/$1.h $(inc)/$2.h | $(obj)
$(CC) $(CFLAGS) -include $(inc)/$2.h -c $(src)/$1.c -o $(obj)/$1$(lnk)$2.o
endef
$(foreach dep,$(xdepend),\
$(eval $(call rule,$(call pred,$(dep)),$(call succ,$(dep))))\
)
有了上述定义,我们现在可以通过以下方式构建项目:
# Rules for Magenta Build and intermediate objects
magenta: $(call discover, magenta.o mod_red.o mod_blue.o)
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
$(obj)/%.o: $(src)/%.c $(inc)/%.h | $(obj)
$(CC) $(CFLAGS) -c -o $@ $<
$(obj):
mkdir $(obj)
有关进一步说明和最新知识,请阅读GNU Make Manual。