我有一个需要很长时间才能完成的shell程序。如前所述,执行make build
需要 4 x 2 秒才能完成,因为为每个文件计算$(value)
。
解决方案是使用:=
而不是=
来声明value
deferred
变量。
不幸的是,这也不是解决方案,因为它会使make clean
和任何其他目标的执行速度减慢 2 秒,因为value
是无偿计算的。
value = $(shell sleep 2 && echo 42)
in = a b c d
out = $(addsuffix .out,$(in))
build: $(out)
%.out: %
echo $(value) > $< || [ rm $@ -a true ]
init:
touch $(in)
clean:
rm -vf $(out)
如何设置仅在使用时才分配的变量,但只计算一次?
换句话说,我希望build
花 2 秒钟来完成,clean
立即完成。
我对涉及条件的解决方案不感兴趣,以便在目标未build
的情况下绕过value
的分配.
另一种解决方案是这样的。不幸的是,在这种情况下,我需要检查是否需要重新生成shelve
文件。
value = $(cat shelve)
shelve:
sleep 2 && echo 42 > $@ || [ rm $@ -a true ]
in = a b c d
out = $(addsuffix .out,$(in))
build: $(out)
%.out: %
echo $(value) > $< || [ rm $@ -a true ]
init:
touch $(in)
clean:
rm -vf $(out)
这里有一个你可以玩的技巧:
value = $(eval value := $(shell cat shelve))$(value)
工作原理:首先使用递归赋值分配value
,因此不会扩展 RHS 上的值。
第一次展开value
时,make 解析器将首先运行$(eval ...)
,该 为 makefile 启动一个"新解析器"。 在该分析器中,将评估内容value := $(cat shelve)
。 在这里,value
是一个简单的变量赋值,因此 RHS 会立即展开,$(shell ...)
运行并分配给value
。
请记住,make 实际上并没有变量作用域的概念,因此此value
只是我们在外部解析器中设置的相同全局value
变量。
然后 eval 完成并扩展到空字符串,并继续解析内容。 在这里,它找到$(value)
的值并扩展它......value
现在有来自 eval 的结果,而不是评估文本本身,所以这就是将要扩展的内容。
也许这会有所帮助:
value = $(eval value := $(shell cat shelve))$(value)
此处value
包含字符串$(eval value := $(shell cat shelve))$(value)
现在你展开它:
%.out: %
echo $(value) > $< ...
制作开始扩展此食谱。 它到达$(value)
并看到它需要扩展变量value
:因为它是递归的,所以它扩展了值:
$(eval value := $(shell cat shelve))$(value)
首先,它展开eval
,解析如下:
value := $(shell cat shelve)
这会将value
变量设置为简单展开的变量,因此 RHS 会立即展开。 假设cat shelve
的结果是"foo",所以value
现在设置为foo
(并且标记为只是展开)。
这就是 eval 的结束,所以 make 开始下一部分是$(value)
,所以它查找变量value
并发现它是一个值为foo
的简单扩展变量。
一种解决方案是将该值转换为常规文件目标,该目标仅在其先决条件更改时更新。如果您坚持为每个构建重新构建该目标,请将其标记为虚假。
当目标不依赖于该文件clean
则在调用make clean
时不会重建该文件。
在
%.out: %
echo $(value) > $< || [ rm $@ -a true ]
echo $(value) > $<
更新先决条件,而make
期望它仅更新目标。更新先决条件必须由单独的规则完成,该先决条件是目标。
您可以使赋值依赖于$(MAKECMDGOALS)
中的目标名称:
ifneq ($(MAKECMDGOALS),clean)
value := $(shell sleep 2 && echo 42)
endif
有关详细信息,另请参阅文档。