为什么这个 makefile 无限循环,但只在一台机器上?

Why does this makefile loop infinitely, but only on one machine?

考虑以下 makefile:

-include target.d

build:

%.d: %.c
    @echo ">>> %.d: %.c"
    touch $@

target.d: dependency
#   @echo ">>> target.d: dependency"

dependency:
    @echo ">>> dependency"

.PHONY: build

如果你把它放在一个有两个(空)文件的目录中,target.d 和 target.c 和 运行 make,然后在我使用它的一个虚拟机上产生以下输出:

$ make
>>> dependency
>>> %.d: %.c
touch target.d
>>> dependency
>>> %.d: %.c
touch target.d
make: Nothing to be done for `build'.

在另一个虚拟机上,它无限循环。两台虚拟机都是 运行ning Centos7,并且都使用相同版本的 GNU make (3.82)。

(注意:如果您取消注释 target.d 目标下方的注释行,那么两者都会产生完全相同的输出;至少这种行为对我来说是有意义的)

我知道通过将配方添加到 target.d: dependency 可以使其优先于通用配方,同时只需添加依赖项。但我不明白的是,为什么一个系统会导致无限循环发生,而另一个极其相似的系统却不会。

这种奇怪行为的原因是什么?

(编辑:我发现我可以简化 makefile 并且仍然看到相同的行为)

我认为这里的问题是有一个名为 target.d 的目标,它与包含文件 target.d 相同,这样当您通过触摸它来编辑 target.d 时,它会以某种方式调用一个“未定义”的行为。在这种情况下,它似乎触发了对 makefile 的重新调用。我可以将您的问题归结为:

-include target.d

target.d: dep
    touch target.d

dep:
    @echo dep

唯一存在的文件是:makefiletarget.d 创建于 make 的第一个 运行。

$ ls
makefile
$ make
dep
touch target.d
dep
touch target.d
dep
touch target.d
     :
    etc
     :
$ ls
makefile target.d

我认为将目标同时包含在 makefile 中是不正确的。通常使用 .d(例如,使用 gcc 自动生成 dep 文件),您将在编译规则中生成这些文件,例如 %.o: %.c ...,它创建 .o 和 .d 文件。然后你有你的 include %.d 类型行。但是你不应该有直接生成 .d 文件的目标。我不认为这是有效的 - 如果我在这里错了,请有人纠正我。

例如我认为您想要的生成文件:

-include target.d

.PHONY: build
build: target.o

# Simulates a compile line that generates and object file (.o) and dependency file (.d)
target.o: target.c dependency
    touch target.o
    touch target.d

.PHONY: dependency
dependency:
    @echo ">>> dependency"

输出:

$ ls
makefile target.c
$ make
>>> dependency
touch target.o
touch target.d
$ ls
makefile  target.c  target.d  target.o

所以正如@code_fodder的建议的那样,整个makefile可以大大简化为

-include a

a: b
    touch a

b:
    @echo b

关于包含您创建的 makefile 是否是个好主意,这是一个有效的问题;正如上面的评论所述,这是我正在使用但无法控制的构建系统的一部分的提炼,所以这就是生活。

所以仍然存在一个问题,即为什么这在两个不同的系统上表现不同?

问题如下:一台虚拟机 运行 在远程服务器上,另一台虚拟机在本地 运行。特别是,我的主机 machine 是一个 mac,我在 mac 和 VM 之间共享一些文件系统。看来我在 Mac 上使用的文件系统只将时间戳保持为秒级精度,而虚拟机上的时间戳保持为亚毫秒级精度。因此,Mac 不会选择“更新的”文件。

通过添加各种长度的睡眠命令,我能够在与 mac 共享文件的虚拟机上看到这一点:

-include a

a: b
    sleep 0.8
    touch a

b:
    @echo b

这将导致它 运行 有限但准随机的次数。或者,更简单地说,运行ning make; make 产生

$ make; make
b
touch a
b
touch a
make: `a' is up to date.
b
touch a
make: `a' is up to date.

即运行连续 make 两次让第二个 运行 看到第一个的更新时间戳,因此它不会触发第二次。