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 行可能会导致不需要的行为。
考虑清单 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 行可能会导致不需要的行为。