1.0 Makefile 基础

在 Linux 下,通过 make 命令即可实现通过 Makefile 文件定义的规则自动化执行任意命令(包括编译)。

即通过一个命令一个文件即可实现预定义好的任意操作。

1.1 规则

Makefile 由若干条规则(Rule)构成,每条规则指出一个目标文件(Target),若干依赖文件(Prerequisites),以及生成目标文件的命令。

例如,如果需要生成文件 m.txt 其由 a.txtb.txt 合并而来,则有如下规则:

# 目标文件:依赖文件 1 依赖文件 2
m.txt: a.txt b.txt
    cat a.txt b.txt > m.txt

第一行定义了一条规则,第二行通过tab规定了一条命令,使用 cat 合并两个文件,其中 # 开头的为注释 make命令会忽略。

在 makefile 中命令必须使用 tab 定义,空格定义的无效。

由于 make 执行时默认执行第一条规则,所以仿照上述的方法可以写出第二个规则:

x.txt: m.txt c.txt
    cat m.txt c.txt > x.txt

m.txt: a.txt b.txt
    cat a.txt b.txt > m.txt

此时在系统内某一文件夹创建 a.txtb.txtc.txt,输入一些内容执行 make

make 默认执行第一条规则,在创建 x.txt 的时候发现 m.txt 不存在,因此先执行规则 m.txt 创建 m.txt 文件,再执行规则 x.txt

综上可见,Makefile 定义了一系列的规则,在每个规则满足目标的前提下执行命令,就能创建出一个目标文件。

在编写 Makefile 的时候把默认执行的规则放在第一条,其它的规则顺序无关紧要,因为 make 在执行的时候会自行判断依赖。

此外 make 会打印出每一条执行的命令,便于观察执行顺序。

在上述 make 命令执行完毕后再次执行 make 后,输出如下:

$ make
make: “x.txt”已是最新。

make 检测到 x.txt 为最新的版本,无需再次执行,因为 x.txt 的创建时间晚于它依赖文件的最后修改时间。

可见,make 使用文件的创建和修改时间来判断是否该更新一个目标文件。

如果修改任意依赖文件后,再次执行 make 命令即可触发 x.txt 的更新。

假设修改了 c.txt 文件,则执行 make 命令后会触发 x.txt 的更新,并不会触发 m.txt 的更新,因为 m.txt 的依赖文件并没有变化,所以,make 会根据 Makefile 执行必要的规则,并非无脑执行所有规则。

在编译大型程序时,完全编译一次往往需要几十分钟甚至数小时,若每次对文件修改后都要执行完全编译则太过于浪费时间,Makefile 的存在即可大幅节省编译时间。

当然,对于 Makefile 是否能高效的完成增量更新,取决于规则书写的是否正确,make 本身并不会检查规则逻辑是否正确。

1.2 伪目标

在上述的例子中,m.txtx.txt 是自动生成的文件,所以可以安全地删除。

但是如果我们希望自动删除,则可以编写一个 clean 规则来删除它们:

clean:
    rm -f m.txt
    rm -f x.txt

clear 命令,与上面所用的命令不同,其没有依赖文件,因此需要执行 clean 命令时必须在命令行使用命令 make clean

值得注意的是,如果在执行 clean 的时候,目录下并没有 clean 文件,每次执行 make clean 命令时都会执行 Makefile 中这个规则的命令。

所以,如果目录下有一个名为 clean 的文件,则此时 Makefile 中的 clean 规则就不会执行了!

如果目录下既需要 clean 文件,还想正常使用 Makefile 文件的 clean 规则,则可以添加一个标识:

.PHONY: clean
clean:
    rm -f m.txt
    rm -f x.txt

此时的 clean 就不会被视为一个文件,而是伪目标(Phony Target)

在大型项目中约定俗成下将:cleaninstall 这些视为伪目标名称,方便用户快速执行对应的任务。

一般不会有人在目录下创建名为 clean 的文件,除非有人蓄意搞破坏。

1.3 执行多条命令

1.4 控制打印

1.5 控制错误

1.6 小结

编写 Makefile 就是编写一系列的规则,用来告诉 make 该如何执行这些规则,从而生成我们最终需要的文件。