如何让 Makefile 在 .tpp 模板实现文件修改后触发重新编译?
需要让 TestMRE.o 依赖 MRE.tpp。这样修改 MRE.tpp 后,make 才会重新编译 TestMRE.cpp,然后重新链接程序。.tpp 文件不需要单独生成 .o 文件。
这个示例可以直接补齐依赖。如果项目中的头文件较多,用 GCC 或 Clang 自动生成依赖文件会更方便。
为什么修改 .tpp 没有触发编译?
编译器实际处理的包含关系是:
TestMRE.cpp → MRE.hpp → MRE.tpp
MRE.hpp 和 MRE.tpp 都会影响 TestMRE.o 的内容,但 make 不会自行解析 C++ 的 #include。它需要通过 Makefile 或编译器生成的依赖文件了解这些关系。
原来的规则还有一个文件名匹配问题:
%.o: %.cpp %.hpp
对于 TestMRE.o,这里的 % 是 TestMRE,所以这条规则要求存在 TestMRE.cpp 和 TestMRE.hpp。它不会自动找到 MRE.hpp,也没有声明 MRE.tpp。
示例没有提供 TestMRE.hpp,因此这条自定义模式规则可能无法应用。如果 GNU make 启用了内置规则,目标文件仍可能通过内置的 .cpp 编译规则生成,但不会因此补上所需的头文件依赖。
方法一:显式列出依赖
对于这个最小示例,可以直接使用:
TestMRE: TestMRE.o
$(CXX) $(CXXFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@
TestMRE.o: TestMRE.cpp MRE.hpp MRE.tpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
这里,TestMRE.o 依赖三个文件。只要其中任意一个文件的修改时间比 TestMRE.o 新,make 就会重新执行编译命令。生成新的 TestMRE.o 后,make 会继续重新链接 TestMRE。
$< 表示第一个依赖文件,这里是 TestMRE.cpp;$^ 表示所有依赖文件,在链接规则中是 TestMRE.o;$@ 表示当前目标。
Makefile 中的命令行必须以真正的 Tab 开头。
方法二:让编译器自动生成依赖
使用 GCC 或 Clang 时,可以在编译时生成 .d 文件,再让 GNU make 读取:
TestMRE: TestMRE.o
$(CXX) $(CXXFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@
%.o: %.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -MMD -MP -MF $(@:.o=.d) -c $< -o $@
-include TestMRE.d
各选项的作用如下:
-MMD:在编译时生成依赖信息,包含通过#include引入的非系统头文件,也包括间接包含的文件。-MP:为依赖项生成空目标规则,减少删除或重命名头文件后,旧依赖记录导致的 make 报错。它不会让编译器忽略缺失的#include文件。-MF $(@:.o=.d):指定依赖文件路径。在这个例子中,TestMRE.o对应TestMRE.d。-include TestMRE.d:读取依赖文件;首次构建时即使文件尚不存在,也不会因此报错。
编译器生成的依赖文件会表达类似下面的关系,具体路径写法和排版可能不同:
TestMRE.o: TestMRE.cpp MRE.hpp MRE.tpp
.tpp 扩展名不会妨碍依赖生成。只要文件通过 #include 被实际读取,就能记录为依赖。
如何让已有构建开始使用自动依赖?
如果 TestMRE.o 已存在,而 TestMRE.d 尚未生成,仅修改 Makefile 中的编译命令通常不会让 make 自动重新编译已有目标。
采用上面的自动依赖配置后,可以先在 GNU make 中执行一次:
make -B
这次强制构建会生成 TestMRE.o 和 TestMRE.d。之后修改 MRE.tpp,再执行:
make
./TestMRE
在文件时间戳正常、修改已保存且构建成功的前提下,make 应重新编译并链接,程序输出应变为:
final message
后续需要保留并读取 .d 文件。如果删除了依赖文件,却保留已有的 .o 文件,就需要重新生成依赖。其他编译器的依赖生成选项可能不同,需要使用相应工具链支持的方式。
备注:内容仅供参考。
