如何阻止Make将已存在目录判定为比其内部目标更新?
我写了个Makefile,通过规则创建构建产物的目录结构,再把构建产物放到对应目录里。构建产物的目标依赖于其父目录的创建目标,以此类推。
我不想在仓库里预置构建目录结构和大量.git-keep文件——这样make clean会变复杂,没法只用rm -r build搞定。有人在SuperUser上说这种做法“有点问题”,但基于我的需求,我觉得没毛病。
现在有两个问题:
- 怎么让Make停止把已存在的目录判定为“比其包含的目标更新”?
- 从XY问题的角度看,我的做法错在哪?正确的做法是什么?
执行make cluster时(需要Postgres开发库),不管什么情况都会重新构建依赖的目标文件,这是我要避免的。以下是make -d cluster的输出片段:
Considering target file 'cluster'.
Considering target file 'build/config-server.make'.
File 'build/config-server.make' was considered already.
Considering target file 'src/servers/cluster/main.cpp'.
Looking for an implicit rule for 'src/servers/cluster/main.cpp'.
No implicit rule found for 'src/servers/cluster/main.cpp'.
Finished prerequisites of target file 'src/servers/cluster/main.cpp'.
No need to remake target 'src/servers/cluster/main.cpp'.
Considering target file 'build/artefacts/shared/db.o'.
Considering target file 'build/artefacts/shared'.
Considering target file 'build/artefacts'.
Finished prerequisites of target file 'build/artefacts'.
No need to remake target 'build/artefacts'.
Finished prerequisites of target file 'build/artefacts/shared'.
Prerequisite 'build/artefacts' is older than target 'build/artefacts/shared'.
No need to remake target 'build/artefacts/shared'.
Considering target file 'src/shared/db.cpp'.
Looking for an implicit rule for 'src/shared/db.cpp'.
No implicit rule found for 'src/shared/db.cpp'.
Finished prerequisites of target file 'src/shared/db.cpp'.
No need to remake target 'src/shared/db.cpp'.
Considering target file 'src/shared/db.h'.
Looking for an implicit rule for 'src/shared/db.h'.
No implicit rule found for 'src/shared/db.h'.
Finished prerequisites of target file 'src/shared/db.h'.
No need to remake target 'src/shared/db.h'.
Considering target file 'build/config.make'.
File 'build/config.make' was considered already.
Finished prerequisites of target file 'build/artefacts/shared/db.o'.
Prerequisite 'build/artefacts/shared' is newer than target 'build/artefacts/shared/db.o'.
Prerequisite 'src/shared/db.cpp' is newer than target 'build/artefacts/shared/db.o'.
Prerequisite 'src/shared/db.h' is newer than target 'build/artefacts/shared/db.o'.
Prerequisite 'build/config.make' is newer than target 'build/artefacts/shared/db.o'.
Must remake target 'build/artefacts/shared/db.o'.
makefile:20: update target 'build/artefacts/shared/db.o' due to: build/artefacts/shared src/shared/db.cpp src/shared/db.h build/config.make
g++
-std=c++23 -O3 -Wall -Wextra -Werror -Wsign-conversion -pedantic-errors -g -Isrc
src/shared/db.cpp
-c
-o build/artefacts/shared/db.o
一、阻止Make将目录判定为比目标更新
问题核心是:目录的修改时间(mtime)会在其内部文件/子目录变化时自动更新,导致它永远比已存在的目标文件“新”。有两种直接解决方法:
1. 使用有序依赖(Order-Only Prerequisites)
这是Make官方推荐的处理目录依赖的方案。把目录依赖放到|符号后面,这样Make只会确保目录存在,不会将其修改时间和目标文件对比——只有当目录不存在时才会创建它,不会因为目录mtime更新触发重建。
示例修改:
# 保留目录创建规则 build/artefacts/shared: build/artefacts mkdir -p $@ # 构建目标的依赖分为普通依赖和有序依赖 build/artefacts/shared/db.o: src/shared/db.cpp src/shared/db.h build/config.make | build/artefacts/shared g++ $(CFLAGS) -c $< -o $@
2. 在编译命令中直接创建目录
不需要单独定义目录规则,直接在编译命令前用mkdir -p确保目标文件的父目录存在,完全规避目录作为依赖的问题。
示例修改:
build/artefacts/shared/db.o: src/shared/db.cpp src/shared/db.h build/config.make # 先创建父目录,$(@D)表示目标文件的父目录路径 mkdir -p $(@D) g++ $(CFLAGS) -c $< -o $@
二、XY问题分析:你的做法错在哪?正确做法是什么?
错误点
你把目录直接作为构建目标的普通依赖,而目录的mtime特性决定了:只要目录内有任何文件(包括其他构建产物)被创建、修改,目录的mtime就会更新。这导致每次目录内有操作,之前的目标文件都会被判定为过时,触发不必要的重建——这就是你遇到的问题根源。
另外,Make对目录的处理逻辑和普通文件不同,它本质上不适合把目录作为依赖项来管理更新,因为目录的mtime太容易变化,不符合Make依赖管理的预期(依赖应该是内容稳定、仅在需要时更新的文件)。
正确做法
结合你的需求(不预置目录、make clean简单),推荐两种方案:
- 优先使用有序依赖:既能确保目录自动创建,又不会因为目录mtime变化触发重建,完全满足你的需求,也符合Make的设计规范。
- 编译前自动创建目录:这种方式更简洁,不需要单独维护目录规则,
make clean依然可以用rm -r build一键清理,没有任何额外负担。
两种方案都不需要在仓库中预置目录或.git-keep文件,完美匹配你的核心需求。
内容的提问来源于stack exchange,提问作者Adam Barnes

