GNU make中的错误:特定于目标的变量不会在隐式规则中扩展吗?

时间:2010-11-01 13:57:38

标签: makefile gnu gnu-make

我一直在设计一个多配置Makefile(一个支持单独的'debug'和'release'目标),并且遇到了一个奇怪的问题,这似乎是GNU make中的一个错误。

当隐式规则中引用这些变量时,GNU make似乎没有正确扩展特定于目标的变量。这是一个简化的Makefile,它显示了这个问题:

all:
    @echo specify configuration 'debug' or 'release'

OBJS := foo.o bar.o

BUILDDIR = .build/$(CONFIG)

TARGET = $(addprefix $(BUILDDIR)/,$(OBJS))

debug: CONFIG := debug
release: CONFIG := release

#CONFIG := debug

debug: $(TARGET)
release: $(TARGET)

clean:
    rm -rf .build

$(BUILDDIR)/%.o: %.c
    @echo [$(BUILDDIR)/$*.o] should be [$@]
    @mkdir -p $(dir $@)
    $(CC) -c $< -o $@

当指定目标'debug'时,CONFIG设置为'debug',BUILDDIR和TARGET同样正确扩展。但是,在从对象构建源文件的隐式规则中,$ @被展开,就好像CONFIG不存在一样。

以下是使用此Makefile的输出:

$ make debug
[.build/debug/foo.o] should be [.build//foo.o]
cc -c foo.c -o .build//foo.o
[.build/debug/bar.o] should be [.build//bar.o]
cc -c bar.c -o .build//bar.o

这表明BUILDDIR正在扩展正常,但结果$ @不是。如果我然后注释掉目标变量规范并手动设置CONFIG:= debug(上面的注释行),我会得到我期望的结果:

$ make debug
[.build/debug/foo.o] should be [.build/debug/foo.o]
cc -c foo.c -o .build/debug/foo.o
[.build/debug/bar.o] should be [.build/debug/bar.o]
cc -c bar.c -o .build/debug/bar.o

我在Gentoo和MinGW上使用make-3.81和在Gentoo上使用make-3.82进行了测试。所有人都表现出相同的行为。

我发现很难相信我会是第一个遇到这个问题的人,所以我猜我可能只是做错了 - 但我会说实话:我不知道我是怎么回事可能。 :)

那里是否有任何制作大师可以对这个问题有所了解?谢谢!

3 个答案:

答案 0 :(得分:9)

基本上,Make会计算出依赖项的DAG,并创建一个必须在运行任何规则之前运行的规则列表。分配特定于目标的值是Make在运行规则时所做的事情,稍后会出现。这是一个严重的限制(我和其他人之前曾抱怨过),但我不会把它称为bug,因为它在文档中有描述。根据GNUMake手册:

6.11 Target-specific Variable Values:“与自动变量一样,这些值仅在目标配方的上下文中可用(以及在其他特定于目标的分配中)。”

“目标配方的上下文”是指命令,而不是先决条件:

10.5.3 Automatic variables:“无法在规则的先决条件列表中直接访问[自动变量]。”

有很多方法可以解决这个问题。你可以使用Secondary Expansion,如果你的Make GNUMake版本有它(3.81没有,我不知道3.82)。或者你可以不用特定于目标的变量:

DEBUG_OBJS = $(addprefix $(BUILDDIR)/debug/,$(OBJS))
RELEASE_OBJS = $(addprefix $(BUILDDIR)/release/,$(OBJS))

debug: % : $(DEBUG_OBJS)
release: $(RELEASE_OBJS)

$(DEBUG_OBJS): $(BUILDDIR)/debug/%.o : %.cc
$(RELEASE_OBJS): $(BUILDDIR)/release/%.o : %.cc

$(DEBUG_OBJS) $(RELEASE_OBJS):
    @echo making $@ from $^
    @mkdir -p $(dir $@)                                                        
    $(CC) -c $< -o $@ 

答案 1 :(得分:7)

正如Beta所指出的那样,这确实不是make中的错误,因为文档中描述了限制(我想我一定错过了那个特定的部分 - 抱歉)。

无论如何,我实际上能够通过做一些更简单的事情来解决这个问题。由于我只需要根据目标分配变量,我发现我可以使用$(MAKECMDGOALS)变量来正确扩展构建目录。消除$(CONFIG)变量并重写Makefile,如下所示完全符合我的需要:

all:
        @echo specify configuration 'debug' or 'release'

OBJS := foo.o bar.o

BUILDDIR := .build/$(MAKECMDGOALS)

TARGET := $(addprefix $(BUILDDIR)/,$(OBJS))

debug: $(TARGET)
release: $(TARGET)

clean:
        rm -rf .build

$(BUILDDIR)/%.o: %.c
        @echo [$(BUILDDIR)/$*.o] should be [$@]
        @mkdir -p $(dir $@)
        $(CC) -c $< -o $@

然后给出正确的结果:

$ make debug
[.build/debug/foo.o] should be [.build/debug/foo.o]
cc -c foo.c -o .build/debug/foo.o
[.build/debug/bar.o] should be [.build/debug/bar.o]
cc -c bar.c -o .build/debug/bar.o
$ make release
[.build/release/foo.o] should be [.build/release/foo.o]
cc -c foo.c -o .build/release/foo.o
[.build/release/bar.o] should be [.build/release/bar.o]
cc -c bar.c -o .build/release/bar.o
$ make debug
make: Nothing to be done for `debug'.
$ make release
make: Nothing to be done for `release'.

如果在命令行中指定了多个目标,这当然会中断(因为$(MAKECMDGOALS)包含一个以空格分隔的列表),但处理这个并不是太大的问题。

答案 2 :(得分:2)

以下是如何在没有MAKECMDGOALS内省的情况下解决问题。问题基本上是您在Makefile中指定的规则构成静态图。特定于目标的分配在规则体的执行期间使用,但在编译期间不使用。

解决方案是获取对规则编译的控制:使用GNU Make的类似宏的结构来生成规则。然后我们完全控制:我们可以将可变材料粘贴到目标,先决条件或配方中。

以下是我的Makefile

版本
all:
        @echo specify configuration 'debug' or 'release'

OBJS := foo.o bar.o

# BUILDDIR is a macro
# $(call BUILDDIR,WORD) -> .build/WORD
BUILDDIR = .build/$(1)

# target is a macro
# $(call TARGET,WORD) -> ./build/WORD/foo.o ./build/WORD/bar.o
TARGET = $(addprefix $(call BUILDDIR,$(1))/,$(OBJS))

# BUILDRULE is a macro: it builds a release or debug rule
# or whatever word we pass as argument $(1)
define BUILDRULE
$(call BUILDDIR,$(1))/%.o: %.c
        @echo [$(call BUILDDIR,$(1))/$$*.o] should be [$$@]
        @mkdir -p $$(dir $$@)
        $$(CC) -c -DMODE=$(1) $$< -o $$@
endef

debug: $(call TARGET,debug)
release: $(call TARGET,release)

# generate two build rules from macro
$(eval $(call BUILDRULE,debug))
$(eval $(call BUILDRULE,release))

clean:
        rm -rf .build

现在,请注意优势:我可以一次构建debugrelease个目标,因为我已经从模板中实例化了两个规则!

$ make clean ; make debug release
rm -rf .build
[.build/debug/foo.o] should be [.build/debug/foo.o]
cc -c -DMODE=debug foo.c -o .build/debug/foo.o
[.build/debug/bar.o] should be [.build/debug/bar.o]
cc -c -DMODE=debug bar.c -o .build/debug/bar.o
[.build/release/foo.o] should be [.build/release/foo.o]
cc -c -DMODE=release foo.c -o .build/release/foo.o
[.build/release/bar.o] should be [.build/release/bar.o]
cc -c -DMODE=release bar.c -o .build/release/bar.o

此外,我已经自由地将宏参数添加到cc命令行中,以便模块接收一个MODE宏,告诉他们如何编译它们。

我们可以使用变量间接来设置不同的CFLAGS或其他。看看如果我们修补上述内容会发生什么:

--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,9 @@

 OBJS := foo.o bar.o

+CFLAGS_debug = -O0 -g
+CFLAGS_release = -O2
+
 # BUILDDIR is a macro
 # $(call BUILDDIR,WORD) -> .build/WORD
 BUILDDIR = .build/$(1)
@@ -17,7 +20,7 @@ define BUILDRULE
 $(call BUILDDIR,$(1))/%.o: %.c
        @echo [$(call BUILDDIR,$(1))/$$*.o] should be [$$@]
        @mkdir -p $$(dir $$@)
-       $$(CC) -c -DMODE=$(1) $$< -o $$@
+       $$(CC) -c $$(CFLAGS_$(1)) -DMODE=$(1) $$< -o $$@
 endef

 debug: $(call TARGET,debug)

执行命令

$ make clean ; make debug release
rm -rf .build
[.build/debug/foo.o] should be [.build/debug/foo.o]
cc -c -O0 -g -DMODE=debug foo.c -o .build/debug/foo.o
[.build/debug/bar.o] should be [.build/debug/bar.o]
cc -c -O0 -g -DMODE=debug bar.c -o .build/debug/bar.o
[.build/release/foo.o] should be [.build/release/foo.o]
cc -c -O2 -DMODE=release foo.c -o .build/release/foo.o
[.build/release/bar.o] should be [.build/release/bar.o]
cc -c -O2 -DMODE=release bar.c -o .build/release/bar.o

最后,我们可以将其与MAKECMDGOALS结合起来。我们可以检查MAKECMDGOALS并过滤掉那里没有指定的构建模式。如果调用了make release,我们就不需要展开{​​{1}}规则。补丁:

debug

请注意,我通过将--- a/Makefile +++ b/Makefile @@ -3,6 +3,11 @@ OBJS := foo.o bar.o +# List of build types, but only those mentioned on command line +BUILD_TYPES := $(filter $(MAKECMDGOALS),debug release) + +$(warning "generating rules for BUILD_TYPES := $(BUILD_TYPES)") + CFLAGS_debug = -O0 -g CFLAGS_release = -O2 @@ -17,18 +22,15 @@ TARGET = $(addprefix $(call BUILDDIR,$(1))/,$(OBJS)) # BUILDRULE is a macro: it builds a release or debug rule # or whatever word we pass as argument $(1) define BUILDRULE +$(1): $(call TARGET,$(1)) $(call BUILDDIR,$(1))/%.o: %.c @echo [$(call BUILDDIR,$(1))/$$*.o] should be [$$@] @mkdir -p $$(dir $$@) $$(CC) -c $$(CFLAGS_$(1)) -DMODE=$(1) $$< -o $$@ endef -debug: $(call TARGET,debug) -release: $(call TARGET,release) - -# generate two build rules from macro -$(eval $(call BUILDRULE,debug)) -$(eval $(call BUILDRULE,release)) +$(foreach type,$(BUILD_TYPES),\ + $(eval $(call BUILDRULE,$(type)))) clean: rm -rf .build debug:目标滚动到release:宏来简化了事情。

BUILDRULE