5

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

每个模块虽然对于完整的应用程序至关重要,但旨在单独使用或与其他模块以任何合理的组合使用——始终公开功能最强大的 API,这是由于为特定构建选择的其他模块提供的组件的可用性而产生的。

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

期望的情况

期望的情况

图 1 显示了三个模块(mod_red.cmod_green.c« mod_blue.cRGB»);在相邻模块实现的三个跨模块功能区域(青色、洋红色和黄色«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 $@ $<

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

4

2 回答 2

1

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

虽然拥有所有可能的模块构建规则,但我们离目标仅一步之遥;然而,这是一个相当棘手的步骤。我们需要重新构建每个构建的先决条件,以显式公开每个模块间的依赖关系。它是通过用环境邻域产生的特殊情况替换单个模块对象来完成的(例如,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

于 2013-12-02T17:47:46.620 回答
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 $@ $<
于 2013-09-25T17:05:22.977 回答