【问题标题】:Testing the eval function of GNU make测试 GNU make 的 eval 函数
【发布时间】:2017-07-25 06:34:14
【问题描述】:

GNU make 手册说 eval 函数扩展参数,然后将扩展结果提供给 make 解析器。以下内容引用自 GNU make 手册。

The argument to the eval function is expanded, then the results of that expansion are parsed as makefile syntax. 

我不太明白 make 解析器如何处理 eval 输入的文本,所以 我写了下面的makefile来测试。

define myprint
    echo "this is a line"
endef

goal:
    $(eval $(call myprint))
    gcc -o goal test.c

我知道myprint的正确调用应该只使用调用函数:$(call myprint)并在回显之前删除'Tab'字符。我以这种形式编写makefile只是为了测试eval函数。

我的期望:首先eval函数扩展myprint,这是一个回显命令,前面有一个'Tab','Tab'用于使扩展文本成为合法的配方。然后 eval 将扩展的文本提供给 maker 解析器,后者将识别文本为配方并运行它。由于该命令是合法的,makefile 应该可以正常运行。

但是,我遇到了这样一个错误:

Makefile:6: *** recipe commences before first target.  Stop.

有人能解释一下为什么 make 会产生这样的错误吗?

【问题讨论】:

    标签: makefile


    【解决方案1】:

    该扩展的结果被解析为 makefile 语法

    您对eval 的使用不同:您希望将其解析为shell 语法。你可以写:

    define myprint
    echo "this is a line"
    endef
    
    goal:
        $(myprint)
        gcc -o goal test.c
    

    或:

    define myprint
    echo "this is a $(1)"
    endef
    
    goal:
        $(call myprint,line)
        gcc -o goal test.c
    

    因为在 make 扩展之后,配方是有效的 shell 语法。但不是你写的,因为 eval 的扩展仍然被解释为 make 语法,而不是 shell。为了说明evalcall 的典型用法,请考虑以下内容:

    define myprint
    echo "this is a $(1)"
    endef
    
    define mygoal
    $(1):
        $$(call myprint,line)
        gcc -o $(1) $(2).c
    endef
    $(eval $(call mygoal,goal,test))
    

    这比第一个示例(没有eval)要复杂一些,但它说明了eval 的真正目的:以编程方式实例化 make 构造。下面是它的工作原理,一步一步:

    在其两阶段算法的第一阶段,make扩展$(eval...函数调用,即:

    1. 扩展$(eval...函数的参数($(call...函数):
      1. 扩展$(call...函数的参数(goaltest)。在我们的例子中没有影响。
      2. 将结果分配给临时变量$(1)$(2)
      3. 在此上下文中扩展mygoal 变量,将$(1)$(2)$$(call... 分别替换为goaltest$(call...
    2. 将结果实例化(在内存中)作为一个 make 构造,在这种情况下是一个完整的规则:

      goal:
          $(call myprint,line)
          gcc -o goal test.c
      

    第一阶段继续,但对这个实例化规则没有影响,因为配方在 第二 阶段由 make 扩展。

    在第二阶段,到了构建goal目标的时候,make会在执行之前扩展recipe,即:

    1. 展开$(call myprint...参数(line,无效)。
    2. 将结果赋值给临时参数$(1)
    3. 在此上下文中扩展变量myprint,生成:

      echo "this is a line"
      

    因此,所有这些都与我们编写规则一样:

    goal:
        echo "this is a line"
        gcc -o goal test.c
    

    注意mygoal的初始定义中的双$$

    重要的是要意识到 eval 参数被扩展了两次; 首先是 eval 函数,然后扩展的结果是 当它们被解析为 makefile 语法时再次展开。这意味着你 可能需要为“$”字符提供额外的转义级别 使用评估。

    【讨论】:

    • 感谢您的详细回复和示例。我想我明白了让我感到困惑的一点:在参数的第一次扩展之后,结果将作为常规 make 语法发送到 make 解析器。我认为以“Tab”开头的 echo 命令不是常规语法。这就是为什么我的makefile 不能正常运行的原因。我的理解对吗?
    • 不,不完全是。问题不在于标签。自己试试,去掉这个前导标签。你仍然会得到一个错误,因为最后,$(eval $(call myprint)) 扩展为 make 语句,而它应该是普通的 shell。这与您将 CFLAGS := -g 放入配方中的问题相同:在需要 shell 的地方生成语法。
    • 对不起,我不知道如何比弗洛里安已经写的更好地解释这一点。尝试再次阅读这部分文档并进行思考: 扩展 eval 函数的参数,然后将该扩展的结果解析为 makefile 语法。扩展的结果可以定义新的 make 变量、目标、隐式或显式规则等。 坚持,也许,as makefile 语法
    • 抱歉我的删除行为。我不熟悉如何在 cmets 中编写代码。以下是我之前的cmets。您的意思是问题在于 eval 的来源应该是 make 语法吗?根据您的建议,我尝试遵循 makefile,并且它有效。 [定义 mydef a = test.c endef 目标:$(eval $(call mydef)) echo $(a)​​ gcc –o 目标 test.c]
    • eval 有一点需要理解:它返回空字符串。您尝试的方法有效,因为如果您将$(eval... 替换为空字符串(但保留前导选项卡),并在其他位置添加a = test.c,您的makefile 仍然有效。您在配方的上下文中定义了一个 make 变量,这有点奇怪,但显然 GNU make 可以容忍这一点。相反,如果您尝试定义另一个完整的 make 构造,如规则(目标+先决条件+配方),它不起作用,因为您不能在配方的上下文中使用它。
    【解决方案2】:

    $(eval …) 需要一个语法完整的 makefile 片段。它不能用于将标记粘贴到其他 makefile 结构中。可能手册解释得不够清楚,但它是通过阅读它的参数来实现的,就好像它是一个包含的 makefile。

    【讨论】:

    • 谢谢,你的回答很清楚。以“Tab”开头的 echo 命令在语法上不完整。
    【解决方案3】:

    @RenaudPacalet,我写了下面的makefile来测试调用的扩展是否'吃'一美元。

    define myprint
    echo "In my print $$(ls)"
    endef
    
    goal:
        $(call myprint)
        $(info $(call myprint))
        gcc -o goal test.c
    

    它的输出是:

    echo "In my print $(ls)"
    echo "In my print $(ls)"
    In my print call.mk ... (files list)
    

    由于$(call myprint)正确输出"In my print $(ls)",必须先扩展为echo "In my print $$(ls)",再扩展为正确的shell命令echo "In my print $(ls)"。所以我认为调用函数不会“吃掉”一美元。

    另一个证据是 info 函数的输出。 GNU make 手册说:

    $(info text…)
    This function does nothing more than print its (expanded) argument(s) to standard output. 
    

    从手册中我们可以推断出 make 会扩展 info 函数的参数。由于信息的输出是echo "In my print $(ls)",所以展开前的参数应该是echo "In my print $$(ls)"。因此我们可以得出结论,调用函数不会“吃掉”一美元。

    【讨论】:

    • 你显然不明白的是展开是递归的。当 make 运行 goal 配方时,它会逐行扩展它。扩展$(info $(call myprint))时,首先扩展$(info的参数,即$(call myprint)。在扩展$(call myprint) 时,它首先扩展myprint(无效),然后扩展myprint 的值并吃掉一个$。结果echo "In my print $(ls)"$(info... 的最终扩展参数,因此在make 评估它时$(info... 打印的内容。注意:您不必将info 放入配方中。
    • 只要把$(info $(call myprint))放在你的Makefile中,你会看到$(call myprint)扩展的结果,一个$吃掉了。 info 在标准输出上输出的内容正是使用$(eval $(call myprint)) 时传递给$(eval... 的内容。唯一的区别是$(info... 只是将它发送到标准输出,而$(eval... 评估它并因此将其实例化为一个 make 构造。这是一次有趣的讨论。
    • 如果$(eval ...的扩展参数是"In my print $(ls)"(一美元),我认为make不会正常运行,因为make会将$(ls)扩展为null,而这个命令无法正常运行。我觉得扩容到$(eval... or $(info...后的参数应该有两块钱。
    • @RenaudPacalet 最后我明白了我对扩展的误解:我误解了$call 的返回值将被$eval 再次扩展。实际上没有这样不必要的步骤。再次感谢您的详细解释。
    猜你喜欢
    • 2012-11-02
    • 2011-12-20
    • 2010-12-17
    • 1970-01-01
    • 1970-01-01
    • 2019-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多