什么时候应该在make配方中评估对* eval *的调用

时间:2016-07-06 15:35:31

标签: makefile gnu-make

我有一些软件项目作为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手册中找到这方面的措辞(如果它在那里,请指出哪一章)。我已经解决了这个问题所以它不是一个麻烦,我只是想知道,这是预期的,如果是这样,为什么?

3 个答案:

答案 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.0some_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函数会帮助你找到正确的命令。