首页 文章

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

提问于
浏览
13

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

当隐式规则中引用这些变量时,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 回答

  • 2

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

    6.11 Target-specific Variable Values:"As with automatic variables, these values are only available within the context of a target's recipe (and in other target-specific assignments)."

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

    10.5.3 Automatic variables:"[Automatic variables] cannot be accessed directly within the prerequisite list of a rule."

    有几种方法可以解决这个问题 . 你可以使用Secondary Expansion,如果你的Make GNUMake版本有它(3.81不知道't, I don't知道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 $@
    
  • 9

    正如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)包含一个以空格分隔的列表),这当然会中断,但处理这个并不是太大的问题 .

  • 7

    以下是如何在没有 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
    

相关问题