1. make和Makefile的关系
- 简单来讲 make 和 Makefile 是 解释器 和 脚本语言 的关系,在 ubuntu 下可以通过如下命令安装 make。
sudo apt install make
- make可以手动指定 脚本文件,也可以不指定 脚本文件 。
手动指定 脚本文件 的方法如下:
若不指定 脚本文件, make 则会在当当前 工作目录 下依次寻找名为 “GNUmakefile ”、“makefile ”、 “Makefile” 进行解释。# 手动指定脚本文件,文件名可以自己指定 # 如下三条指令是等效的 make -f filename make --f filename make --makefile filename
注意:推荐使用 Makefile作为文件名,首字母大写而比较显著。不推荐使用 GNUmakefile 因为以此命名的文件只有 GNU make 才可以识别,而其他版本的 make 程序只会在工作目录下 makefile 和 Makefile 这两个文件。
2. 规则 - Makefile语法的核心
Makefile 语法是由一个一个的 规则 构成的,其他的语法均是辅助完成规则的编写。
2.1 规则的语法
规则是由 目标(target) ,依赖(prerequisites),命令(command) 三部分组成,其中 依赖(prerequisites) 和 命令(command) 是可以省略的,本节记录规则的语法,暂不对其具体用法分析,本节列举了三种写法。
在一个规则中只有 目标(target) 是必须的,依赖(prerequisites) 和命令(command) 都是可以省略的
2.1.1 使用 TAB键标识命令的写法。
行首出现TAB键,标识后面跟着一个命令。使用TAB键标识命令,每条命令都会创建一个新的shell进程执行,因为TAB必须在行首,所以这种写法命令需要分布在多行。
-
有依赖的规则的写法:
-
省略依赖的规则的写法:
2.1.2 使用 “;” 标识命令的写法
使用 “;” 标识命令,每条命令前面都有一个 “;”, 使用 “;” 标识的命令则不会创建新的shell进程(第一个会创建),在当前shell进程中执行。命令必须写在一行,如果要分行写则要写换行符 “\”。
- 有依赖的规则的写法:
- 省略依赖的规则的写法:
2.1.3 混合写法
使用TAB标识的命令会创建新的进程并执行,使用 “;” 标识的命令则不会创建新的进程,在当前进程中执行执行
-
有依赖的规则的写法:
-
没有依赖的规则的写法:
2.2 make是如何解释规则的
Makefile 是由一个一个的 规则 构成, 可以这样理解, Makefile 文件中只有 规则, 其他的语法都是辅助。make 对 Makefile 的解释就是对规则的解释。
2.2.1 规则的设计思想
- 什么是 target(目标) ?
我们可以这样理解,target(目标) 即是 目标文件,我们为什么要费劲心思写一个 规则 ,肯定是有 目的 的;make 被设计的初衷是用来构建 c语言程序 的,所以 最终目标 是生成一个 可执行文件 。 - 什么是 依赖(prerequisites) ?
我们也应该将 依赖(prerequisites) 当做一个 文件 来理解,即 生成 目标文件 依赖的 文件,换句话说,要生成 目标文件 首先要有 依赖文件 ,如果没有 依赖文件 则 make 会将 依赖文件 作为 目标 去寻找可以生成此 依赖文件 的规则,依赖文件 再有依赖文件,最终形成一个规则的 依赖树 。 - 什么是命令(command)?
一条可以运行在 当前工作shell 中的 命令,暂时理解为生成 目标文件 的命令。
注:以上描述存在不准确的地方,旨在让大家对make的规则有一个感性的认识
2.2.2 make如何解释一个规则
- 没有依赖的规则
- make 会首先会在当前 工作路径 下寻找有没有 目标文件。如果没有则执行 当前规则下的 命令 , 如果有则退出并打印提示信息。
- 存在依赖的规则
- 先找到 依赖 对应的 规则 ,依赖 可能还有 依赖 , 继续找,找到最后一层没有 依赖 为止,因为一个 目标 可能存在多个 依赖,向下找的过程是一个树形分布。
- 找到最底层的规则后,则一层一层往上比较目标和依赖,若没有依赖则执行命令,若存在依赖则比较目标文件和依赖文件的时间戳,若依赖文件时间戳更加新则说明目标已经过时了,则执行对应命令。就这样一层一层往上解析规则,最终解析到最顶层结束退出。
- make对于目标和依赖处理的总结:
- 没有依赖判断有无目标文件,有则执行命令,无则不执行命令。
- 有依赖则检查目标与依赖的时间戳,依赖时间戳更新则执行命令
2.2.3 make对于命令的处理
- 规则和依赖的关系由make解析,命令则单独由make创建一个shell进程,由shell去解析,命令写了什么make并不知道。每条命令执行完,make会检查命令的返回码,命令执行成功则执行下一条命令(创建新的进程执行),执行失败则退出。
- TAB键修饰命令表示创建一个新的shell进程执行命令。“;” 修饰则表示不用创建新的shell进程,就在当前shell进程中执行命令。
- 在不同shell进程执行命令的示例
all : cd / # 进入根目录 pwd # 注意此处打印的是工作路径,而非 根目录 ,因为这两条命令在不同的进程中执行
- 在同一shell进程执行命令的示例
all : @cd / ; pwd # 这两条命令会在同一进程中执行,pwm命令会打印根目录路径。
2.2.4 make会首先解释哪个规则
- 可以通过命令行指定make解释哪条规则,make通过指定目标名的方式指定规则入口,如下所示:
make all # make会在Makefile文件下找到目标名为 “all” 的规则进行解析
make clean # make会在Makefile文件下找到目标名为 “clean” 的规则进行解析
- 没有通过命令行指定,则make会将Makefile第一个目标所在的规则作为规则入口。如果规则存在多个目标,第一个目标就是入口。
2.3 一个简单的Makefile示例
hello.out all : func.o main.o
gcc -o hello.out func.o main.o
func.o : func.c
gcc -o func.o -c func.c
main.o : main.c
gcc -o main.o -c main.c
3. 本文总结
- Makefile 的核心语法是规则,其他语法都是为为描述规则语法进行服务,本文描述了make的工作机制,make是如何解释一个规则的,本章是后续学习的基础。
- 如有描述错误的地方欢迎指正,共同学习。