Makefile 仅在使用静态模式规则时重新创建丢失的依赖文件

Makefile recreates missing dependency files only when a static pattern rule is used

考虑清单 1 中的 makefile 代码。目标是为第 9 行调用 C 编译器时生成的每个目标文件 %.o 生成一个依赖文件 $(DEPDIR)/%.d。具体来说,生成依赖文件是在第 9 行调用 C 编译器将 %.c 文件转换为 %.o 文件的副作用——即第 9 行发出两个文件:$(DEPDIR)/%.d 和 % .o.

清单 1.

1   DEPDIR := deps
2
3   SOURCES.c  := $(wildcard *.c)
4   OBJS.c  := $(SOURCES.c:.c=.o)
5
6   CPP_DEPFLAGS = -MT $@ -MMD -MF $(DEPDIR)/$*.d
#
#   Other rules, variable definitions, etc. are here...
#
7   %.o : %.c
8   $(OBJS.c) : %.o : %.c $(DEPDIR)/%.d | $(DEPDIR)
9       $(COMPILE.c) $(CPP_DEPFLAGS) $(OUTPUT_OPTION) $<
10
11  $(DEPDIR) : ; mkdir -p $@
12
13  # Ensure make considers missing $(DEPDIR)/%.d files as "not updated"
14  $(DEPDIR)/%.d : ;
15
16  # [EDIT]
17  .PRECIOUS: $(DEPDIR)/%.d

这个 makefile 代码可以正常工作,但我不明白为什么(见下文)。具体来说,如果 DEPDIR 文件夹不存在,则调用 make 会创建该文件夹,并且第 9 行的配方会发出 $(DEPDIR)/%.d 文件以及 %.o 文件。

现在假设用户删除了一个或多个 %.d 文件,或者整个 DEPDIR 文件夹。重新调用 make 会根据需要重建丢失的依赖文件,但同样,我不明白为什么

$ rm -fr depdir *.o
$ make # Generates DEPDIR and the $(DEPDIR)/%.d files
$ rm -fr depdir
# NB: The %.o files still exist
$ make # Regenerates DEPDIR and the $(DEPDIR)/%.d files

现在用清单 2 中所示的隐式规则替换第 8 行的静态模式规则,否则保持原样:

清单 2.

7   %.o : %.c
8   %.o : %.c $(DEPDIR)/%.d | $(DEPDIR)
9       $(COMPILE.c) $(CPP_DEPFLAGS) $(OUTPUT_OPTION) $<

当使用隐式规则时,make 不会重新生成 deleted/missing 依赖文件——这不是我预期的结果:

$ rm -fr depdir *.o
$ make # Generates DEPDIR and the $(DEPDIR)/%.d files
$ rm -fr depdir
# NB: The %.o files still exist
$ make # Generates DEPDIR only; DOES NOT regenerate the $(DEPDIR)/%.d files

静态模式规则版本认为 deleted/missing 先决条件 $(DEPDIR)/%.d(第 8 行)已更新 (这是我没想到的) 第 14 行的规则,从而使 %.o 目标过时,因此调用第 9 行的配方来重建 %.o 文件。

另一方面,隐式模式规则显然认为 deleted/missing 先决条件 $(DEPDIR)/%.d(第 8 行)是 not updated 是最新的(这是 NOT 我所期望的),因此 make 认为 %.o 目标是最新的并且第 9 行的配方未被调用。

那么为什么隐式模式规则(清单 2)不调用第 9 行的配方行?我在 GNU Make 文档中找不到任何内容来解释静态模式规则和隐式规则之间的区别。

如果您想了解这一点,您可以考虑阅读 Auto-dependency Generation。但要说明:

静态模式规则起作用的原因是第 14 行:

13  # Ensure make considers missing $(DEPDIR)/%.d files as "not updated"
14  $(DEPDIR)/%.d : ;

这会创建一个隐式规则,说明如何构建一个 .d 文件并提供一个空配方。配方不做任何事情,但如果 make 需要 运行 它 make 将考虑任何依赖于它的目标已过时。因此,如果文件丢失,make 将重建任何依赖它的目标。

删除了原来的错误答案

好的,现在我自己试了一下,明白了。

您的隐式规则不起作用的原因是 .d 文件被视为 intermediate files。因为它们是中间文件,如果它们不存在,那么 make 不会尝试重建它们,除非由于其他原因需要重建它们;这里没有。

您可以通过先删除所有 .d 文件然后触摸 .c 文件强制重建它们来证明我的理论是正确的:

$ touch *.c
$ rm deps/*.d
$ make
cc    -c -MT a.o -MMD -MF deps/a.d -o a.o a.c
cc    -c -MT b.o -MMD -MF deps/b.d -o b.o b.c
cc    -c -MT d.o -MMD -MF deps/d.d -o d.o d.c
rm deps/a.d deps/d.d deps/b.d

注意最后一行:删除所有中间文件。

如果你查看我上面的博客 post,你会发现我建议将隐式规则更改为显式规则,对你来说,这将是这样的:

$(SOURCES.c:%.c=$(DEPDIR)/%.d):

现在所有 .d 文件都是显式目标,因此不能是中间文件。

请注意博客 post 我 link 上面提供了一种稍微好一点的方法来处理这个问题。

经过进一步实验,我原来 post 中清单 1 的问题是第 14 行:

$(DEPDIR)/%.d: ;

哎呀。我在想什么!?此行应该通过替换引用(或通过调用 patsubst 等)来表达:

$(OBJS:%.o=$(DEPDIR)/%.d): ;

或者遵循 MadScientist 的建议,恕我直言,它提供了更高的可读性(并且我使用 OBJS 而不是 SOURCES 略有不同),

DEPFILES := $(OBJS:%.o=$(DEPDIR)/%.d)
$(DEPFILES): ;

哪里

OBJS.c := $(SOURCES.c:%.c=%.o)
OBJS.cc := $(SOURCES.cc:%.cc=%.o)
OBJS := $(OBJS.c) $(OBJS.cc)

在 changing/fixing 第 14 行之后,我的 makefile 持续调用配方第 9 行(在清单 1 中)为静态模式规则案例和隐式规则案例重建 deleted/missing 依赖文件。

[编辑]

根据 MadScientist 的评论,我已经从 makefile 中删除了 .PRECIOUS 行,因为它不再需要(在进行了我上面描述的更改之后)并且 .PRECIOUS 行可能会导致不需要的行为。