为何在Makefile中导出含shell命令的变量耗时极长?
问题描述
我的测试Makefile内容如下:
VERSION ?= $(shell cat VERSION | grep "VERSION" | cut -d'=' -f2) VERSION_MAJOR ?= $(shell echo $(VERSION) | cut -d'.' -f1) VERSION_MINOR ?= $(shell echo $(VERSION) | cut -d'.' -f2) VERSION_PATCH ?= $(shell echo $(VERSION) | cut -d'.' -f3) DATE ?= "$(shell date +"%Y-%m-%dT%H:%M")" SOME_NESTED ?= "-X=aaa=$(VERSION_MAJOR) -X=bbb=$(VERSION_MINOR) -X=ccc=$(VERSION_PATCH) -X=ddd=$(DATE)" #.EXPORT_ALL_VARIABLES: t: echo "Test $(SOME_NESTED)"
测试用的VERSION文件通过以下命令生成:
echo "VERSION=1.2.3" > VERSION
原本Makefile运行正常,但取消注释.EXPORT_ALL_VARIABLES后,执行make t的耗时从约0.02秒骤增至近5秒。用make -d t查看日志时,发现大量递归展开相关信息,且命令显示后实际输出有明显延迟。我的Make版本是GNU Make 4.4.1,想了解该现象的原因以及是否存在不良用法。
原因分析
.EXPORT_ALL_VARIABLES会让Make把所有变量都导出给后续执行的shell进程,核心问题出在变量的递归展开特性:
- 你定义的
VERSION_MAJOR、VERSION_MINOR、VERSION_PATCH都是依赖VERSION的递归展开变量,SOME_NESTED又依赖这三个变量和DATE,而DATE本身也是带shell函数的递归展开变量。 - 启用
.EXPORT_ALL_VARIABLES后,Make需要为每一个要导出的变量完成完整递归展开,确保传递给shell的是最终值。这些变量里嵌套了多个shell函数,每一次展开都会触发新的shell进程执行命令(比如cat VERSION、date等)。 - 递归展开还会导致变量被多次重复展开:比如展开
SOME_NESTED时会触发VERSION_MAJOR的展开,VERSION_MAJOR又会触发VERSION的展开,VERSION再执行一次shell命令。大量重复的shell调用叠加后,就导致了耗时剧增。
不良用法与优化建议
避免无差别导出所有变量:除非确实需要把所有变量传递给子进程,否则不要用
.EXPORT_ALL_VARIABLES,而是用export单独指定需要导出的变量,比如只导出SOME_NESTED:export SOME_NESTED改用简单展开变量(
:=)替代递归展开(=):对于不需要延迟展开的变量,用:=定义,这样变量会在定义时就完成一次性展开,避免后续重复触发shell命令。修改后如下:VERSION := $(shell cat VERSION | grep "VERSION" | cut -d'=' -f2) VERSION_MAJOR := $(shell echo $(VERSION) | cut -d'.' -f1) VERSION_MINOR := $(shell echo $(VERSION) | cut -d'.' -f2) VERSION_PATCH := $(shell echo $(VERSION) | cut -d'.' -f3) DATE := "$(shell date +"%Y-%m-%dT%H:%M")" SOME_NESTED := "-X=aaa=$(VERSION_MAJOR) -X=bbb=$(VERSION_MINOR) -X=ccc=$(VERSION_PATCH) -X=ddd=$(DATE)"这样即使启用
.EXPORT_ALL_VARIABLES,变量也只会展开一次,不会重复执行shell命令,耗时会大幅降低。简化shell命令:如果VERSION文件里只有一行
VERSION=xxx的内容,提取VERSION的命令可以简化,去掉多余的grep:VERSION := $(shell cut -d'=' -f2 VERSION)
内容的提问来源于stack exchange,提问作者LingSamuel
相关产品推荐
相关产品推荐

