当文件有几十、上百甚至上万个的时候用终端输入 gcc 命令的方法显然是不现实的,为此提出了一个解决大工程编译的工具:make,描述哪些文件需要编译、哪些需要重新编译的文件就叫做 Makefile,Makefile 就跟脚本文件一样,Makefile 里面还可以执行系统命令。使用的时候只需要一个 make 命令即可完成整个工程的自动编译,极大的提高了软件开发的效率。
一、编辑 Makefile 文件
在工程目录下创建Makefile
文件:
Makefile 和 .c 文件是处于同一个目录的,在Makefile文件中输入如下代码:
main: main.o fun1.o fun2.o
gcc -o main main.o fun1.o fun2.o
main.o: main.c
gcc -c main.c
fun1.o: fun1.c
gcc -c fun1.c
fun2.o: fun2.c
gcc -c fun2.c
clean:
rm *.o
rm main
上述代码中所有行首需要空出来的地方一定要使用 “TAB” 键!不要使用空格键!这是 Makefile 的语法要求
二、make
Makefile 编写好以后我们就可以使用make
命令来编译我们的工程了,直接在命令行中输入make
即可,make 命令会在当前目录下查找是否存在 “Makefile” 这个文件,如果存在的话就会按照 Makefile 里面定义的编译方式进行编译:
make
此时我们修改一下 fun1.c 的文件源码,再make
:
可以看出因为我们修改了fun1.c
这个文件,所以fun1.c
和最后的可执行文件main
重新编译了,其它没有修改过的文件就没有编译,而且我们只需要输入make
命令即可,非常方便。
三、make clean
make clean
四、Makefile 基本语法
1、Makefile 规则格式:
目标...: 依赖文件集合...
命令1
命令2
......
!!命令列表中的每条命令必须以 TAB 键开始,不能使用空格!
- 比如:
main: main.o fun1.o fun2.o
gcc -o main main.o fun1.o fun2.o
这条规则的目标是main
,main.o
、fun1.o
、fun2.o
是生成main
的依赖文件,如果要更新目标main
,就必须先更新它的所有依赖文件,如果依赖文件中的任何一个有更新,那么目标也必须更新,“更新”就是执行一遍规则中的命令列表。make
命令会为Makefile
中的每个以 TAB 开始的命令创建一个 Shell 进程去执行。
- 示例:
main: main.o fun1.o fun2.o
gcc -o main main.o fun1.o fun2.o
main.o: main.c
gcc -c main.c
fun1.o: fun1.c
gcc -c fun1.c
fun2.o: fun2.c
gcc -c fun2.c
clean:
rm *.o
rm main
make
命令在执行这个Makefile
的时候其执行步骤如下:首先更新第一条规则中的main
,第一条规则的目标成为默认目标,只要默认目标更新了那么就认为Makefile
的工作完成。在第一次编译的时候由于main
还不存在,因此第一条规则会执行,第一条规则依赖于文件main.o
、fun1.o
和fun2.o
这个三个.o
文件,这三个.o
文件目前还都没有,因此必须先更新这三个文件。make 会查找以这三个.o
文件为目标的规则并执行。以main.o
为例,发现更新main.o
的是第二条规则,因此会执行第二条规则,第二条规则里面的命令为“gcc –c main.c”,这行命令就是不链接编译main.c
,生成main.o
。最后一个规则目标是clean
,它没有依赖文件,因此会默认为依赖文件都是最新的,所以其对应的命令不会执行,当我们想要执行clean
的话可以直接使用命令make clean
,执行以后就会删除当前目录下所有的.o
文件以及main
,因此clean
的功能就是完成工程的清理工作。
- 总结
make
执行过程:
① make
命令会在当前目录下查找以 Makefile(或makefile) 命名的文件。
② 当找到 Makefile 文件以后就会按照 Makefile 中定义的规则去编译生成最终的目标文件。
③ 当发现目标文件不存在,或者目标所依赖的文件比目标文件新(也就是最后修改时间比目标文件晚)的话就会执行后面的命令来更新目标。
!!注意:
除了 Makefile 的“终极目标”所在的规则以外,其它规则的顺序在 Makefile 中是没有意义的,“终极目标”就是指在使用 make 命令的时候没有指定具体的目标时,make 默认的那个目标,它是 Makefile 文件中第一条规则的目标,如果 Makefile 中的第一个规则有多个目标,那么这些目标中的第一个目标就是 make 的“终极目标”。
2、Makefile 变量:
跟 C 语言一样 Makefile 也是支持变量的,如前面的例子:
main: main.o fun1.o fun2.o
gcc -o main main.o fun1.o fun2.o
上述 Makefile 语句中,main.o、fun1.o、fun2.o 这三个依赖文件,我们输入了两遍,我们这个 Makefile 比较小,如果 Makefile 复杂的时候这种重复输入的工作就会非常费时间,而且容易出错,为了解决这个问题,Makefile 加入了变量支持。Makefile 中的变量都是字符串,类似 C 语言中的宏。使用变量将上面的代码修改:
# Makefile 变量的使用
objects = main.o fun1.o fun2.o
main: $(objects)
gcc -o main $(objects)
Makefile 的注释用#
。我们定义了一个变量objects
,并且给这个变量进行了赋值,其值为字符串main.o fun1.o fun2.o
,Makefile 中变量的引用方法是$(变量名)
,如本例中:$(objects)
。
3、赋值符:
① 赋值符=
:
name = lcx
curname = $(name)
name = licx
print:
@echo curname: $(curname)
第一行定义了一个变量name
,值为lcx
,第二行定义了变量curname
,值也为lcx
,第三行变量name
的值改为licx
,第五、六行是输出变量curname
的值。在 Makefile 要输出一串字符的话使用echo
。(第六行中的echo
前面加了个@
符号,因为 Make 在执行的过程中会自动输出命令执行过程,在命令前加上@
就不会输出命令执行过程)执行结果:
可以看出,
curname
的值不是lcx
,而是变量name
最后一次赋值的结果,所以=
赋值,变量的真实值取决于它所引用的变量的最后一次有效值。
② 赋值符:=
:
name = lcx
curname := $(name)
name = licx
print:
@echo curname: $(curname)
执行结果:
显然,此时
curname
值为lcx
,而不是licx
,这是因为赋值符:=
不会使用引用后来改变的值,只能使用前面定义的值。
③ 赋值符?=
:
curname ?= licx
上述代码的意思是,如果变量curname
前面没有被赋值,那么此变量就是licx
,如果前面已经赋过值了,那么就使用前面赋的值。
④ 变量追加符+=
:
Makefile 中的变量是字符串,有时候我们需要给前面已经定义好的变量添加一些字符串,此时就要使用变量追加符号+=
,如:
objects = main.o fun1.o
objects += fun2.o
print:
@echo objects: $(objects)
可以看出,
objects
最后的值为main.o fun1.o fun2.o
。
4、Makefile 模式规则:
有如下示例代码:
objects = main.o fun1.o fun2.o
main: $(objects)
gcc -o main $(objects)
main.o: main.c
gcc -c main.c
fun1.o: fun1.c
gcc -c fun1.c
fun2.o: fun2.c
gcc -c fun2.c
clean:
rm *.o
rm main
显然,如果工程中.c
文件很多,这样做就很不方便,为此,我们可以使用 Makefile 中的模式规则,通过模式规则我们就可以使用一条规则来将所有的.c
文件编译为对应的.o
文件。
模式规则中,至少在规则的目标定义中要包含%
,否则就是一般规则,目标中的%
表示对文件名的匹配,%
表示长度任意的非空字符串,比如%.c
就是所有的以.c
结尾的文件,类似于通配符。
示例可以改成如下形式:
objects = main.o fun1.o fun2.o
main: $(objects)
gcc -o main $(objects)
%.o: %.c
#命令
clean:
rm *.o
rm main
第六行的命令我们需要借助下面的自动化变量 ↓↓↓
5、Makefile 自动化变量:
上面讲的模式规则中,目标和依赖都是一系列的文件,每一次对模式规则进行解析的时候都会是不同的目标和依赖文件,而命令只有一行,那么如何通过一行命令来从不同的依赖文件中生成对应的目标?自动化变量就是完成这个功能的!所谓自动化变量就是这种变量会把模中所定义的一系列的文件自动的挨个取出,直至所有的符合模式的文件都取完,自动化变量只应该出现在规则的命令中,常用的自动化变量如下:
常用三个:
$@
$<
$^
那么上述代码我们可以完善:
objects = main.o fun1.o fun2.o
main: $(objects)
gcc -o main $(objects)
%.o: %.c
gcc -c $<
clean:
rm *.o
rm main
6、Makefile 伪目标:
Makefile 有一种特殊的目标——伪目标,一般的目标名都是要生成的文件,而伪目标不代表真正的目标名,在执行make
命令的时候通过指定这个伪目标来执行其所在规则的定义的命令。
使用伪目标的主要是为了避免 Makefile 中定义的只执行命令的目标和工作目录下的实际文件出现名字冲突,有时候我们需要编写一个规则用来执行一些命令,但是这个规则不是用来创建文件的,比如文章示例有如下代码用来完成清理工程的功能:
clean:
rm *.o
rm main
上述规则中并没有创建clean
文件的命令,因此工作目录下永远都不会存在文件clean
,当我们输入make clean
以后,后面的rm *.o
和rm main
总是会执行。可是如果我们在工作目录下创建一个名为clean
的文件,那就不一样了,当执行make clean
的时候,规则因为没有依赖文件,所以目标被认为是最新的,因此后面的rm
命令也就不会执行,我们预先设想的清理工程的功能也就无法完成。为了避免这个问题,我们可以将clean
声明为伪目标,声明方式如下:
.PHONY:clean
那么我们可以将示例代码修改为:
.PHONY : clean
clean:
rm *.o
rm main
文章参考自:【正点原子】I.MX6U嵌入式Linux开发指南