如何声明一个只计算一次的延迟变量?



我有一个需要很长时间才能完成的shell程序。如前所述,执行make build需要 4 x 2 秒才能完成,因为为每个文件计算$(value)

解决方案是使用:=而不是=来声明valuedeferred变量。

不幸的是,这也不是解决方案,因为它会使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

有关详细信息,另请参阅文档。

最新更新