Makefile GNU make中的错误:特定于目标的变量未在隐式规则中展开?

Makefile GNU make中的错误:特定于目标的变量未在隐式规则中展开?,makefile,gnu,gnu-make,Makefile,Gnu,Gnu Make,我一直在设计一个多配置Makefile(一个支持单独的“调试”和“发布”目标的文件),并且遇到了一个奇怪的问题,这似乎是GNU make中的一个bug 当在隐式规则中引用特定于目标的变量时,gnumake似乎没有正确地扩展这些变量。下面是一个简化的Makefile,它显示了这个问题: all: @echo specify configuration 'debug' or 'release' OBJS := foo.o bar.o BUILDDIR = .build/$(CONFIG)

我一直在设计一个多配置Makefile(一个支持单独的“调试”和“发布”目标的文件),并且遇到了一个奇怪的问题,这似乎是GNU make中的一个bug

当在隐式规则中引用特定于目标的变量时,gnumake似乎没有正确地扩展这些变量。下面是一个简化的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 $@
这表明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。所有人都表现出相同的行为

我发现很难相信我会是第一个遇到这个问题的人,所以我猜我可能只是做错了什么——但老实说,我不明白我怎么会这样


是否有任何制造大师能够为这个问题提供一些线索?谢谢

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

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

“目标配方的上下文”指的是命令,而不是prereq:

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

有两种方法可以解决这个问题。如果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 $@ 
DEBUG_OBJS=$(addprefix$(BUILDDIR)/DEBUG/,$(OBJS))
RELEASE_OBJS=$(addprefix$(BUILDDIR)/RELEASE/,$(OBJS))
调试:%:$(调试对象)
发布:$(发布对象)
$(DEBUG_OBJS):$(BUILDDIR)/DEBUG/%.o:%.cc
$(RELEASE_OBJS):$(BUILDDIR)/RELEASE/%.o:%.cc
$(调试对象)$(发布对象):
@回声从$^
@mkdir-p$(dir$@)
$(抄送)-c$<-o$@

正如Beta所指出的,这确实不是make中的一个bug,因为文档中描述了限制(我想我一定错过了那个特定部分——对不起)

无论如何,我实际上能够通过做一些更简单的事情来解决这个问题。因为我所需要的只是根据目标分配一个变量,所以我发现我可以使用该变量来正确地扩展构建目录。消除$(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 $@

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

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

解决方法是控制规则编译:使用gnumake的类似宏的构造来生成规则。然后我们就有了完全的控制权:我们可以将可变材料粘贴到目标、先决条件或配方中

这是我的
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
此外,我还随意将宏参数添加到
cc
命令行中,以便模块接收一个
模式
宏,该宏告诉模块如何编译

我们可以使用变量间接设置不同的
cflag
或任何东西。观察如果我们像这样修补上面的问题会发生什么:

--- 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)
最后,我们可以将其与
MAKECMDGOALS
相结合。我们可以检查
MAKECMDGOALS
并过滤掉其中未指定的构建模式。如果调用了
makerelease
,则不需要扩展
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),我可能会同意你的建议,但实际上我们有六个不同的配置目标——在Makefile中复制所有这些目标将相当混乱,但绝对不是不可能的。至于二次扩展,它似乎从make-3.81开始就得到了支持,但当我尝试使用它时,make会挂起,似乎卡在一个无限循环中。当我发现$(MAKECMDGOALS)变量时,我就放弃了找出问题所在的尝试。再次感谢!非常有用的信息--不值得在没有任何追加投票的情况下保留。。。请您简要解释一下二次扩展是如何解决这个问题的好吗?这都有点重复,但是重复的位可以被抽象成一个Make函数,每个所需的配置调用一次。可能的重复谢谢。这个答案是错误的。我的第一个50悬赏是错误的答案!然后我给了100分。
$ 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
--- 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
--- 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
$ make clean ; make release
Makefile:9: "generating rules for BUILD_TYPES := "
rm -rf .build
Makefile:9: "generating rules for BUILD_TYPES := release"
[.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

$ make clean ; make release debug
Makefile:9: "generating rules for BUILD_TYPES := "
rm -rf .build
Makefile:9: "generating rules for BUILD_TYPES := debug release"
[.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
[.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