Makefile中timeout命令执行结果与Bash不一致问题排查
我在Makefile中编写了如下规则:spike_bin由标志控制,取值为timeout 10 /opt/riscv/bin/spike或/opt/riscv/bin/spike。不带timeout的版本可正常运行,但带timeout的版本无法运行。即使设置shell := /bin/bash也无法解决该问题。
执行时返回错误:
timeout 1 /opt/riscv/bin/spike -d --debug-cmd=debug/csr_basic.s.spike.script --log-commits --log=debug/csr_basic.s.raw_golden.log csr_basic.s.riscv make: *** [Makefile:108: debug/csr_basic.s.raw_golden.log] Error 124
错误码124是timeout超时的返回码(如timeout 1 sleep 2; echo $?会返回124),但直接在Bash中运行上述相同命令却能成功完成。请问我遗漏了什么?
解决方案
核心问题是Makefile变量的解析逻辑和Bash不一样:
当你把
spike_bin设为timeout 10 /opt/riscv/bin/spike时,Make不会像Bash那样自动把timeout识别为命令、后面的作为参数,而是可能把整个字符串当作一个带空格的命令名来执行,或者参数传递混乱,导致timeout没有正确接收参数,进而触发超时返回码124。最稳妥的解决办法是拆分变量,把命令和参数分开:
# 用USE_TIMEOUT标志控制是否启用timeout ifeq ($(USE_TIMEOUT),1) TIMEOUT_CMD = timeout 10 SPIKE_CMD = /opt/riscv/bin/spike else TIMEOUT_CMD = SPIKE_CMD = /opt/riscv/bin/spike endif # 规则里分开调用 debug/%.raw_golden.log: %.riscv $(TIMEOUT_CMD) $(SPIKE_CMD) -d --debug-cmd=debug/$*.spike.script --log-commits --log=$@ $<这样Make会分别展开两个变量,shell能正确识别
timeout是命令,后续的10和spike路径都是它的参数。如果一定要把整个命令存在一个变量里,得让shell完整解析这个命令串,比如用
bash -c包裹:SPIKE_BIN = "timeout 10 /opt/riscv/bin/spike" debug/%.raw_golden.log: %.riscv bash -c "$(SPIKE_BIN) -d --debug-cmd=debug/$*.spike.script --log-commits --log=$@ $<"但这种方式容易因为引号转义出问题,不如拆分变量靠谱。
至于直接在Bash里能运行,是因为Bash会自动按空格分割命令和参数,而Make的变量展开逻辑是先把变量内容直接替换到命令行,再交给shell处理,这时候如果变量里包含多个命令/参数,Make的单词分割规则可能和Bash不一致,导致执行异常。
内容的提问来源于stack exchange,提问作者Ömer GÜZEL

