You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用SECONDEXPANSION基于目标特定变量设依赖的GNU Make版本兼容问题

GNU Make SECONDEXPANSION跨版本兼容性问题

我希望基于目标特定变量设置依赖,因此使用SECONDEXPANSION编写了如下测试Makefile:

foo.h:
    touch foo.h
bar.h:
    touch bar.h

.SECONDEXPANSION:
baz.h: $$(HEADER_NAME)
    echo "In baz.h with HEADER_NAME = $(HEADER_NAME)"

foo: HEADER_NAME=foo.h
foo: baz.h
    file $(HEADER_NAME)
bar: HEADER_NAME=bar.h
bar: baz.h
    file $(HEADER_NAME)

clean:
    rm -f *.h

但我的系统上安装了多个GNU Make版本,该Makefile仅在4.4.1版本中正常工作。4.3和4.2.1版本始终不会执行foo.h或bar.h目标(仿佛第二次展开时HEADER_NAME为空)。我是否错误使用了SECONDEXPANSION,仅在v4.4.1中偶然生效?还是早期版本存在问题?我确认这并非新增特性,因为4.2.1版本的文档已包含SECONDEXPANSION。


问题原因

你没有错误使用SECONDEXPANSION,问题出在GNU Make的版本行为差异上:

在GNU Make 4.4之前的版本中,目标特定变量(比如foo: HEADER_NAME=foo.h)的作用域仅限于目标本身及其执行命令,不会传递给依赖规则的二次展开阶段。也就是说,当处理baz.h: $$(HEADER_NAME)时,早期版本无法获取到foo或bar目标定义的HEADER_NAME变量,导致二次展开后依赖为空,因此不会触发foo.h或bar.h的构建。

而GNU Make 4.4改进了目标特定变量的作用域规则,允许它们传递给通过SECONDEXPANSION展开的依赖目标,所以你的写法在4.4.1中能正常工作。

修正方案

如果需要兼容4.3及更早版本,可以调整Makefile的规则结构,确保HEADER_NAME在依赖展开时能被正确识别:

方案1:直接让顶层目标依赖对应头文件

将foo和bar的依赖直接包含对应的头文件,同时保留baz.h的规则:

foo.h:
    touch foo.h
bar.h:
    touch bar.h

.SECONDEXPANSION:
baz.h:
    echo "In baz.h with HEADER_NAME = $(HEADER_NAME)"

foo: HEADER_NAME=foo.h
foo: $$(HEADER_NAME) baz.h
    file $(HEADER_NAME)
bar: HEADER_NAME=bar.h
bar: $$(HEADER_NAME) baz.h
    file $(HEADER_NAME)

clean:
    rm -f *.h

方案2:用eval动态生成规则

通过eval为每个目标生成对应的baz.h依赖规则,确保变量作用域正确:

foo.h:
    touch foo.h
bar.h:
    touch bar.h

define generate_target
$(1): HEADER_NAME=$(1).h
$(1): baz.h-$(1)
	file $$(HEADER_NAME)

baz.h-$(1): $$(HEADER_NAME)
	echo "In baz.h with HEADER_NAME = $$(HEADER_NAME)"
endef

$(foreach t,foo bar,$(eval $(call generate_target,$(t))))

clean:
    rm -f *.h baz.h-*

这两种写法都能在GNU Make 4.2.1、4.3及4.4.1版本中正常工作。


内容的提问来源于stack exchange,提问作者Carcophan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 23:47:35