You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何阻止Make将已存在目录判定为比其内部目标更新?

问题描述

我写了个Makefile,通过规则创建构建产物的目录结构,再把构建产物放到对应目录里。构建产物的目标依赖于其父目录的创建目标,以此类推。

我不想在仓库里预置构建目录结构和大量.git-keep文件——这样make clean会变复杂,没法只用rm -r build搞定。有人在SuperUser上说这种做法“有点问题”,但基于我的需求,我觉得没毛病。

现在有两个问题:

  1. 怎么让Make停止把已存在的目录判定为“比其包含的目标更新”?
  2. 从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简单),推荐两种方案:

  1. 优先使用有序依赖:既能确保目录自动创建,又不会因为目录mtime变化触发重建,完全满足你的需求,也符合Make的设计规范。
  2. 编译前自动创建目录:这种方式更简洁,不需要单独维护目录规则,make clean依然可以用rm -r build一键清理,没有任何额外负担。

两种方案都不需要在仓库中预置目录或.git-keep文件,完美匹配你的核心需求。

内容的提问来源于stack exchange,提问作者Adam Barnes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 00:24:54