我有一些软件项目作为RPM分发。它们使用semantic versioning进行版本化,我们在其中添加版本号。使用常规约定,这是 MAJOR.MINOR.PATCH-REL_NUM 。虽然超出了本文的范围,但是版本号存储在git中。 makefile
中的发布目标看起来像这样:
release:
make clean
$(BLD_ROOT)/tools/incr_rel_num
# Although the third step, this was re-ordered to step 1
$(eval RELEASE_NUMBER=$(shell cat $(BLD_ROOT)/path/to/rel_num.txt))
make rpm RPM_RELEASE_NUM=$(RELEASE_NUMBER)
在调试时,我最终发现,尽管对 eval 的调用是配方中的第三步,但它实际上正在进行评估 first !这就是为什么RPM的版本号总是比我看到的数字少一个被推到遥控器的版本。
我已经做了很多谷歌搜索,我还没有找到任何能够解释在食谱中使用 eval 的评价顺序的点击。也许它甚至不是关于 eval ,而是一般的功能。此外,我还没有在GNU手册中找到这方面的措辞(如果它在那里,请指出哪一章)。我已经解决了这个问题所以它不是一个麻烦,我只是想知道,这是预期的,如果是这样,为什么?
答案 0 :(得分:2)
The $(eval ...)
function
生成一个make-sytax片段,它成为解析后的makefile的一部分。
在执行任何配方和配方之前,完全解析makefile
执行所有make语句,make-expressions和make-variables
评估了。
因此将$(eval ...)
调用视为一个调用是没有意义的
食谱的线条。它可能生成在make-expansion中使用的值
配方,但如果是这样,那么在配方运行之前解析生成文件时会发生 。
因此在您的示例中,行:
$(eval RELEASE_NUMBER=$(shell $(BLD_ROOT)/path/to/rel_num.txt))
我认为应该是:
$(eval RELEASE_NUMBER=$(shell cat $(BLD_ROOT)/path/to/rel_num.txt))
在解析makefile时会计算,并且让它说它会导致
make-variable RELEASE_NUMBER
获取值1.0
,因为,当
解析makefile,文件$(BLD_ROOT)/path/to/rel_num.txt)
包含
1.0
。在那种情况下你的食谱:
release:
make clean
$(BLD_ROOT)/tools/incr_rel_num
$(eval RELEASE_NUMBER=$(shell cat $(BLD_ROOT)/path/to/rel_num.txt))
make rpm RPM_RELEASE_NUM=$(RELEASE_NUMBER)
将解析为:
release:
make clean
some_build_dir/tools/incr_rel_num
make rpm RPM_RELEASE_NUM=1.0
您将观察make
运行配方的时间,它不打印任何行
是"扩展" $(eval RELEASE_NUMBER=$(shell cat $(BLD_ROOT)/path/to/rel_num.txt))
,
因为配方中没有这样的东西。这并不重要:
some_build_dir/tools/incr_rel_num
可能是在文件1.1
中写入2.0
或some_build_dir/path/to/rel_num.txt
的命令。
这个动作对食谱没有任何影响。没有在配方中执行的任何事情
可以更改配方。
$(eval ...)
在您的食谱中没有业务。你想要实现的只是:
release:
make clean
$(BLD_ROOT)/tools/incr_rel_num
RELEASE_NUMBER=$$(cat $(BLD_ROOT)/path/to/rel_num.txt) && \
make rpm RPM_RELEASE_NUM=$$RELEASE_NUMBER
其中$$
是您在makefile中为逃避$
所做的事情,在这种情况下,
执行配方时将它留给shell。
此配方扩展为按顺序执行的3个shell命令:
$ make clean
$ some_build_dir/tools/incr_rel_num
$ RELEASE_NUMBER=$(cat some_build_dir/path/to/rel_num.txt) && \
make rpm RPM_RELEASE_NUM=$RELEASE_NUMBER
并且可以进一步简化为:
release:
make clean
$(BLD_ROOT)/tools/incr_rel_num
make rpm RPM_RELEASE_NUM=$$(cat $(BLD_ROOT)/path/to/rel_num.txt)
答案 1 :(得分:2)
缺少的一点,上面没有人得到,很简单:当make要运行一个配方时,它首先扩展配方的所有行,然后再启动第一行。所以:
release:
make clean
$(BLD_ROOT)/tools/incr_rel_num
# Although the third step, this was re-ordered to step 1
$(eval RELEASE_NUMBER=$(shell $(BLD_ROOT)/path/to/rel_num.txt))
make rpm RPM_RELEASE_NUM=$(RELEASE_NUMBER)
当make决定运行release
目标时,它首先展开配方中的所有行,这意味着展开eval
,然后运行生成的行。这就是为什么你得到了你所看到的行为。
我真的不明白为什么你需要在这里使用eval
;为什么不直接使用:
release:
$(MAKE) clean
$(BLD_ROOT)/tools/incr_rel_num
$(MAKE) rpm RPM_RELEASE_NUM='$$(cat $(BLD_ROOT)/path/to/rel_num.txt))'
(顺便说一句,你不应该在你的文件中使用裸make
;你应该总是使用$(MAKE)
(或${MAKE}
,同样的事情。)
答案 2 :(得分:1)
你是对的,有多个级别的评估。在实际调用函数之前,第一次评估eval
内的内容。如果您希望在调用eval时评估eval
的内容,则必须通过将其放置两次来转义$
符号,如下所示:
$(eval RELEASE_NUMBER=$$(shell $(BLD_ROOT)/path/to/rel_num.txt))
要查看调用时eval
内的内容,您可以使用info
代替eval
的相同语法:
$(info RELEASE_NUMBER=$$(shell $(BLD_ROOT)/path/to/rel_num.txt))
现在我不太确定过早评估的部分,因此我加倍的$
符号可能不是好的符号,但使用info
函数会帮助你找到正确的命令。