【问题标题】:Bug in GNU make: target-specific variables are not expanded in implicit rules?GNU make 中的错误:特定于目标的变量未在隐式规则中扩展?
【发布时间】:2014-12-05 10:26:48
【问题描述】:

我一直致力于设计一个多配置 Makefile(一个支持单独的“调试”和“发布”目标的文件),并且遇到了一个奇怪的问题,这似乎是 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 $@

指定要制作的目标“调试”时,CONFIG 设置为“调试”,并且 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。都表现出相同的行为。

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

是否有任何制作大师可以阐明这个问题?谢谢!

【问题讨论】:

标签: makefile gnu gnu-make


【解决方案1】:

基本上,Make 会计算出依赖关系的 DAG 并创建一个规则列表,在运行任何规则之前必须运行这些规则。分配特定于目标的值是 Make 在运行规则时所做的事情,稍后会出现。这是一个严重的限制(我和其他人之前曾抱怨过),但我不会称其为错误,因为它在文档中进行了描述。根据 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 $@ 

【讨论】:

  • 首先感谢您的回答!如果我们的构建系统只有两个配置目标(调试和发布),我可能会接受你的建议,但我们实际上有六个不同的配置目标——在 Makefile 中复制所有这些目标会相当混乱,但绝对不是不可能的。至于Secondary Expansion,似乎从make-3.81开始就支持了,但是当我尝试使用它时,make就会挂起,似乎陷入了无限循环。我一发现 $(MAKECMDGOALS) 变量就放弃了试图找出问题所在。再次感谢!
  • 非常有用的信息 -- 不值得在没有任何赞成票的情况下留下......您介意简要解释一下二次扩展如何解决这个问题吗?
  • 这有点重复,但是重复的位可以被抽象成一个 Make 函数,每个所需的配置调用一次。
【解决方案2】:

正如 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) 包含一个以空格分隔的列表),但处理这并不是什么大问题。

【讨论】:

  • 谢谢。这个答案是life saver
  • 我的第一个 50 赏金回答错误!然后我给了100。
【解决方案3】:

这里是如何在没有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,我们不需要扩展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 宏中来简化事情。

$ 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

【讨论】:

    猜你喜欢
    • 2017-09-26
    • 2013-06-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-23
    • 2014-04-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多