在规则执行时定义make变量

292

在我的GNUmakefile中,我想要一个使用临时目录的规则。例如:

out.tar: TMP := $(shell mktemp -d)
        echo hi $(TMP)/hi.txt
        tar -C $(TMP) cf $@ .
        rm -rf $(TMP)

如上所述,当前规则的编写方式会在解析规则时创建临时目录。这意味着,即使我不总是制作out.tar,也会创建许多未使用的临时目录。 我想避免我的/tmp被无用的临时目录填满。

有没有办法使变量仅在规则被触发时被定义,而不是在定义它时就立即生效?

我的主要想法是将mktemp和tar转储到shell脚本中,但这似乎有些难看。

4个回答

457
在你的示例中,当评估 out.tar 的规则时,TMP 变量被设置(并创建了临时目录)。为了仅在实际触发 out.tar 时创建目录,需要将目录创建移动到步骤中:
out.tar : 
    $(eval TMP := $(shell mktemp -d))
    @echo hi $(TMP)/hi.txt
    tar -C $(TMP) cf $@ .
    rm -rf $(TMP)

eval函数会将一个字符串作为手动输入到makefile中的命令解析。在这种情况下,它会将TMP变量设置为shell函数调用的结果。

编辑(回应评论):

要创建一个唯一的变量,您可以执行以下操作:

out.tar : 
    $(eval $@_TMP := $(shell mktemp -d))
    @echo hi $($@_TMP)/hi.txt
    tar -C $($@_TMP) cf $@ .
    rm -rf $($@_TMP)

这将在变量前缀添加目标的名称(在本例中为 out.tar),从而生成一个名为 out.tar_TMP 的变量。希望这足以避免冲突。


55
注意这个解决方案!$(eval $@_TMP := $(shell mktemp -d))会在Makefile首次评估时发生,而不是按规则程序顺序执行。换句话说,$(eval ...)比你想象的要早。虽然对于这个例子来说可能没问题,但这种方法会给某些连续操作造成问题。 - JamesThomasMoon
3
@JamesThomasMoon1979,你知道如何克服这些限制吗?例如,对一些变量进行评估,这些变量应该是之前执行规则步骤的结果。 - Vadim Kotov
2
回答自己的问题:对我来说解决方法是在第一条规则执行期间创建文件,并尝试在第二条规则的第一步中对它们进行评估。 - Vadim Kotov
2
@JamesThomasMoon1979,如果在这个Makefile中运行make,似乎在第一次评估Makefile时不会执行。换句话说,文件夹不会像预期的那样通过make创建,只有通过make TEST才能创建。https://gist.github.com/jsign/c7313f359daa99c02bafb222becacad3 - Ignacio Hagopian
如果你想让声明的变量在下一个连续的目标中可访问,你可以使用 @$(eval export TMP = : $(shell mktemp -d)) - undefined
显示剩余3条评论

116

相对容易的方法是将整个序列编写为一个shell脚本。

out.tar:
   set -e ;\
   TMP=$$(mktemp -d) ;\
   echo hi $$TMP/hi.txt ;\
   tar -C $$TMP cf $@ . ;\
   rm -rf $$TMP ;\

我在这里整理了一些相关的提示:在makefile中使用多行bash命令


6
这绝对是最简单且最佳的答案(避免使用@eval,并完成相同的工作)。请注意,在您的输出中,您会看到$TMP(例如,tar -C $TMP ...),尽管该值已经正确传递到命令中。 - Kalle Richter
这就是通常的做法;我希望人们能看到这个答案,因为在实践中,没有人会费尽心思去完成被接受的那个。 - smheidrich
1
如果您正在使用特定于Shell的命令,那么这些命令必须与调用make的Shell语言相同,对吗?我曾经看到一些带有shebang的makefile。 - ptitpion
1
@ptitpion: 你也许想在你的makefile中加入 SHELL := /bin/bash 来启用BASH特定功能。 - Brent Bradburn

43

另一种可能性是在规则触发时使用单独的行来设置Make变量。

例如,下面是一个带有两个规则的makefile。如果一个规则被触发,它将创建一个临时目录并将TMP设置为临时目录名称。

PHONY = ruleA ruleB display

all: ruleA

ruleA: TMP = $(shell mktemp -d testruleA_XXXX)
ruleA: display

ruleB: TMP = $(shell mktemp -d testruleB_XXXX)
ruleB: display

display:
    echo ${TMP}

运行代码会产生预期的结果:

$ ls
Makefile
$ make ruleB
echo testruleB_Y4Ow
testruleB_Y4Ow
$ ls
Makefile  testruleB_Y4Ow

提醒一下,GNU make有一个仅限顺序的先决条件语法,可能需要补充这种方法。 - anol
19
注意这个解决方案!ruleA:TMP = $(shell mktemp -d testruleA_XXXX)ruleB:TMP = $(shell mktemp -d testruleB_XXXX)将在首次评估Makefile时发生。换句话说,ruleA:TMP = $(shell ...比您想象的更早发生。虽然这对于这种特定情况可能有效,但这种方法会为某些连续操作带来问题。 - JamesThomasMoon
1
这不是我的经验 - 规则本地变量仅在请求特定规则时扩展(显式或作为依赖项)。 - Zaar Hai
1
如果您执行 ruleA: private TMP = ...,这仍然可以正常工作。请参阅目标特定变量 - Victor Sergienko

7

我不喜欢“不要”回答,但是...不要。

make的变量是全局的,应该在makefile的“解析”阶段进行评估,而不是在执行阶段。

在这种情况下,只要变量局限于单个目标,请遵循@nobar的回答,将其设置为shell变量。

其他make实现也认为特定于目标的变量具有危害性:katiMozilla pymake。由于这些变量存在,一个目标可以根据它是作为独立构建的目标还是作为父级目标的依赖项中的目标来构建。 你不会知道它是哪种方式,因为你不知道已经构建了什么。


1
即使局部变量真正是局部的,也就是说它们在我们执行的规则下面是不可见的,但它们仍然非常有用。 - Attila Lendvai
1
在回答时,我不知道你可以使目标特定变量私有。这肯定使它们更安全,但我仍然不鼓励使用它们。 - Victor Sergienko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接