```makefile
compiler: lexer.o parser.o ast.o
    gcc lexer.o parser.o ast.o -o compiler

lexer.o: lexer.c
    gcc lexer.c -c -o lexer.o

parser.o: parser.c
    gcc parser.c -c -o parser.o

ast.o: ast.c
    gcc ast.c -c -o ast.o

lexer.c: lexer.l
    flex -o lexer.c lexer.l

parser.c: parser.y
    bison -d -o parser.c parser.y
```

在命令行中执行:

```
make compiler
```

然后, `make` 就会根据 `Makefile` 的内容, 自动帮我们生成 `compiler` 这个文件.

`Makefile` 的基本语法其实很简单. 你可以注意到上面的例子中出现了很多 `file1: file2 file3 ...` 的写法, 它的含义是: 要生成 `file1`, 必须确保 `file2`, `file3`, … 是存在的, 如果他们不存在, 就去生成相应的文件. 在这行之后带有缩进的内容描述了应该如何生成冒号之前的那个文件.

所以, 例子中的 `Makefile` 描述了这样的事情: 要想生成 `compiler`, 就必须保证 `lexer.o`, `parser.o` 和 `ast.o` 是存在的. 如果这些文件存在, 就使用 `gcc lexer.o parser.o ast.o -o compiler` 这条命令来生成 `compiler` 这个文件; 如果这些文件不存在, 就尝试用文件里写明的其他规则生成这些文件. 之后的规则与之类似, 不再赘述.

当然, 我们其实可以直接在命令行执行:

```
make
```

这和 `make compiler` 是等价的, 因为在不指定 `make` 什么文件的时候, `make` 会查找 `Makefile` 里出现的第一个规则, 然后去生成这个规则对应的文件.

`make` 不仅会检查文件是否存在, 还会检查文件是否是最新的. 在 `compiler: lexer.o parser.o ast.o` 规则中, 如果 `lexer.o`, `parser.o` 和 `ast.o` 都存在, 但其中某个文件 “不是最新的”, 那 `Makefile` 也会重新生成这些文件, 再用更新后的文件生成 `compiler`.

`make` 如何判断文件是最新的呢? 假设我们已经执行过一次 `make`, 然后修改了 `lexer.l`, 再执行一次 `make`. `make` 会做以下操作:

- 检查 `compiler` 是否需要更新, 如果一个文件不存在, 或者它依赖的其他文件中, 有文件不存在或需要更新, 这个文件就需要更新.
  - `compiler` 存在, 依赖 `lexer.o`, `parser.o` 和 `ast.o`, 检查这三个文件是否需要更新.
    - `lexer.o` 存在, 依赖 `lexer.c`, 检查这个文件是否需要更新.
      - `lexer.c` 存在, 依赖 `lexer.l`, 检查这个文件是否需要更新.
        - `lexer.l` 存在, 被修改过. 这个文件没有对应的更新规则, 所以 `make` 不会去管它. 你可以理解为 `make` 认为这个文件已经被用户更新了.
      - `lexer.c` 需要更新, 执行 `flex -o lexer.c lexer.l` 来更新文件.
    - `lexer.o` 需要更新, 执行 `gcc lexer.c -c -o lexer.o` 来更新文件.
    - 检查 `parser.o` 和 `ast.o` 是否需要更新. 它们都不需要更新.
  - `compiler` 需要更新, 执行 `gcc lexer.o parser.o ast.o -o compiler` 来更新文件.
- 所有需要更新的文件都被更新过了, `make` 的任务完成了, 退出.

`make` 通过上述方式, 实现了 “只重新编译我们修改过的文件” 的功能, 节省了编译的时间.

当然, 你可能会觉得我们给出的这个示例 `Makefile` 写的太啰嗦了. 比如, 所有的 `.o` 文件其实都依赖于对应文件名的 `.c` 文件, 而且他们的构建方式也完全相同, 那我们为什么要把类似的规则重复写三遍呢? `make` 提供了一些语法来帮助我们简化 `Makefile` 的写法:

```makefile
compiler: lexer.o parser.o ast.o
    gcc $^ -o $@

%.o: %.c
    gcc $^ -c -o $@

lexer.c: lexer.l
    flex -o $@ $^

parser.c: parser.y
    bison -d -o $@ $^复制错误已复制
```

至于上面这些语法都是什么含义, 此处就不再赘述了, 你可以自行查看 `make` 手册中的相关部分 ([Pattern Rules](https://www.gnu.org/software/make/manual/make.html#Pattern-Rules) 和 [Automatic Variables](https://www.gnu.org/software/make/manual/make.html#Automatic-Variables)).

推荐:

- [GNU make](https://www.gnu.org/software/make/manual/make.html).