假设我们有一个相互依赖的 C 模块的集合,并且我们想要创建一个 GNU Makefile,以便为几个不同的构建(例如,单元测试、用户工具、多个版本)单独编译它们。
每个模块虽然对于完整的应用程序至关重要,但旨在单独使用或与其他模块以任何合理的组合使用——始终公开功能最强大的 API,这是由于为特定构建选择的其他模块提供的组件的可用性而产生的。
为了一个最小且完整的示例,假设我们的程序具有三个模块(红色、绿色和蓝色),所有可能的条件功能都通过条件编译进行切换。每个模块都有两个这样的条件块,每一个都由两个可能的邻居之一启用。这为我们提供了三种可能的单一构建(红色、绿色、蓝色)、三个双重构建(青色、洋红色、黄色)和一个三重构建(白色)——每个构建都包含一个专用的主程序(核心),构建在一组 profided 之上特征。
期望的情况
图 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 $@ $<
这里的问题很明显:最后一条规则没有区分特定的构建——上下文丢失了。此外,我需要为每个模块提供不同的二进制版本以满足不同的构建。正确的方法是什么?