Makefile精读 · M02-01 | 显式规则:目标、依赖、配方与 Tab 纪律
2026/7/19 3:12:42 网站建设 项目流程

模块:M02 规则语法精读
篇号:M02-01 / 41
预计阅读:35 分钟
主线:GNU Make


文章目录

    • 本篇目标
    • 30 秒速览
    • 正文
      • 1. 显式规则的标准形态
      • 2. 多个目标、多个依赖(显式写法)
      • 3. Tab 纪律:不是审美,是语法
        • 3.1 正确 vs 错误
        • 3.2 编辑器与「看不见的字符」
        • 3.3 哪些行需要 Tab?
      • 4. 多行配方:一行一个 shell
      • 5. 配方前缀:`@`、`-`、`+`
      • 6. 注释、空行与可读性
      • 7. 无配方规则:只挂依赖
      • 8. 对照表:显式规则要素
    • 读 Makefile 检查清单
    • 练习
      • 判断题(对 / 错)
      • 改错题
      • 动手题
    • 下一篇预告

本篇目标

把「一条显式规则」的语法钉死:目标、依赖、配方如何书写;多行配方如何执行;@/-/+前缀做什么;Tab 与空格为何是纪律问题而非风格偏好。学完本篇,你能正确写出并读懂最常见的文件规则,并为 M02-02 的多目标、同目标多规则打下基础。


30 秒速览

  • 显式规则基本形:目标: 依赖…,下一行起Tab+ 配方命令。
  • 一条规则可以有多个目标多个依赖;依赖可为文件或其它目标名。
  • 配方可多行:默认每一行单独启动一次 shell;要用同一 shell 会话需用\续行或写在一行用;连接。
  • 配方前缀:@不回显、-忽略错误、+-n时仍执行(少见但要认得)。
  • 配方缩进必须是 Tab;空格会触发*** missing separator
  • 注释用#;规则行也可用\续行把长依赖列表拆开。

正文

1. 显式规则的标准形态

目标: 依赖1 依赖2 配方命令1 配方命令2

最小完整例:

hello: hello.c gcc -Wall -o hello hello.c

拆解:

部分本例说明
目标hello冒号左侧;要更新的名字
依赖hello.c冒号右侧;可空(无依赖目标也合法)
配方gcc ...必须以 Tab 开头

无依赖的动作目标(呼应 M01-03):

.PHONY: clean clean: rm -f hello *.o

clean:右侧为空:没有文件依赖,只靠.PHONY保证每次执行。


2. 多个目标、多个依赖(显式写法)

app: main.o util.o gcc -o app main.o util.o main.o: main.c util.h gcc -c main.c util.o: util.c util.h gcc -c util.c

要点:

  1. 依赖列表用空格分隔。
  2. 长列表可用反斜杠续行:
app: main.o util.o extra.o \ net.o ui.o gcc -o app main.o util.o extra.o net.o ui.o
  1. 「一行写多个目标」的显式规则(共用同一套依赖与配方)合法,但语义要小心——M02-02专讲;本篇先认单目标为主的常见写法。

3. Tab 纪律:不是审美,是语法

3.1 正确 vs 错误
# 正确:配方前是 Tab hello: hello.c gcc -o hello hello.c # 错误:四个空格 hello: hello.c gcc -o hello hello.c

错误时 GNU Make 典型报错:

Makefile:3: *** missing separator. Stop.

「separator」在这里主要指:规则头与配方之间,配方行必须以 Tab 作为分隔/引导

3.2 编辑器与「看不见的字符」

高频事故:

原因现象
编辑器把 Tab 转成空格missing separator
从网页复制 Makefile空格冒充缩进
「显示空白」未开,肉眼看不出来本地好好的,CI 挂了

建议:

  1. 编辑 Makefile 时关闭「Tab 转空格」,或为Makefile/makefile/*.mk单独配置。
  2. 用能显示 Tab/空格的编辑器视图检查。
  3. 可疑时:cat -A Makefile(Tab 常显示为^I)。
3.3 哪些行需要 Tab?
行类型是否 Tab
目标: 依赖否(顶格或仅因风格缩进——不要用 Tab 当「美观缩进」去缩规则头)
配方命令行必须 Tab 开头
变量 = 值include、条件指令
空行、#注释

规则头若被误加了 Tab,也可能让 make 把整行当成配方或解析失败——规则头顶格写最省心。


4. 多行配方:一行一个 shell

默认情况下,配方的每一行各自调用一次 shell:

wrong: cd build pwd

第二行的pwd不会build/里执行:cd只影响第一个短暂 shell,进程结束即丢。

正确写法(同一 shell):

ok: cd build && pwd # 或续行(反斜杠后换行,仍属「逻辑一行」配方的常见写法) ok2: cd build && \ pwd

也可用分号:

ok3: cd build; pwd

经验:

  • 需要保留目录/变量状态 → 写在同一配方行的同一 shell里(&&/;/\)。
  • 彼此独立的命令 → 分行即可。

配方里也可以写多条独立命令:

hello: hello.c gcc -Wall -c hello.c -o hello.o gcc -o hello hello.o rm -f hello.o

每行仍是独立 shell,但这里不依赖「上一行 cd 的状态」,所以没问题。


5. 配方前缀:@-+

写在配方命令最前面(Tab 之后):

demo: @echo "不回显 echo 这一行命令本身" -rm -f maybe-missing.txt +echo "即使 make -n 也可能执行(少见用途)"
前缀含义
@不向终端回显该命令行
-该命令失败(非零退出)时,不让 make 因此失败
+make -n/某些「不执行」模式下,仍执行该行(给递归$(MAKE)等场景用;M08 再遇)

可叠用,顺序惯例为@-+的组合,例如:

clean: -@rm -f *.o hello

含义:尝试删除;失败不致命;且少回显。

慎用-:忽略错误容易掩盖真问题。清理缺失文件时用-rm较常见;编译步骤一般不要随意加-


6. 注释、空行与可读性

# 这是注释:生成演示程序 hello: hello.c # 行尾注释也可以 gcc -o hello hello.c

注意:

  1. 配方行里的#是否启动注释,取决于传给 shell 的内容——配方交给/bin/sh后,按shell 规则解释。Make 层对规则头/变量行的#注释更直观。
  2. 空行可以分隔规则,增强可读性;空行不会结束「当前规则的配方」——只要下一非空行仍是 Tab 配方,就仍属于上一规则(直到下一个规则头或非配方指令)。
hello: hello.c gcc -c hello.c -o hello.o gcc -o hello hello.o

上面两行配方仍都属于hello(中间空行无妨)。若误把第二段写成无 Tab 的裸命令,才会解析错乱。


7. 无配方规则:只挂依赖

all: hello libutil.a hello: main.o gcc -o hello main.o

all只有依赖、没有配方:请求all时,Make 负责更新依赖目标。这是all的常见写法(M01-03)。

也可以给文件目标「只追加依赖、配方在别处」——这是M02-02「同一目标多条规则」的话题,本篇只要求你认得:规则可以没有配方


8. 对照表:显式规则要素

要素写法要点
规则头目标: 依赖…,建议顶格
依赖分隔空格;长列表用\续行
配方每行以 Tab 开头
多行 shell 状态&&/;/\,勿假设跨行共享 cwd
@-+Tab 后、命令前的前缀
注释#
无配方仅表达依赖聚合(如all

读 Makefile 检查清单

看到一条显式规则时:

  • 冒号左侧目标、右侧依赖是否分得清
  • 配方是否Tab缩进(必要时cat -A
  • 多行配方是否错误地假设「上一行cd仍然有效」
  • 是否滥用-前缀忽略了不该忽略的错误
  • @是否导致排错时看不到关键命令(可临时去掉@或去掉make -s

练习

判断题(对 / 错)

  1. 配方行用四个空格缩进,在 GNU Make 中与 Tab 完全等价。
  2. 默认情况下,配方的每一行各自在一个新的 shell 中执行。
  3. 前缀-表示该行命令失败时不让 make 失败退出。
  4. 规则头hello: hello.c必须以 Tab 开头。
参考答案
  1. 错。配方必须 Tab;空格会missing separator
  2. 对。
  3. 对。
  4. 错。规则头不应(也不必)用 Tab 开头;Tab 用于配方行。

改错题

指出问题并改正:

hello: hello.c gcc -o hello hello.c cd out cp hello out/
参考答案

问题:

  1. 第一行配方用了空格,应改为 Tab。
  2. cd out与下一行分属两个 shell,cp不一定在out/下执行;且hello产物路径与cp逻辑也混乱。

一种改正思路(按你真实布局调整):

hello: hello.c mkdir -p out gcc -o out/hello hello.c

或:

hello: hello.c gcc -o hello hello.c mkdir -p out && cp hello out/

动手题

  1. 故意把配方改成空格缩进,观察完整报错行号。
  2. 写一个含cd的错误多行配方,再用&&改正确,对比pwd输出。
  3. clean加上-@rm -f ...,观察回显与「文件不存在」时是否仍成功。

下一篇预告

M02-02:《多目标规则、同一配方与「一个目标多条规则」》— 一行多个目标时 Make 如何解释、同一目标拆成「依赖规则 + 配方规则」怎么合并,以及常见误读。学完 M02-02,你读拆得很碎的工程 Makefile 时不会懵。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询