Linux项目自动化构建工具——make/Makefile
一、make/Makefile的基本概念
1.背景:
一个文件中的源文件不计数,其按类型、功能、模块分别放在若干目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。
makefile的好处是自动化编译,一旦写好整个工程只需要一句make命令就可以完全自动编译,极大提高了软件开发效率。
make是一个命令工具,是一个解释makefile中指令的命令工具。
make是一个命令,makefile是一个文件,两者搭配使用,完成项目自动化构建。
2.操作:
(1)首先创建一个可执行的文件code.c,里面写上代码,写完之后保存退出。之后创建一个文件叫Makefile,m可大写也可小写。
之后打开Makefile,在里面写上要编译的程序code.c,想要将其编译成可执行的文件code就是code:code.c ,然后起第二行写上编译的命令,就是gcc -o code code.c,注意第二行开始要按一下table键。这其中,code和code.c是依赖关系,使用gcc去编译出code是依赖方法。
保存退出后输入命令make,这样系统就会去找make对应的文件Makefile并执行里面的命令。
(2)清理代码:
打开Makefile,在里面写上清理code文件的指令:
要先定义.PHONY,用这个修饰后面的clean,将clean称之为伪目标。下一行的clean依赖关系要有,但依赖文件列表可以为空,然后下一行就执行删除code文件的指令。保存退出后执行make clean,code文件就被删除了。
make命令扫描Makefile文件时是从上往下,默认形成第一个目标文件,所以默认情况下写Makefile文件时把要形成的可执行程序放在最前面。
.PHNOY的作用:
将.PHONY那一行删除:
退出来执行make命令,可以发现仍然可以生成可执行文件,程序没有影响。
那么.PHONY有什么用?用.PHONY修饰clean之后clean就是一个伪目标,伪目标作用就是表示这个目标总是被执行,也就是对应的依赖方法要总是被执行,以后make clean时,rm -f code 这个操作总是要被执行。举个反例,code因为没有被.PHONY修饰,所以它不是总是被执行,所以我去编译的时候第一次可以执行make,有了code了就无法执行make了。
如果用.PHONY去修饰code的话,make就可以一直执行了。
但是实际使用中并不能用.PHONY来修饰,也就是不想让它总是重新编译,实际使用中如果程序没有被更新过,系统就不会重新形成可执行程序,如果此时Makefile里面有几百个程序,但我只改了几个,退出来用make指令时如果是不默认只重新编译改过的程序而是这几百个程序重新再生成一份可执行程序,这样会大大增加编译的时间,效率很低。
总结一下就是:默认老代码不做重新编译。
(3)make怎么知道哪个新哪个旧?
stat code查看文件相关时间:
首先文件等于内容+属性,Modify表示修改了文件内容的时间,Change表示修改了文件属性的时间,而Access表示查看文件内容的时间。但是这个Access时间比较特殊。首先查一次文件:显示时间Access发生变化,然后再查一次,再显示时间: Access时间并没有发生改变,这是因为在Linux系统中,查一个文件内容和属性和修改文件内容和属性相比,查一个文件比重占很多,如果Linux设计成只要查一下文件内容就更新一下access时间,过于高频的更新时间成本很大。所以Linux后来就变成当查看文件若干次这个时间才会变化,具体多少次与特定的Linux版本有关系。
有了以上基础,就可以得知每一个文件都有其modify time,一开始没有可执行程序只有一个code.c,第一次编译的时候形成可执行程序调用gcc命令,执行的时候就知道源文件的modify time比可执行程序的modify time早,就检测到可执行程序code比code.c要新,也就是code.c没有被修改,所以就不被编译。如果修改了code.c的内容,modify时间就更新,就比可执行程序要新,系统就意识到code文件是旧的,就会重新编译。但是加了PHONY就可以忽略文件的新旧来一直编译,因此PHONY的作用就是忽略对比时间、忽略对比新旧的变化直接编译。
二、code编译的过程:
(1)code可执行程序其实有code.c经过iso一步步来的:
在这个过程中,makefile从上到下扫描,要编译出code就要现有code.o,但是没有code.o,这条命令就先入栈,再到下一条命令,要编译出code.o就要有code.s,但是没有code.s,这条命令也入栈,跳到下一条命令,依次类推直到找到code.c可以编译出code.i,上面的命令依次出栈,就编译出了可执行程序code。(2)makefile第二种写法: 将各个需要的文件定义为变量
将要生成的可执行文件myproc定义为变量BIN,测试命令用echo打印出来,在变量面前加$(),就可以打印这个变量的值也就是myproc,这个用法可以类比为指针,在指针前加上*就打印出指针指向的值。退出后make一下
这样命令会回显出来,可以在echo前加上@符号就不会回显。
因此,我们可以把gcc定义为CC,myproc.c定义为SRC,-o定义为FLAGS,rm -f定义为RM,把一开始的版本进行替换。
还可以再改进,$(BIN):$(SRC) $(CC) $(FLAGS) $(BIN) $(SRC) 这两行代码中,由于第一行已经包含了依赖关系,所以可以用@表示目标文件,^表示被依赖的文件列表,于是可以写成:
(3)依赖关系有一组,但依赖方法可以不止一条,也就是说我们可以在完成程序编译的下一行进行其他的命令,就比如可以打印出编译程序的信息。
三、如果有多个文件要同时生成可执行文件,那么就应该把多个文件同时编译出 .o 文件再一起去链接。对上面的变量做一下调整,为了可以生成 .o 的文件。
那么编译的命令就应该修改为gcc -c myproc.c myproc.o gcc -o myproc.o myproc.exe,由于gcc -c myproc.c 就会默认生成同名 .o 文件,所以变量替换之后就是:
可是,需要的是多个 .o 文件,这样写只能包含一个,所以就要修改:
%表示把当前目录下的 .o 和 .c 依次展开,有多少个展开多少个,$< 就相当于依次展开所有的 .c 文件编译成 .o 文件。完整的命令如下:
但是这样写还是很麻烦,并不能展现Makefile的特点,因为要是有很多个 .c 文件,那SRC后面就要自己手写很多 .c 文件。于是就可以在SRC后面写上shell的指令,这样SRC就可以自动获取指令对应的结果,就比如:
用test命令去测试打印出$(SRC) 就会出现ls执行的结果。
但实际想要的不是ls的结果而是所有的 .c 文件,于是:
这样子Makefile在从上到下扫描的时候就会执行ls *c这个命令然后把所有的源文件列表放在这个SRC当中。保存退出运行一下:
Makefile内部包含一些类似函数的功能,可以把ls替换为wildcar效果也是一样的。获取完 .c 之后,还需要 .o 的文件,所以需要对OBJ这个变量做一下修改:OBJ=$(SRC: .c=.o),这样Makefile就会自动把 .c 文件换成同名 .o 文件。完整的版本如下: