Makefile 最佳实践
同时开着几个仓库时,拖慢进度的通常不是语言,是「这个项目怎么跑起来」。默认入口用 GNU Make。不是为了编译 C,是因为 make help 比 README 里的命令博物馆更能活下去。
问题不是构建,是入口
教科书式的 gcc / .o 示例,这里用不上。AI Simi 和周边仓库几乎不这么用 Make。
要解决的是:
- 三个月后再打开这个目录,第一件事该敲什么
- 新来的 agent(Copilot、Claude Code、Leo)不该猜「启动脚本在哪」
- 同一套动作在 Mac 和 VPS 上不要各写一份
约定因此很窄:仓库根目录有 Makefile,默认目标是 help,每个可执行动作是一个 target。文档只写「跑 make help」,不写一长串裸命令。
这是 Convention over Configuration 落到日常开发上的样子。
最小可用形状
下面是 AI Simi 仓库里的骨架,不是 hello.c 教程。依赖关系很简单:人记住一个词,机器展开其余的。
SHELL := /bin/bash
.DEFAULT_GOAL := help
BLUE := \033[36m
GREEN := \033[32m
RESET := \033[0m
.PHONY: help
help: ## 查看所有可用命令
@awk 'BEGIN {FS = ":.*?## "} \
/^## / { printf "\n%s\n", substr($$0, 4) } \
/^[a-zA-Z0-9_-]+:.*?## / { printf " %-22s %s\n", $$1, $$2 }' \
$(MAKEFILE_LIST)
.PHONY: setup
setup: setup-env setup-workspace ## 完整初始化
.PHONY: setup-env
setup-env: ## 初始化本机配置目录
@bash scripts/setup-env.sh
.PHONY: agent-status
agent-status: ## 查看所有 agent 状态
@bash scripts/agent-status.sh
要点只有四个:
.DEFAULT_GOAL := help:裸敲make不会误跑一个「构建全世界」的目标。.PHONY:这些名字不是磁盘上的产物。漏写的话,一旦目录里出现同名文件,target 会静默跳过。官方说明在 Phony Targets。##注释 +awk:帮助文本和实现写在一起,README 不会再漂移。- target 只做编排,脏活放进
scripts/*.sh。Makefile 不是 bash 的坟场。
语法以 GNU Make 手册 为准。这里只用很小一截:变量、依赖、phony、默认目标。
核对入口:
make
make help
make -n agent-statusmake -n 只打印 recipe,不执行。改危险 target 之前先看这一眼。
失败怎么看见
漏写 .PHONY 时,失败是安静的。目录里如果有一个叫 help 的文件,make help 会说已经是最新,recipe 根本不跑。
touch help
make help输出类似 make: 'help' is up to date. 就是这个坑。删掉那个文件,补上 .PHONY: help。
recipe 真失败时,Make 默认停在第一条非零退出。不要在行尾加 - 把错误吞掉,除非那个失败是故意可忽略的。需要看展开结果,用 make -d 太吵;先 make -n,再对那个 script 单独跑一遍。
为什么不是 package.json / just / Taskfile
这些工具都能用。选 Make 的理由很具体:
| 选择 | 适用场景 | 不选的原因 |
|---|---|---|
| npm scripts | 纯 JS 前端仓库 | 一个 Go + Flutter + agent 的单人工作室,不该为了 dev 装 Node |
| just | 新仓库、团队已统一 | 多一台机器就要多装一个二进制;agent 环境不一定有 |
| Task | 团队已经说 YAML | 又多一份要记住的 DSL |
| Makefile | 跨语言、跨机器、给人和 agent 共用 | 语法偶尔难看;换来的是几乎每台 Unix 都已安装 |
独立开发者的约束是「下一台机器上也要零摩擦」。Make 已经在 macOS 开发者工具和 Ubuntu 镜像里。这个优势大过语法洁癖。
Agent 比人更需要这扇门
2026 年更多操作交给 agent。它们擅长读约定,不擅长在五个 README 里做选择。
给 agent 仓库的第一句通常是:先跑 make help,只使用列出来的 target。这比在 prompt 里复制 20 行 shell 更稳,也比让模型自己发明 docker compose 参数更安全。
副作用:Makefile 如果是一堆没有说明的目标,agent 会随机挑一个。帮助文本不是礼貌,是接口。
强制遵守的几条
- target 用 kebab-case:
agent-up-leo,不要agentUpLeo - 一个 target 一件事;
setup可以依赖子目标,但不要在一个 recipe 里写 80 行 - 需要密钥的动作只引用变量名和
~/.simi/路径,不把值写进仓库 - 破坏性动作(删数据、force push、对生产发帖)不要做成无确认的默认 target
- 能用已有 POSIX 工具就不要再包一层 Python
违反最后一条的信号:Makefile 里开始出现 python3 -c 长脚本。那是该拆到 scripts/ 的时候。
什么时候不要用 Make
- 真正的增量编译图已经很复杂(大型 C/C++ / 内核),用语言自己的构建系统
- 团队已经有统一的 Taskfile,再坚持 Make 是在制造方言
- Windows 原生环境且没有 GNU Make——不是这里的日常,不为它扭曲约定
边界:Make 负责「人怎么进来」,语言工具链负责「代码怎么变成产物」。两者搅在一起,README 和 CI 会一起腐烂。