【问题标题】:Define make variable at rule execution time在规则执行时定义 make 变量
【发布时间】:2010-12-26 22:07:47
【问题描述】:

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

【问题讨论】:

    标签: makefile gnu-make


    【解决方案1】:

    在您的示例中,每当评估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 函数调用的结果。

    编辑(回应 cmets):

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

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

    这会将目标的名称(在本例中为 out.tar)添加到变量中,从而生成名称为 out.tar_TMP 的变量。希望这足以防止冲突。

    【讨论】:

    • 很酷的一些澄清...这不会将 TMP 限定到这个目标,是吗?因此,如果有其他规则有自己的 $(TMP) 用法(可能与 -j 并行),是否会发生冲突?另外,@echo 甚至是必要的吗?看来你可以把它排除在外。
    • 似乎可以解决问题(尽管对于普通的非 Make 大师来说有点不透明 :-) 谢谢!
    • 小心这个解决方案! $(eval $@_TMP := $(shell mktemp -d)) 将在 Makefile 第一次按规则过程的顺序评估not 时发生。换句话说,$(eval ...) 发生的时间比你想象的要快。虽然对于这个例子来说这可能没问题,但这种方法会导致一些顺序操作出现问题。
    • @JamesThomasMoon1979 你知道如何克服这些限制吗?评估一些应该是规则中先前执行的步骤的结果的变量?
    • 回答我自己的问题:我的解决方法是在第一条规则执行期间创建文件,并尝试在第二条规则的第一步中评估它们。
    【解决方案2】:

    一种相对简单的方法是将整个序列编写为 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

    【讨论】:

    • 这绝对是最简单也是最好的答案(避免@eval 并做同样的工作)。请注意,在您的输出中您会看到$TMP(例如tar -C $TMP ...),尽管该值已正确传递给命令。
    • 正常情况下是这样的;我希望人们能看到这个答案,因为在实践中没有人会经历接受的所有努力。
    • 如果您使用的是特定于 shell 的命令怎么办?在这种情况下,shell 命令应该使用与调用 make 相同的 shell 语言,对吗?我看过一些带有shebang的makefile。
    • @ptitpion:您可能还希望在您的 makefile 中使用 SHELL := /bin/bash 来启用 BASH 特定的功能。
    【解决方案3】:

    另一种可能性是在触发规则时使用单独的行来设置 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 有一个order-only prerequisites 的语法,这可能是补充这种方法所必需的。
    • 小心这个解决方案! ruleA: TMP = $(shell mktemp -d testruleA_XXXX)ruleB: TMP = $(shell mktemp -d testruleB_XXXX) 将在首次评估 Makefile 时发生。换句话说,ruleA: TMP = $(shell ... 发生的时间比你想象的要快。虽然这可能适用于这种特殊情况,但这种方法会导致一些顺序操作出现问题。
    • 这不是我的经验 - 仅当请求特定规则时(显式或作为依赖项)才扩展规则局部变量
    • 如果您执行ruleA: private TMP = ...,这应该仍然有效。见target-specific variables
    【解决方案4】:

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

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

    在这种情况下,只要变量局部于单个目标,跟随@nobar's answer,使其成为一个shell变量。

    目标特定变量也被其他 make 实现认为是有害的:katiMozilla pymake。正因为如此,一个目标可以不同地构建,具体取决于它是独立构建的,还是作为具有目标特定变量的父目标的依赖项。 而且你不会知道它是哪条路,因为你不知道已经建成了什么。

    【讨论】:

    • 局部变量会很有用,即使它们是真正的局部变量,也就是说,它们对于在我们之下执行的规则是不可见的。
    • @AttilaLendvai 在撰写答案时,我不知道you can make target-specific variable private。这肯定会让它们更安全,但我仍然不鼓励使用它们。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-02
    • 1970-01-01
    • 2011-05-06
    • 2021-03-17
    • 2012-01-28
    • 2016-09-04
    相关资源
    最近更新 更多