我一直在设计一个多配置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进行了测试。所有人都表现出相同的行为。
我发现很难相信我会是第一个遇到这个问题的人,所以我猜我可能只是做错了 - 但我会说实话:我不知道我是怎么回事可能。 :)
那里是否有任何制作大师可以对这个问题有所了解?谢谢!
答案 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
现在,请注意优势:我可以一次构建debug
和release
个目标,因为我已经从模板中实例化了两个规则!
$ 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