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

Krz*_*icz 5 c gcc makefile compilation

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

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

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

期望的情况

理想的情况

图1示出了三个模块(mod_red.c,mod_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
Run Code Online (Sandbox Code Playgroud)

至于期望的情况,这个例子只显示了三个代表性的版本:绿色,洋红色和白色.其他类似的形成.

经典的方法

在此输入图像描述

使用经典的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 $@ $<
Run Code Online (Sandbox Code Playgroud)

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

Krz*_*icz 1

使用 GNU Make 3.82,可以将罐装配方 \xe2\x80\x93 在参数化后 \xe2\x80\x93 用作双/三重对象构建规范的模板。然后可以使用 Make\ 的独特函数实例化模板,并使用自版本 3.8 起可用的非常特殊的 进行评估。这个技巧使我们能够避免大量的样板代码,并使 Makefile 能够自动适应即将到来的项目增长。define $(call var,par,...)$(eval code)

\n\n

虽然拥有所有可能的模块构建规则,但我们距离目标仅一步之遥;然而,这是一个相当棘手的步骤。我们需要重建每个构建的先决条件,以显式公开每个模块间依赖关系。这是通过用周围邻域产生的特殊情况替换单个模块对象来完成的(例如,mod_red.o mod_green.o用显式替换表面mod_red+G.o mod_green+R.o)。替换由包含discover每个先决条件列表的宏处理,并由xdepend全局变量驱动,该变量在实践中指定交叉依赖 \xe2\x80\x93 ,只有少数模块会相互依赖。

\n\n
# Cross dependency specification (dependent+dependency)\nxdepend := blue+red red+blue\n\n# Test for string equality and set membership\nequals = $(if $(subst $1,,$2),$(empty),true)\nexists = $(if $(filter $1,$2),true,$(empty))\n\n# Extract of the first and second member of a dependency token\npred = $(firstword $(subst +, ,$1))\nsucc = $(lastword $(subst +, ,$1))\n\n# Rebuild prerequisites to expose modules\' interdependencies\ndiscover = $(strip $(foreach mod,$(basename $1),\\\n    $(subst $(space),,$(obj)/$(mod)\\\n    $(foreach dep,$(xdepend),\\\n        $(and\\\n            $(call equals,$(call pred,$(dep)),$(mod)),\\\n            $(call exists,$(call succ,$(dep)),$(basename $1)),\\\n            $(lnk)$(call succ,$(dep))\\\n        )\\\n    ).o)\\\n))\n\n\n# Create compilation rules for interdependent module objects\ndefine rule\n$(obj)/$1$(lnk)$2.o: $(src)/$1.c $(inc)/$1.h $(inc)/$2.h | $(obj)\n    $(CC) $(CFLAGS) -include $(inc)/$2.h -c $(src)/$1.c -o $(obj)/$1$(lnk)$2.o\n\nendef\n\n$(foreach dep,$(xdepend),\\\n    $(eval $(call rule,$(call pred,$(dep)),$(call succ,$(dep))))\\\n)\n
Run Code Online (Sandbox Code Playgroud)\n\n

有了上述定义,我们现在可以通过以下方式构建我们的项目:

\n\n
# Rules for Magenta Build and intermediate objects\nmagenta: $(call discover, magenta.o mod_red.o mod_blue.o)\n    $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)\n\n$(obj)/%.o: $(src)/%.c $(inc)/%.h | $(obj)\n    $(CC) $(CFLAGS) -c -o $@ $<\n\n$(obj):\n    mkdir $(obj)\n
Run Code Online (Sandbox Code Playgroud)\n\n

如需进一步说明和最新知识 \xe2\x80\x93 请阅读GNU Make 手册

\n