Makefile 不只是编译用的
make deploy 可以把 npm run build && rsync ... && ssh ... 这类长命令收成一个稳定入口。
很多人第一次接触 Make,都是在 C/C++ 项目里编译代码,于是很容易把它理解成“只适合编译的老工具”。这个判断不算错,但不完整。对日常项目来说,Makefile 更像一个命令调度入口:把会重复执行、顺序固定、参数容易忘的命令,收成几个短名字。
它解决的不是“能不能写复杂脚本”,而是另一个更常见的问题:工作流散落在 README、shell 历史记录、编辑器任务和 CI 配置里,真正执行时还得自己临场拼接。
Make 最适合做什么
Make 的核心不是“编译”,而是“目标”和“依赖”。
- 目标:你想做的事,比如
build、test、deploy - 依赖:做这件事之前,哪些事得先做
如果你平时会写这些命令:
- 启动本地开发环境
- 格式化代码
- 跑测试
- 打包静态文件
- 同步配置
- 生成文档
- 一键部署
那 Makefile 通常就有用。
它不一定比 shell 脚本更强,但通常比“十几个脚本文件 + 多套入口”更收敛。它的优点是轻、直观、系统里常见;边界是复杂逻辑不好写,跨平台细节也要小心。
一个够用的 Makefile
下面这个例子可以直接作为起点:
1 | |
有三个点值得记:
1. .PHONY 要写
它的意思是:这些目标不是实际文件。
不写也能跑,但一旦目录里刚好有个同名文件,比如 build,Make 可能会误判“这个目标已经存在,不用执行了”。
2. 依赖比注释更可靠
deploy: test build 的意思是,部署前先测试、再构建。
这比在文档里写“部署前请先执行测试”更可靠,因为文档会被忘,依赖会被执行。
3. 入口统一,切换成本会下降
团队协作里常见的问题不是不会写命令,而是每个人都记一套命令。
有人执行 pnpm build,有人跑包装脚本,有人手动上传。最后“怎么部署”只能靠聊天记录和口口相传。Makefile 的价值就在这:把入口收成 make xxx。
不只管代码,也可以管环境
Makefile 还有一个实用场景:检查运行环境。
1 | |
这里的思路很简单:
make info:快速看系统和硬件信息make check:确认依赖命令是否存在
|| true 的意思是:某个命令不存在时,不要让整个流程直接报错退出。适合做“尽量多输出信息”的检查。
这类目标适合放在项目根目录。新机器、新环境、迁移服务器时,先跑一遍,通常比手工核对更省事。
用变量收住那些容易改的地方
不要把路径、主机名、端口直接写死在命令里。Makefile 虽然简单,但变量够用。
1 | |
如果想临时覆盖:
1 | |
这比复制一份 deploy-staging.sh、deploy-prod.sh 干净,也更容易看清差异到底在哪:通常只是变量不同,不是流程不同。
什么时候别硬上 Make
Make 不是工作流平台,别把它写成一门新语言。
下面这些情况,更适合换工具或下沉到脚本里:
1. 逻辑分支很多
比如十几个参数组合、复杂条件判断、循环重试。
这种时候 shell、Python、Node 脚本会更清楚。
2. 强依赖跨平台一致性
Windows、macOS、Linux 的命令差异不少。
如果使用者环境很杂,Makefile 可能反而会变成兼容性问题入口。
3. 需要详细交互
Make 更适合“执行一组命令”,不适合做交互式向导。
它应该是薄入口,不该承担全部业务逻辑。
一个实用做法是:Make 负责调度,脚本负责复杂实现。
1 | |
这样既保留统一入口,也不会把 Makefile 写成难读的迷宫。
写 Makefile 时顺手整理动作边界
写 Makefile 的过程,其实是在回答几个问题:
- 这个项目最常用的动作有哪些?
- 哪些步骤应该强制串联?
- 哪些参数应该显式暴露?
- 哪些检查应该自动做,而不是靠人记住?
这些问题本来就存在。Makefile 只是把它们从聊天记录、历史命令和个人习惯里拎出来,变成能执行的入口。
如果你手边已经有一堆“偶尔执行一次,但每次都要翻记录”的命令,先写一个 Makefile,放进 5 个最常用目标,通常就已经够用了。