我有一个 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 之间的区别。
哇,你真的跳进了泳池的最深处。让我们回到浅水区一分钟,然后先上一两节课。
让我们把注意力集中到这部分:
$(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
然后它就会如你所期望的那样工作。