在规则执行时定义make变量


208

在我的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脚本中,但这似乎有些难看。

Answers:


321

在您的示例中,TMP只要评估了规则out.tar便会设置变量(并创建临时目录)。为了仅在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。希望这足以防止冲突。


2
冷静一些澄清...这不会将TMP限制在这个目标范围内,对吗?因此,如果还有其他规则具有自己的$(TMP)用法(可能与-j并行使用),是否会发生冲突?另外,@ echo甚至有必要吗?似乎您可以不理会它。
Emil Sit

3
似乎可以解决问题(尽管对普通的非制作大师来说有点不透明:-)谢谢!
埃米尔·谢特

29
请谨慎使用此解决方案! $(eval $@_TMP := $(shell mktemp -d))第一次评估Makefile时,将发生错误,而不是按照规则过程的顺序进行。换句话说,$(eval ...)发生比您想象的要早。尽管对于此示例来说可以,但是此方法将导致某些顺序操作出现问题。
JamesThomasMoon1979年

1
@ JamesThomasMoon1979您是否知道如何克服这些限制,例如评估一些应由规则中较早执行的步骤导致的变量?
Vadim Kotov

2
回答我自己的问题:我的解决方法是在执行第一条规则期间创建文件,并尝试在第二条规则的第一步中评估它们。
Vadim Kotov

63

一个相对简单的方法是将整个序列编写为Shell脚本。

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

我在这里整合了一些相关的技巧:https : //stackoverflow.com/a/29085684/86967


3
这绝对是最简单的,因此最佳答案(避免@eval和做同样的工作)。请注意,尽管该值正确传递给了命令,但您在输出中会看到$TMP(例如tar -C $TMP ...)。
卡尔·里希特

通常是这样的。我希望人们能看到这个答案,因为实际上没有人会经历所有被接受的努力。
pmos

如果您使用的是特定于Shell的命令怎么办?在那种情况下,shell命令应使用与调用make的外壳程序语言相同的外壳语言,对吗?我看过一些有关shebang的文件。
ptitpion

@ptitpion:您可能还希望SHELL := /bin/bash在makefile中启用特定于BASH的功能。
nobar

31

另一种可能性是在规则触发时使用单独的行来设置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具有仅订购先决条件的语法,这可能是对这种方法的补充。
酒精2014年

11
请谨慎使用此解决方案! ruleA: TMP = $(shell mktemp -d testruleA_XXXX)并且ruleB: TMP = $(shell mktemp -d testruleB_XXXX)会在首次评估Makefile时发生。换句话说,ruleA: TMP = $(shell ...发生比您想象的要早。尽管这可能适用于此特定情况,但此方法将对某些顺序操作造成问题。
JamesThomasMoon1979年

1

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

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

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

特定于目标的变量也被其他make实现视为有害:katiMozilla pymake。由于这些原因,可以根据是独立构建目标还是将其作为具有特定于目标的变量的父目标的依赖项来构建不同的目标。而且您将不知道它是哪种方式,因为您不知道已经构建了什么。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.