在Linux下可以使用gcc来编译源程序,当源文件不多的时候,gcc可以承担编译工作。但一个项目的源文件不计其数,使用gcc编译效率低下,于是就需要使用Makefile。
Makefile定义了一系列的规则来指定:哪些文件需要先编译,哪些文件需要后编译,甚至于进行更复杂的功能操作。Makefile的好处就是 “ 自动化编译 ” ,一旦写好,只需要一个make命令,整个工程完全自动编译,极大地提高了软件开发效率。
make是一条命令,Makefile是一个文件,两个工具搭配使用,完成项目的自动化构建。
创建Makefile
创建Makefile文件:
touch Makefile
在UNIX系统中,习惯使用Makefile作为makefile文件。如果要使用其它文件作为makefile,则可利用类似下面的make命令选项指定makefile文件(通过参数-f指定我们的Makefile文件):
假如我们写的makefile文件名字叫做lib.mk:
make -f lib.mk
需要在Makefile文件中阐明多个文件之间的依赖关系&依赖方法。
- 依赖关系:文件A的变更会影响到文件B,那么就称文件B依赖于文件A。
例如:hello文件是由hello.c文件通过预处理,编译,汇编,链接之后生成的文件,所以hello.c文件的变更会影响hello文件,所以说hello文件依赖于hello.c文件。
- 依赖方法:如果文件B依赖于文件A,那么通过文件A得到文件B的方法,就是文件B依赖于文件A的依赖方法。
例如:hello文件依赖于hello.c文件,而hello.c通过 gcc hello.c -o hello 指令就可以得到hello,那么hello依赖于hello.c 的依赖方法就是 gcc hello.c -o hello
vim Makefile 打开Makefile文件,并在其中编辑好hello 和 hello.c 的依赖关系和依赖方法。
书写规则
一个基本规则:
目标:依赖条件
(一个Tab缩进)命令(依赖方法)
hello:hello.c
gcc hello.c -o hello
两个常用函数:
- wildcard参数:按给定参数匹配文件名。
例如:src,匹配当前目录下所有 .c 文件,将文件名组成列表赋值给src
src:=$(wildcard src/*.c)
- patsubst 参数1,参数2,参数3:将参数3中,模式字符串替换,包含参数1的部分,替换为参数2。
例如:当前目录所有的 .c 文件替换成 .o文件赋值给obj
obj:=$(patsubst %.c,%.o,$(wildcard src/*.c))
$(wildcard src/*.c)会匹配src目录下的所有以 .c 结尾的文件,然后$(patsubst %.c, %.o, ...)将匹配到的文件名中的 .c 替换为 .o,最终得到一个以 .o 结尾的文件列表,赋值给变量 obj。
三个自动变量:
- $@:在规则的命令中,表示规则中要生成的目标文件(冒号左侧)
//编译.c文件生成.o文件
%.o:%.c
gcc -c $< -o $@
//$@表示目标文件的名
//$<表示第一个依赖条件(.c文件)
//-o $@表示生成的目标文件
- $^:在规则的命令中,表示所有依赖条件(冒号右侧)
//链接目标文件生成可执行文件
my_program:file1.o file2.o file3.o
gcc $^ -o $@
//$^表示所有依赖文件的列表
//$@表示目标文件(my_program)
- $<:在规则的命令中,第一个依赖条件(如果将该变量应用在模式规则中它可将依赖条件列表中的依赖依次取出,套用模式规则)
//编译.c文件生成.o文件
%.o:%.c
gcc -c $< -o $@
//$<表示第一个依赖文件(.c文件)
//-o $@表示生成的目标文件
项目清理
在每次重新生成可执行程序前,都应该将上一次生成可执行程序时生成的一系列文件进行清理,每次清理时执行的都是相同的清理指令,这时我们可以将项目清理的指令也加入到Makefile文件当中。
hello:hello.c
gcc hello.c -o hello
.PHONY:clean
clean:
rm -f hello
显示执行make clean 指令,可执行文件hello被清除。
通配符
如果我们想定义一系列比较类似的文件,我们可以使用通配符,make最常用的通配符就是* 。
通配符代替了一系列文件,如:*.c表示所有后缀为c的文件。一个需要我们注意的是,如果我们的文件名中有通配符,如:*,那么可以使用转义字符 \ ,如 \*来表示真实的 *字符,而不是任意长度的字符串。
clean:
rm -f *.o
伪目标
正像我们前面例子中的 “clean” 一样,既然我们生成了许多文件编译文件,我们也应该提供一个清除它们的“目标”以备完整地重编译而用。(以“make clean”来使用该目标)
因为,我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显式地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。
为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显式地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。
.PHONY:clean
//特性是:总是被执行的
变量的定义与使用
Makefile中的变量的使用其实非常的简单,因为它并没有像其它语言那样定义变量的时候需要使用数据类型。变量的名称可以由大小写字母、阿拉伯数字和下划线构成。等号左右的空白符没有明确的要求,因为在执行 make 的时候多余的空白符会被自动的删除。至于值列表,既可以是零项,又可以是一项或者是多项。如:
OBJ = add.o sub.o
调用变量的时候可以用 "$(OBJ)"(常用) 或者是 "${OBJ}" 来替换,这就是变量的引用。
OBJ = add.o sub.o main.o #所有的依赖条件
main:$(OBJ)
gcc $(OBJ) -o main
这就是引用变量后的 Makefile 的编写,比我们之前的编写方式要简单的多。当要添加或者是删除某个依赖文件的时候,我们只需要改变变量 "OBJ" 的值就可以了。
在Makefile中#表示注释
make命令执行机制
stat指令
我们可以通过stat指令来查看源文件和可执行文件的所有属性,重点关注如下属性:
- Access:最后一次访问该文件的时间
- Change:最后一次改变文件属性的时间
- Modify:最后一次修改文件内容的时间
make命令执行
当使用make命令生成目标文件后,再次make就会得到:' hello ' is up to date (hello文件已经是最新的)信息。
那么make指令是否执行,取决于Modify时间谁更新。
目标文件hello的Modify时间在hello.c文件的Modify时间之后,所以make指令不执行,当目标文件hello的Modify时间在hello.c文件的Modify时间之前,make指令执行,无论目标文件是否存在。