本题类似于Defining Variables inside macro.
我在 v3.82 和 v4.3 上都试过了。在下面的代码片段中,当我使用宏中的 shell 语句的输出初始化变量时,出现错误。在我的系统上,__LINUX_GCC_MAIN_VERSION 应该初始化为 11,然后我希望在 __LINUX_GCC_V7_OR_LATER 的初始化中使用它。这似乎没有发生,导致 expr.
的语法错误我添加了 $(info) 语句来帮助调试。
LINUX_CC = gcc -m32
define __DECLARE_VERSION_FLAGS_GCC
$(info [1])
__$(2)_GCC_MAIN_VERSION = $(shell $($(2)_CC) -dumpversion | sed -e 's/\..*//')
__$(2)_GCC_V7_OR_LATER = $(shell expr $(__$(2)_GCC_MAIN_VERSION) \>= 7)
$(info [5])
endef
$(info [6])
$(eval $(call __DECLARE_VERSION_FLAGS_GCC,linux,LINUX))
$(info [7])
# This outputs '11' as expected
$(info $(__LINUX_GCC_MAIN_VERSION))
$(info [8])
# This should be the result of "expr 11 >= 7", but the result is "FALSE"
ifeq ($(__LINUX_GCC_V7_OR_LATER),1)
V7 = "TRUE"
else
V7 = "FALSE"
endif
$(info $(V7))
$(info [9])
.PHONY: all
all:
@echo Done
~/temp/tmp$ make -f Makefile3
[6]
[1]
expr: syntax error: unexpected argument ‘7’
[5]
[7]
11
[8]
"FALSE"
[9]
Done
我希望看到的是:
~/temp/tmp$ make -f Makefile3
[6]
[1]
[5]
[7]
11
[8]
"TRUE"
[9]
Done
为什么这没有按预期工作,我需要做什么来纠正它?
调试
eval
问题的正确方法是使用info
。重复与 eval
相同的行,但将 eval
替换为 info
,make 将打印出 eval
将解析的 makefile 部分。
问题,就像曾经存在的几乎所有
eval
问题一样,都没有完全理解扩展是如何工作的。考虑这一行:
$(eval $(call __DECLARE_VERSION_FLAGS_GCC,linux,LINUX))
make 是怎么处理的?与 ALL 扩展一样,它的工作原理是“由内而外”。所以首先最里面的变量被展开,从左向右移动(但这里我们没有任何同一级别的值,所以这无关紧要)。这意味着 make 做的第一件事就是扩展它:
$(call __DECLARE_VERSION_FLAGS_GCC,linux,LINUX)
这里是非常简单的字符串替换:
call
没有 makefile 语法的概念。它只是将第一个参数的值扩展为变量名__DECLARE_VERSION_FLAGS_GCC
,其中$1
设置为linux
,$2
设置为LINUX
。这是如何运作的?变量值为:
$(info [1])
__$(2)_GCC_MAIN_VERSION = $(shell $($(2)_CC) -dumpversion | sed -e 's/\..*//')
__$(2)_GCC_V7_OR_LATER = $(shell expr $(__$(2)_GCC_MAIN_VERSION) \>= 7)
$(info [5])
所以我们开始扩张。请记住,对于
call
,这只是一个字符串。它没有 make 语法的概念。所以首先它展开$(info [1])
并打印出[1]
。然后展开__$(2)_GCC_MAIN_VERSION =
,结果是__LINUX_GCC_MAIN_VERSION =
。然后它扩展 shell 命令,它首先将参数扩展到 shell,它将 $($(2)_CC)
扩展为 gcc -m32
,因此它将运行 shell 命令:
gcc -m32 -dumpversion | sed -e 's/\..*//'
将打印
11
,到目前为止,我们已经将 call
函数扩展为:
__LINUX_GCC_MAIN_VERSION = 11
但至关重要的是,我重复一遍,这只是一个字符串。我们实际上并没有将 make 变量
__LINUX_GCC_MAIN_VERSION
设置为 11
。就品牌而言,这可能是zippity doo dah 11
.
然后我们转到下一行:
__$(2)_GCC_V7_OR_LATER = $(shell expr $(__$(2)_GCC_MAIN_VERSION) \>= 7)
$(info [5])
现在我们以同样的方式展开它,在这里当我们到达
$(__LINUX_GCC_MAIN_VERSION)
时这个变量没有设置,因为我们仍然在call
。所以它扩展为空,shell 运行 expr \>= 7
这是无效的语法,你会得到你看到的错误。
这是一起使用
eval
和 call
的技巧:
作为一般规则,变量的内容应该escape所有
$
除了call
参数($1
,$2
等)
这样,所有那些
$
都通过了call
而不展开,然后eval
将它们展开。所以像这样重写你的定义:
define __DECLARE_VERSION_FLAGS_GCC
$(info [1])
__$(2)_GCC_MAIN_VERSION = $$(shell $$($(2)_CC) -dumpversion | sed -e 's/\..*//')
__$(2)_GCC_V7_OR_LATER = $$(shell expr $$(__$(2)_GCC_MAIN_VERSION) \>= 7)
$(info [5])
endef
(是否逃脱
$(info ...)
由您决定,因为它对扩展结果没有影响)。
现在在调用之后,shell 等还没有运行,你将把这个字符串传递给
eval
:
__LINUX_GCC_MAIN_VERSION = $(shell $(LINUX)_CC) -dumpversion | sed -e 's/\..*//')
__LINUX_GCC_V7_OR_LATER = $(shell expr $(__LINUX_GCC_MAIN_VERSION) \>= 7)
然后它将起作用:eval 将首先设置
__LINUX_GCC_MAIN_VERSION
变量,然后当它到达第二行时将被设置。