单独编译相互依赖的C模块

时间:2013-09-24 22:52:34

标签: c gcc compilation makefile

假设我们有一组相互依赖的C模块,我们想为几个不同的构建(例如,单元测试,用户工具,多个版本)为单独编译创建一个GNU Makefile。

每个模块虽然对整个应用程序至关重要,但可以单独使用,也可以与其他模块以任何合理的组合使用 - 始终公开最具特色的API,这些API是由为特定构建选择的其他模块提供的组件的可用性产生的

为了最小和完整的例子,我们假设我们的程序有三个模块(红色,绿色和蓝色),所有可能的条件功能都通过条件编译切换。每个模块具有两个这样的条件块,每个条件块由两个可能的邻居之一的存在来启用。这为我们提供了三种可能的单一构建(红色,绿色,蓝色),三种双重构建(青色,洋红色,黄色)和一种三重构建(白色) - 每个构建都包含一个专用的主程序(核心),构建在一组构建的特征。

期望的情况

desired situation

图1显示了三个模块(mod_red.cmod_green.cmod_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

至于所需的情况,此示例仅显示三个代表性构建:绿色,洋红色和白色。其他类似的形成。

经典方法

enter image description here

使用经典的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 $@ $<

这里明显暴露了问题:最后一条规则没有区分特定的构建 - 上下文丢失了。此外,我需要最终得到每个模块的不同二进制版本,以满足不同的构建。这样做的正确方法是什么?

2 个答案:

答案 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