makefile: 管理项目基本用法
1、 基本介绍
使用 GCC 的命令行进行程序编译在单个文件下是比较方便的,当工程中的文件逐渐增多,甚至变得十分庞大的时候,使用 GCC 命令编译就会变得力不从心。这种情况下我们需要借助项目构造工具 make 帮助我们完成这个艰巨的任务。 make是一个命令工具,是一个解释makefile中指令的命令工具。
make工具在构造项目的时候需要加载一个叫做makefile的文件,makefile关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。
makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。makefile文件有两种命名方式 makefile 和 Makefile,构建项目的时候在哪个目录下执行构建命令 make这个目录下的 makefile 文件就会别加载,因此在一个项目中可以有多个 makefile 文件,分别位于不同的项目目录中。
2、基本用法
命名:makefile Makefile (make) 命令
**1 个规则:**依赖条件(一个tab缩进)命令
-
目标的时间必须晚于依赖条件的时间,否则,更新目标
-
依赖条件如果不存在,找寻新的规则去产生依赖条件。 ALL:指定 makefile 的终极目标。否则终极目标是第一个目标
2 个函数:
src = $(wildcard ./*.c): 匹配当前工作目录下的所有.c 文件。将文件名组成列表,赋值给变量 src。src = add.c sub.c div1.c
obj = $(patsubst %.c, %.o, $(src)): 将参数3中,包含参数1的部分,替换为参数2。obj = add.o sub.o div1.o
clean: (没有依赖) -rm -rf $(obj) a.out “-”:作用是,删除不存在文件时,不报错。顺序执行结束。 make clean -n 模拟执行3 个自动变量:
$@: 在规则的命令中,表示规则中的目标。
$^: 在规则的命令中,表示所有依赖条件。
$<: 在规则的命令中,表示第一个依赖条件。如果将该变量应用在模式规则中,它可将依赖条件列表中的依赖依次取出,套用模式规则。模式规则:
%.o:%.c gcc -c $< -o $@**静态模式规则:**大目标是obj,这个obj就是各种.o文件,然后%.o就是具体的解释,而%.c就是对应同样名字的.c文件
$(obj):%.o:%.c gcc -c $< -o $@**伪目标:**告诉make,.PHONY:后面跟着的名称不是指文件名
.PHONY: clean ALL
参数: -n:模拟执行make、make clean 命令。
-f:指定文件执行 make 命令。 xxxx.mk3、详细用法
3.1.、规则
Makefile的框架是由规则构成的。make命令执行时先在Makefile文件中查找各种规则,对各种规则进行解析后运行规则。规则的基本格式为:
target1,target2...: depend1, depend2, ... command ...... ......每条规则由三个部分组成分别是目标(target), 依赖(depend)和命令(command)。
-
命令(command): 当前这条规则的动作,一般情况下这个动作就是一个 shell 命令- 例如:通过某个命令编译文件、生成库文件、进入目录等。
- 动作可以是多个,
每个命令前必须有一个Tab缩进并且独占占一行。
-
依赖(depend): 规则所必需的依赖条件,在规则的命令中可以使用这些依赖。- 例如:生成可执行文件的目标文件(
*.o)可以作为依赖使用 - 如果规则的命令中不需要任何依赖,那么规则的依赖可以为空
- 当前规则中的依赖可以是其他规则中的某个目标,这样就形成了规则之间的嵌套
- 依赖可以根据要执行的命令的实际需求, 指定很多个
- 例如:生成可执行文件的目标文件(
-
目标(target): 规则中的目标,这个目标和规则中的命令是对应的- 通过执行规则中的命令,可以生成一个和目标同名的文件
- 规则中可以有多个命令, 因此可以通过这多条命令来生成多个目标, 所有目标也可以有很多个
- 通过执行规则中的命令,可以只执行一个动作,不生成任何文件,这样的目标被称为
伪目标
3.2、 工作原理
在此主要为大家剖析一下通过提供的 makefile 文件,构建工具 make 什么时候编译项目中的所有文件, 什么时候只选择更新项目中的某几个文件。另外再研究一下如果makefile里边有多个规则它们之间是如何配合工作的,我们基于下边的例子,依次进行讲解。
make 命令执行的时候会根据文件的时间戳判定是否执行makefile文件中相关规则中的命令。
- 目标是通过依赖生成的,因此
正常情况下:目标时间戳 > 所有依赖的时间戳, 如果执行 make 命令的时候检测到规则中的目标和依赖满足这个条件, 那么规则中的命令就不会被执行。 - 当依赖文件被更新了, 文件时间戳也会随之被更新, 这时候
目标时间戳 < 某些依赖的时间戳, 在这种情况下目标文件会通过规则中的命令被重新生成。 - 如果规则中的目标对应的文件根本就不存在, 那么规则中的命令肯定会被执行。
3.3、自动推导
make 是一个功能强大的构建工具,虽然make需要根据 makefile 中指定的规则来完成源文件的编译。作为小白的我们编写makefile的时候难免写的不是那么严谨从而漏写一些构建规则,但是我们会发现程序还是会被编译成功。这是因为 make 有自动推导的能力,不会完全依赖 makefile。
比如: 使用命令 make 编译扩展名为.c 的 C 语言文件的时候,源文件的编译规则不用明确给出。这是因为 make 进行编译的时候会使用一个默认的编译规则,按照默认规则完成对.c文件的编译,生成对应的.o 文件。它使用命令cc -c来编译.c 源文件。在 Makefile 中只要给出需要构建的目标文件名(一个.o 文件),make 会自动为这个.o 文件寻找合适的依赖文件(对应的.c 文件),并且使用默认的命令来构建这个目标文件。
假设本地项目目录中有以下几个源文件:
$ tree.├── add.c├── div.c├── head.h├── main.c├── makefile├── mult.c└── sub.c目录中 makefile 文件内容如下
calc:add.o div.o main.o mult.o sub.o gcc add.o div.o main.o mult.o sub.o -o calc通过make构建项目:
$ makecc -c -o add.o add.ccc -c -o div.o div.ccc -c -o main.o main.ccc -c -o mult.o mult.ccc -c -o sub.o sub.cgcc add.o div.o main.o mult.o sub.o -o calc我们可以发现上边的 makefile 文件中只有一条规则, 依赖中所有的
.o文件在本地项目目录中是不存在的, 并且也没有其他的规则用来生成这些依赖文件, 这时候 make 会使用内部默认的构造规则先将这些依赖文件生成出来, 然后在执行规则中的命令, 最后生成目标文件 calc。
3.4、 变量
使用 Makefile 进行规则定义的时候,为了写起来更加灵活,我们可以在里边使用变量。makefile中的变量分为三种:自定义变量,预定义变量和自动变量。
自定义变量用 Makefile 进行规则定义的时候,用户可以定义自己的变量,称为用户自定义变量。makefile 中的变量是没有类型的,直接创建变量然后给其赋值就可以了。
变量名=变量值
预定义变量 在 Makefile 中有一些已经定义的变量,用户可以直接使用这些变量,不用进行定义。在进行编译的时候,某些条件下 Makefile 会使用这些预定义变量的值进行编译。这些预定义变量的名字一般都是大写的,经常采用的预定义变量如下表所示:
| 变 量 名 | 含 义 | 默 认 值 |
|---|---|---|
| AR | 生成静态库库文件的程序名称 | ar |
| AS | 汇编编译器的名称 | as |
| CC | C 语言编译器的名称 | cc |
| CPP | C 语言预编译器的名称 | $(CC) -E |
| CXX | C++语言编译器的名称 | g++ |
| FC | FORTRAN 语言编译器的名称 | f77 |
| RM | 删除文件程序的名称 | rm -f |
| ARFLAGS | 生成静态库库文件程序的选项 | 无默认值 |
| ASFLAGS | 汇编语言编译器的编译选项 | 无默认值 |
| CFLAGS | C 语言编译器的编译选项 | 无默认值 |
| CPPFLAGS | C 语言预编译的编译选项 | 无默认值 |
| CXXFLAGS | C++语言编译器的编译选项 | 无默认值 |
| FFLAGS | FORTRAN 语言编译器的编译选项 | 无默认 |
自动变量:Makefile 中的变量除了用户自定义变量和预定义变量外,还有一类自动变量。Makefile 中的规则语句中经常会出现目标文件和依赖文件,
自动变量用来代表这些规则中的目标文件和依赖文件,并且它们只能在规则的命令中使用。下表中是一些常见的自动变量。
| 变 量 | 含 义 |
|---|---|
| $* | 表示目标文件的名称,不包含目标文件的扩展名 |
| $+ | 表示所有的依赖文件,这些依赖文件之间以空格分开,按照出现的先后为顺序,其中可能 包含重复的依赖文件 |
| $< | 表示依赖项中第一个依赖文件的名称 |
| $? | 依赖项中,所有比目标文件时间戳晚的依赖文件,依赖文件之间以空格分开 |
| $@ | 表示目标文件的名称,包含文件扩展名 |
| $^ | 依赖项中,所有不重复的依赖文件,这些文件之间以空格分开 |
3.5、 模式匹配
在介绍概念之前, 先读一下下面的这个 makefile 文件:
calc:add.o div.o main.o mult.o sub.o gcc add.o div.o main.o mult.o sub.o -o calc
add.o:add.c gcc add.c -c
div.o:div.c gcc div.c -c
main.o:main.c gcc main.c -c
sub.o:sub.c gcc sub.c -c
mult.o:mult.c gcc mult.c -c在阅读过程中能够发现从第二个规则开始到第六个规则做的是相同的事情, 但是由于文件名不同不得不在文件中写出多个规则,这就让 makefile 文件看起来非常的冗余,我们可以将这一系列的相同操作整理成一个模板,所有类似的操作都通过模板去匹配 makefile 会因此而精简不少,只是可读性会有所下降。
这个规则模板可以写成下边的样子,这种操作就称之为模式匹配。
%.o:%.c gcc $< -c
3.6、函数
makefile中有很多函数并且
所有的函数都是有返回值的。makefile中函数的格式和C/C++中函数也不同,其写法是这样的:$(函数名 参数1, 参数2, 参数3, ...),主要目的是让我们能够快速方便的得到函数的返回值。这里为大家介绍两个 makefile 中使用频率比较高的函数:
wildcard和patsubst。
wildcard
这个函数的主要作用是获取指定目录下指定类型的文件名,其返回值是以空格分割的、指定目录下的所有符合条件的文件名列表。函数原型如下:
$(wildcard PATTERN...) 参数: 指定某个目录, 搜索这个路径下指定类型的文件,比如: *.c-
参数功能:
- PATTERN 指的是某个或多个目录下的对应的某种类型的文件, 比如
当前目录下的.c文件可以写成*.c - 可以指定多个目录,每个路径之间使用空格间隔
- PATTERN 指的是某个或多个目录下的对应的某种类型的文件, 比如
-
返回值:
- 得到的若干个文件的文件列表, 文件名之间使用空格间隔
- 示例:
$(wildcard *.c ./sub/*.c)- 返回值格式: a.c b.c c.c d.c e.c f.c ./sub/aa.c ./sub/bb.c
patsubst
这个函数的功能是按照指定的模式替换指定的文件名的后缀, 函数原型如下:
$(patsubst <pattern>,<replacement>,<text>)- 参数功能:
- pattern: 这是一个模式字符串, 需要指定出要被替换的文件名中的后缀是什么
- 文件名和路径不需要关心, 因此使用 % 表示即可 [通配符是 %]
- 在通配符后边指定出要被替换的后缀, 比如: %.c, 意味着 .c的后缀要被替换掉
- replacement: 这是一个模式字符串, 指定参数pattern中的后缀最终要被替换为什么
- 还是使用 % 来表示参数pattern 中文件的路径和名字
- 在通配符 % 后边指定出新的后缀名, 比如: %.o 这表示原来的后缀被替换为 .o
- text: 该参数中存储这要被替换的原始数据
- 返回值:
- 函数返回被替换过后的字符串。
- pattern: 这是一个模式字符串, 需要指定出要被替换的文件名中的后缀是什么