程序员

Makefile:一个空格引发的惨案

2019-10-17  本文已影响0人  Mum_Chen

从一个bug说起

最早发现makefile关于空格的"特性"是源于一个bug. 在某个项目中, 我需要大量拷贝相同的变量, 所以实现了一个clone的方法, 用于实现把src_xxx的值拷贝到dest_xxx.

我仿照当时的情况写了一个精简的版本. 代码如下:

# makefile
src_int = 10
src_char = "char"

# clone attributes from src to dest.
# arg1: destTarget
# arg2: srcTarget
# arg3: attr_list
define Clone
$(foreach att,$(3), $(eval $(1)_$(att) := $($(2)_$(att))))
endef

$(call Clone,targetA,src,int char)
$(call Clone, targetB, src, int char)
all: targetA targetB

targetA:
    @echo "targetA: int=$(targetA_int) char=$(targetA_char)"

targetB:
    @echo "targetB: int=$(targetB_int) char=$(targetB_char)"

运行后的结果如下:

$ make all
targetA: int=10 char=char
targetB: int= char=

从结果看到targetA克隆成功了, 但是targetB没有.

为什么会发生这个

其实问题的根源就在于脚本中的这两行:

$(call Clone,targetA,src,int char)
$(call Clone, targetB, src, int char)

targetB的克隆比targetA多了几个空格. Makefile的字符串替换包括空格.
在Clone函数中的$(eval $(1)_$(att) := $($(2)_$(att))))这一段代码里的
$($(2)_$(att)))在两种情况下被解析成为了不同变量.

对于targetA而言: $($(2)_$(att)))被解析为了$(src_$(att)).
对于targetB而言: $($(2)_$(att)))被解析为了$( src_$(att)).

后者比前者多了一个空格, 在makefile的上下文中找不到对应的变量, 所以赋值失败.

当时, 由于习惯使然, 我在逗号后面随手加入了一个空格. 然后我就发现我的构建脚本不能正常运行了.

如何解决

解决方案1: 改变逗号后面加空格的习惯.

然而这种方案过于反人类, 所以我放弃了.(但是修改起来比较快)
考虑到我就是一个写bug的, 不能这么严格的要求自己, 所以我找到了方案2.

解决方案2: 使用strip函数进行变量的处理.

修改如下:

define Clone
$(foreach att,$(3), $(eval $(1)_$(att) := $($(2)_$(att))))
endef

define CloneFix
$(foreach att,$(3), $(eval $(strip $(1))_$(att) := $($(strip $(2))_$(att))))
endef

附录

完整的代码

src_int = 10
src_char = "char"

# arg1: destTarget
# arg2: srcTarget
# arg3: value_list
define Clone
$(foreach att,$(3), $(eval $(1)_$(att) := $($(2)_$(att))))
endef

define CloneFix
$(foreach att,$(3), $(eval $(strip $(1))_$(att) := $($(strip $(2))_$(att))))
endef

%_print:
    @echo "$*: int=$($*_int) char=$($*_char)"

gap:
    @echo

# arg1: target list
define Entry
$(foreach target,$(1), $(eval $(target): $(target)_print))
endef

$(call Entry, v1_a v1_b v1_c v1_d)
$(call Clone,v1_a,src,int char)     # with not space
$(call Clone,v1_b,src, int char)    # space in arg3(param)
$(call Clone,v1_c, src, int char)   # space in arg2(src)
$(call Clone, v1_d,src, int char)   # space in arg1(dest)

$(call Entry, v2_a v2_b v2_c v2_d)
$(call CloneFix,v2_a,src,int char)      # with not space
$(call CloneFix,v2_b,src, int char) # space in arg3(param)
$(call CloneFix,v2_c, src, int char)    # space in arg2(src)
$(call CloneFix, v2_d,src, int char)    # space in arg1(dest)

.DEFAULT_GOAL := all
all: v1 gap v2
v1: $(addprefix v1_, a b c d)
v2: $(addprefix v2_, a b c d)

运行结果

$ make
v1_a: int=10 char=char
v1_b: int=10 char=char
v1_c: int= char=
v1_d: int=10 char=char

v2_a: int=10 char=char
v2_b: int=10 char=char
v2_c: int=10 char=char
v2_d: int=10 char=char

这个版本不管怎么按空格Clone函数都能被正常的执行.

上一篇下一篇

猜你喜欢

热点阅读