VPATH场景下gmake中需每次执行的源码再生命令的两类问题
问题描述
我有一条会检查foo.cpp并可能再生它的命令,再生基于make无法感知的因素,因此必须每次都执行该命令。libfoo.a依赖于foo.o,而foo.o依赖于foo.cpp。foo.cpp不在当前目录,而是位于VPATH路径../elsewhere中,该VPATH可正常工作:若修改../elsewhere/foo.cpp,foo.o会被重新编译,库也会被重建。
我尝试了如下代码,但存在两个问题:
- 如何让VPATH作用于命令的参数?若将
foo.cpp改为../elsewhere/foo.cpp,除问题2外一切正常。虽然我知道foo.cpp的位置,但有没有更通用的解决方案? - 假设第一次执行make后,时间戳顺序为:源码 < 对象文件 < 库 < 依赖该库的二进制文件。之后触发
foo.cpp再生的条件发生变化,第二次执行make时,命令正确运行并再生了foo.cpp,但它的依赖项未被标记为需要重建,此时时间戳顺序变为:对象文件 < 库 < 二进制文件 < 源码。第三次执行make时,对象文件、库及二进制文件才会按预期重建。
示例代码:
libfoo.a:: checkfile .PHONY: checkfile checkfile: myCommand foo.cpp
解决方案
针对问题1:让VPATH自动定位foo.cpp
可以通过Make的变量组合自动从VPATH中查找foo.cpp的实际路径,无需硬编码目录:
# 从VPATH路径中自动查找foo.cpp的实际位置 FOO_CPP := $(firstword $(wildcard $(addsuffix /foo.cpp,$(VPATH)))) libfoo.a:: checkfile .PHONY: checkfile checkfile: myCommand $(FOO_CPP)
$(addsuffix /foo.cpp,$(VPATH))会给VPATH中的每个路径加上/foo.cpp后缀,$(wildcard)匹配存在的文件,$(firstword)取第一个匹配结果(若VPATH中有多个同名文件,会优先取第一个)。这样不管VPATH如何配置,都能自动定位到目标文件。
针对问题2:强制依赖项随foo.cpp更新立即重建
核心问题是foo.cpp更新后,当前make进程不会重新扫描依赖链,导致后续目标无法在本次执行中重建。可以通过在checkfile中对比foo.cpp的时间戳,若文件被修改则强制标记foo.o为过期:
FOO_CPP := $(firstword $(wildcard $(addsuffix /foo.cpp,$(VPATH)))) FOO_O := foo.o # 明确依赖关系:libfoo.a依赖foo.o,foo.o依赖foo.cpp和checkfile libfoo.a:: $(FOO_O) $(FOO_O): $(FOO_CPP) checkfile .PHONY: checkfile checkfile: $(FOO_CPP) @# 记录命令执行前foo.cpp的时间戳 OLD_TIMESTAMP=$$(stat -c %Y $(FOO_CPP)) ; \ # 执行检查/再生命令 myCommand $(FOO_CPP) ; \ # 记录命令执行后foo.cpp的时间戳 NEW_TIMESTAMP=$$(stat -c %Y $(FOO_CPP)) ; \ # 若时间戳变化,将foo.o的时间戳设为Unix纪元时间(强制过期) if [ $$OLD_TIMESTAMP -ne $$NEW_TIMESTAMP ]; then \ touch -d @0 $(FOO_O) ; \ fi
这段逻辑的作用:
- 将
checkfile设为foo.o的依赖,确保每次构建foo.o前都会执行检查命令 - 对比命令执行前后
foo.cpp的时间戳,若文件被更新,就把foo.o的时间戳设为最旧的纪元时间,让Make在本次执行中直接重新编译foo.o,进而重建libfoo.a和依赖它的二进制文件,无需等到第三次执行。
内容的提问来源于stack exchange,提问作者Swiss Frank
相关产品推荐
相关产品推荐

