GNU Makefile 为什么在一些自定义函数中需要 eval?

问题描述 投票:0回答:1

我有一个 Makefile 和一个 a/makefile.mk:

.SUFFIXES:

.PHONY: all
all:

bldroot := /tmp/a

# create a recipe
# parameters:
# - $1 source file (.s, .c)
# - $2 source dir
# - $3 build dir
define makerec
  src := $2/$1

  assrc := $(filter %.s,$(src))
  csrc := $(filter %.c,$(src))
  bld := $3/$(assrc:.s=.o)$(csrc:.c=o)

  mkdir := $(mkdir) $(3)
  all: $(bld)

  $(if $(assrc),
$(bld) : $(src) | $(3)
    @echo AS $$@ $$<
  )

  $(if $(csrc),
$(bld) : $(src) | $(3);
    @echo CC $$@ $$<
  )


endef

# include all makefile.mk
# parameters
# - $1 makefile.mk
define inclmk
$(info --- $1)
  $(eval srcdir := $(patsubst %/,%,$(dir $1)))
  $(eval blddir := $(abspath $(bldroot)/$(subst /src/,/bld/,$(srcdir))))
  $(eval srcdir := $(abspath $(srcdir)))
  $(eval srcs :=)
  $(eval include $1)
$(info +++ $(srcdir))
$(info $(srcs))
  $(foreach s,$(srcs),$(eval $(call makerec,$s,$(srcdir),$(blddir))))
endef

# find all mamakefile.mks
rwildcard = $(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) \
  $(filter $(subst *,%,$2),$d))
mkfiles := $(call rwildcard,.,*/makefile.mk)
$(foreach f,$(mkfiles),$(eval $(call inclmk,$f)))

# create build directory structure
mkdir := $(sort $(mkdir))
$(mkdir):
    mkdir -p $@

# clean
.PHONY: clean
clean:
    rm -rf $(bldroot)
srcs := a.s

还有一个空的 a/a.s.

在这样的目录结构中:

.
|-- Makefile
`-- a
    |-- a.s
    `-- makefile.mk

为什么 makeec 函数无需评估即可工作,而 inclmk 则不能? 如果没有 eval,它甚至不会进行变量赋值。

如果

$(info +++ $(srcdir))
没有 eval,则
$(eval srcdir := $(patsubst %/,%,$(dir $1)))
行仅打印加号。 为什么
srcdir := $(patsubst %/,%,$(dir $1))
不起作用?

我无法理解 makeec 和 inclmk 之间的区别。

gnu-make
1个回答
0
投票

哇,你真的跳进了泳池的最深处。让我们回到浅水区一分钟,然后先上一两节课。

让我们把注意力集中到这部分:

$(eval $(call inclmk,$f))

这到底有什么作用?嗯,正如所有 make 事物一样,宏(和函数)是“由内而外”扩展的。这意味着 first

$f
展开,然后
$(call inclmk,xxx.mk)
(如果
$f
展开为
xxx.mk
)展开,然后
$(eval <results-of-call-expansion>)
展开。

因此,我们可以看到,在

eval
获得
inclmk
变量值的内容之前,它已经被
call
扩展了。

让我们举个例子

Makefile

define inclmk
$(info call: $1)
$$(info eval: $1)
endef

$(info expanding call)
callex := $(call inclmk,hi)

$(info expanding eval)
$(eval $(callex))

当您使用此 makefile 运行

make
时,您将得到:

$ make
expanding call
call: hi
expanding eval
eval: hi
make: *** No targets.  Stop.

这里发生了什么?我们首先完成了

call
(使用
:=
,因此它会立即展开),然后我们对结果进行了
eval
。我们可以看到
$(info call: $1)
立即展开,但是
$$(info eval: $1)
直到稍后的
eval
期间才展开。让我们添加一些细节,以便我们可以看到
call
的结果是什么;将此行添加到 makefile 的末尾:

$(info results of call: "$(callex)")

现在您将看到打印的额外输出:

results of call: "
$(info eval: hi)"

$(info call: $1)
去哪儿了?当
call
函数扩展时,它被扩展为打印该输出,所以它消失了。由于我们逃脱了第二个
$$(info eval: $f)
,所以它的扩展是
$(info eval: hi)
,所以我们在输出中看到了这一点。

然后我们运行

eval
,它会打印第二个
info

所以现在让我们看看(部分)你的 makefile 并取出内部的

eval
:

define inclmk
$(info --- $1)
  srcdir := $(patsubst %/,%,$(dir $1))
  blddir := $(abspath $(bldroot)/$(subst /src/,/bld/,$(srcdir)))
  srcdir := $(abspath $(srcdir))
  srcs :=
  include $1
$(info +++ $(srcdir))
$(info $(srcs))
endef

我省略了内心的

foreach
,因为它对我来说太复杂了,现在无法思考。如上所述,您可以分两个阶段使用它:首先将其扩展
call
,然后将扩展的结果扩展
eval
。只有
eval
真正解析 make 语法:
call
是一个简单的文本替换操作,它不知道任何有关变量赋值等的信息。

所以我们假设

$1
的值为
xxx.mk
。那么第一次展开后,使用
call
,结果将是:

  srcdir := xxx.mk
  blddir := 
  srcdir := 
  srcs :=
  include xxx.mk

这是为什么呢?因为扩展全部发生在

call
期间,但分配直到稍后的
eval
期间才会发生。这意味着当 make 扩展
bindir
右侧的值并尝试使用
srcdir
的值时,该变量尚未设置(直到
eval
才会设置)并且是空字符串,因此
blddir
为空。等等

那么你要做什么才能让这项工作成功呢?好吧,答案在我上面的示例中给出了建议:对于您想要

eval
看到的任何宏或函数,您必须从 call
escape
它。简而言之,只有像
call
$1
$2
参数变量才应该在 define 宏中进行
un
转义。其他一切都应该逃脱。所以你应该这样写你的
define

define inclmk
$$(info --- $1)
  srcdir := $$(patsubst %/,%,$$(dir $1))
  blddir := $$(abspath $$(bldroot)/$$(subst /src/,/bld/,$$(srcdir)))
  srcdir := $$(abspath $$(srcdir))
  srcs :=
  include $1
$$(info +++ $$(srcdir))
$$(info $$(srcs))
endef

然后它就会如你所期望的那样工作。

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.