目录
一、背景
二、基本使用
2.1 make/makefile是什么
2.2 makefile的基本概念和使用
2.3 项目清理
2.4 理解总是被执行
2.5 推导过程
三、扩展
3.1 makefile文件中变量的使用
3.2 扩展一
3.3 扩展二
3.4 扩展三
3.5 扩展四
3.6 扩展五
四、总结
一、背景
- 会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力
- 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
- makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
- make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
- make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。
二、基本使用
2.1 make/makefile是什么
make是一个命令,makefile是一个文件。
2.2 makefile的基本概念和使用
首先我们创建一个code.c的源文件,如下所示:
如果我们需要编辑的话就需要使用gcc或者g++;gcc需要我们自己来敲,如果当前工作目录下的源文件非常多的话,一个一个敲太浪费时间了,效率太低了。
那么当源文件非常多的时候,我们如何编译呢?
我们可以使用自动化构建工具-make来创造一个makefile(Makefile)文件,如下所示:
我们执行make命令之后,就会在当前工作目录下查找一个makefile的文件,然后根据makefile文件里面的内容生成code来运行。
在我们编写的makefile文件中:
第一行:code:code.c表示依赖关系,我们要产生code文件需要依赖code.c文件
第二行:gcc code.c -o code表示依赖方法,从code.c到code需要用到的方法,必须以tab键开头
在makefile文件中必须要有依赖方法和依赖关系。
2.3 项目清理
我们不仅想让代码进行编译,我们还想让代码进行清理,清理如下所示:
在我们执行make clean之前,我们当前目录下是由code文件的,当我们执行make clean之后,就会执行rm -f code这一条命令来清理code文件。
此时我们就可以将一个源文件使用make和make clean的命令组合,就可以自动化的进行代码的编译和清理了,如下所示:
第一行:.PHONY:clean表示使用.PHONY来修饰clean,叫做伪目标。
第二行:clean:表示依赖关系,冒号后面为空,表示clean不需要依赖任何文件
第三行:rm -f code表示依赖方法,
问题一:为什么我们想要产生code文件只需要make呢?而clean需要执行make clean呢?
我们将makefile文件中的代码顺序换一下,如下所示:
然后再次执行make命令,就会执行rm -f code这条指令了,如下所示:
然后make code就是执行gcc code.c -o code这条命令了,如下所示:
make命令在扫描makefile文件的时候,是从上往下扫描,默认形成第一个目标文件,也就是使用第一个目标文件的依赖关系形成第一个目标文件。一旦把第一个执行了,后面的依赖关系生成的依赖文件就不执行了,除非你给make传入一个目标文件,你要形成谁,例如make code。
所以,我们默认情况下,写makefile文件时喜欢把我们形成的可执行程序放在最前面。如下所示:
问题二:.PHONY:clean是什么意思?
我们在makefile文件中将这个去掉,如下所示:
然后使用make和make clean来编译,执行和清理文件,如下所示:
发现并不影响我们make的具体操作,为什么makefile文件里面需要加上.PHONY:clean来声明clean是伪目标呢?
首先.PHONY类似一种建议的关键字,来建议这个clean总是被执行,表示对应的依赖关系和依赖方法总是被执行。当我们执行make clean的时候,表示这组依赖关系rm -f code总是会被执行。
2.4 理解总是被执行
在理解总是被执行之前,我们先理解一下总是不被执行,如下所示:
在我们的makefile文件里面,有两个目标文件,一个是code,一个是clean,其中code是没有被.PYHOY修饰的。
我们在第一次执行make来执行编译命令之后,后面的三次使用make来执行编译命令就不让我来执行了。
我们可以在使用.PYHOY来修饰code目标文件,然后再次执行多次make命令,如下所示:
我们发现,我们使用.PYHOY来修饰code目标文件之后,每次make都会执行对应的依赖方法。
问题一:为什么我们在第一次执行make来执行编译命令之后,后面的三次使用make来执行编译命令就不让我来执行了呢?
因为我的源代码没有被更新过,没有更新过的话,就没有必要重新编译形成目标文件。也就是说,我的源代码没有被更新过,目标文件也就不需要重新编译形成。因为我的文件没有改过,为什么要重新再次编译呢?只有在改了源文件之后,才需要重新编译。当我们有100个源文件,我们只修改了10个,再次使用make命令来编辑,就只会重新编译那10个修改过的文件,来提高编译效率。所以我们在形成可执行程序的时候默认不使用.PYHOY来修饰,来避免总是被执行,降低效率。
问题二:make怎么知道目标文件code和源文件code.c谁新谁旧呢?
在linux系统中,一切皆文件,而其中每一个文件都有相关联的三个时间。我们可以使用stat命令来查看,如下所示:
三个时间分别是Access,Modify,Change。
我们知道文件=内容+属性
如果只修改文件的内容Modify时间就会被更新。
如果只修改文件的属性Change时间就会被更新。
如果不对文件属性和内容做修改,只查看文件内容,Access时间就会被更新(不会每一次查看就会更新,一般是查找多次才会被更新)。
一般我们修改文件的内容,会更新Modify时间和Change时间,因为修改内容之后,文件大小会发生变化,Modify时间也会发生变化,文件大小是文件的属性,Modify时间也是文件的属性。
我们回到问题,make怎么知道目标文件code和源文件code.c谁新谁旧呢?因为每一个文件都用三个时间来表示文件在哪一个时间被修改了,make命令在执行依赖方法会查看Modify时间
如果code的Modify时间在code.c的Modify时间之前,就会执行依赖方法,
如果code的Modify时间在code.c的Modify时间之后,就会不会执行依赖方法,
总结:能不能编译,主要就是看目标文件和源文件的Modify时间
我们可以使用touch来修改文件的时间,如下所示:
我们在使用.PYHOY来修饰目标文件之后,就会让make命令不关注Modify时间,可以总是被执行。
.PHONY:让make忽略源文件和可执行目标文件的M时间对比
2.5 推导过程
实际上我们的code目标文件不是依赖code.c文件形成的,而是依赖code,o文件形成的,而code.o是依赖code.s文件形成的,code.s是依赖code.i文件形成的,code.i文件是依赖code.c文件形成的。这就形式了一套依赖链。makefile文件如下所示:
make是如何工作的,在默认的方式下,也就是我们只输入make命令。那么:
- make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
- 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到code这个文件,并把这个文件作为最终的目标文件。
- 如果code文件不存在,或是code所依赖的后面的code.o文件的文件修改时间要比code这个文件新(可以用touch测试),那么,他就会执行后面所定义的命令来生成code这个文件。
- 如果code所依赖的code.o文件不存在,那么make会在当前文件中找目标为code.o文件的依赖性,如果找到则再根据那一个规则生成code.o文件。(这有点像一个堆栈的过程)
- 当然,你的C文件和H文件是存在的啦,于是make会生成code.o文件,然后再用code.o文件声明make的终极任务,也就是执行文件hello了。
- 这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
- 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。
- make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。
这样写makefile文件我们只是介绍一下依赖链和递归过程,一般我们是不会这样写的。
三、扩展
3.1 makefile文件中变量的使用
makefile允许我们去定义变量,如下所示:
我们定义了一个变量BIN,如果需要使用这个变量,需要使用${BIN},我们执行make命令之后,会显示echo code,并且执行这条命令,如下所示:
如果我们不想让echo code这条命令显示,可以在makefile文件里面的echo ${BIN}前加一个@符号,如下所示:
3.2 扩展一
运行如下所示:
3.3 扩展二
其中在依赖关系中:
1:$@表示目标文件名字,也就是依赖关系里面的${BIN},也是code
2:$^表示源文件名字,也就是依赖关系里面的${SRC},也是code.c
3:如果源文件有很多个,$^统一代表这些源文件。
运行如下所示:
3.4 扩展三
我们不想看到依赖关系,我们可以在依赖关系前面加上@,但是我们又需要看到过程,如下所示:
3.5 扩展四
我们在实际项目中不用源文件直接生成可执行程序,而是先生成目标文件OBJ,在链接形成可执行程序,如下所示:
3.6 扩展五
如果我们有100个源程序呢?我们需要在变量定义的时候SRC写100个吗?如下所示:
首先使用命令创建100个源文件:
count=1; while [ $count -le 100 ]; do touch code${count}.c; let count++; done
四、总结
makefile文件的最终格式如下:
1 BIN=proc.exe
2 CC=gcc
3 #SRC=$(shell ls *.c)
4 SRC=$(wildcard *.c)
5 OBJ=$(SRC:.c=.o)
6 LFLAGS=-o
7 FLAGS=-c
8 RM=rm -f
9
10 ${BIN}:${OBJ}
11 @${CC} ${LFLAGS} $@ $^
12 @echo "linking ... $^ to $@"
13
14 %.o:%.c
15 @${CC} ${FLAGS} $<
16 @echo "compling ... $< to $@"
17
18 .PHONY:clean
19 clean:
20 ${RM} ${BIN} ${OBJ}
1:SRC表示的是当前目录下的全部.c文件
2:OBJ表示的是所有.c文件的同名.o文件
3:@符号指的是形成目标程序,不显示依赖关系
4:$@表示的是该依赖关系的目标文件
5:$^表示的是该依赖关系的源文件
6:%.o表示的是当前目录下的所有.o文件
7:%.c表示的是当前目录下的所有.c文件
8:%<表示的是,将所有的源文件生成对应的.o文件。