Make
前言:前段时间写OS的Lab2时,就已经使用了Makefile
,不过较为简单,也只是一知半解。(不过前段时间听他们群里的说法Makefile
也是旧日荣光了?)
概述
为什么要有Makefile
?
应对大型工程项目,makefile
可以定义了整个编译流程以及各个目标文件与源文件之间的依赖关系,出现修改时,只会重新编译修改会影响的部分,从而降低了编译的时间,提升了编译的效率。
Makefile
究竟是什么?
Makefile
是整个工程的编译原则,定义了一系列的规则来指定文件编译顺序
Makefile
给工程带来了“自动化编译”,一个make
指令就可以使整个工程完全自动编译
区分GNU Make
和Makefile
GNU Make is a tool which controls the generation of executables and other non-source files of a program from the program's source files.
Make gets its knowledge of how to build your program from a file called the makefile, which lists each of the non-source files and how to compute it from other files.
上述是来自于GNU
官网对于两者的定义,可以直观看出,Make
是批处理的工具,而这个工具所仰仗的信息来源于Makefile
,所以写好Makefile
文件使我们能很好利用Make
工具的重要前提。
写Makefile
基本规则
1.如果这个工程没有编译过,那么我们的所有c文件都要编译并被链接。
2.如果这个工程的某几个c文件被修改,那么我们只编译被修改的c文件,并链接目标程序。
3.如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的c文件,并链接目标程序。
默认情况下,make
命令会在当前目录下按顺序寻找文件名为GNUmakefile
、makefile
、Makefile
的文件,最好使用Makefile
这个文件名,最好不要用GNUmakefile
,因为这个文件只能由GNU
去make
。当然也可以使用别的文件名来书写makefile
,不过需要加上-f
可选项进行指定。
基本格式
target: prerequisites |
结合上述基本规则,Makefile
最核心的内容就是prerequisites
中如果有一个以上的文件比target
文件要新的话,recipe
所定义的命令就会被执行。
target
可以是object file
,也可以是一个可执行文件,还可以是一个标签
prerequisites
生成该target
所要依赖的文件或其他target
recipe
该target
要执行的命令,每行一定要以Tab
开头
工作流程
main: a.o b.o |
使用上述makefile
文件,目录结构为
a.c b.c makefile |
frechen026@frechen026-virtual-machine:~/Desktop/MakeTest$ make |
使用make
指令可以看出执行了main
这个target
的内容,对b.c
文件进行修改,发现只执行了gcc -c b.c -o b.o
,未执行gcc -c a.c -o a.o
,在不进行修改的情况下执行make
,发现提示已是最新,无需更新。
make
指令
默认的指令便是make
,我们不需要在makefile
文件中显式的指出(似乎也没法指出,毕竟target
不能是个空吧)
make
指令的工作流程:
1.找寻当前目录下Makefile
文件
2.默认使用第一个target
,找寻第一条target
的依赖
3.处理依赖文件的target
4.根据目标target
修改时间以及依赖的修改时间判断是否需要重新构建
参考教程跟我一起写Makefile,这里的表述我认为是存在一定问题的,未先去处理依赖文件如何获知依赖文件的最后更新时间以及是否存在?时间逻辑的先后应该是先去递归处理依赖文件,最后才是返回来处理目标target
官方的描述为:In the example, this rule is for relinking edit; but before make can fully process this rule, it must process the rules for the files that edit depends on, which in this case are the object files. Each of these files is processed according to its own rule.
后续引入了其他makefile
文件以及变量等概念后,make
工作时的执行步骤为:
1.读入所有的Makefile
2.读入被include的其它Makefile
3.初始化文件中的变量
4.推导隐式规则,并分析所有规则
5.为所有的目标文件创建依赖关系链
6.根据依赖关系,决定哪些目标要重新生成
7.执行生成命令
其他target
指令
make
默认执行的就是第一个target
以及其依赖的target
,故无关联的target
并不会进行执行,如我们所写makefile
中的clean
,但我们可以通过显式执行来执行clean
,即make clean
指令。
frechen026@frechen026-virtual-machine:~/Desktop/MakeTest$ make clean |
效果如上所示,故make
指令也可以显式为make
加上首条target
,在此即为make main
,效果如下所示
frechen026@frechen026-virtual-machine:~/Desktop/MakeTest$ make main |
因此我们可以通过显式方法来执行其余target
,其余target
的工作流程也与make
相同。
不难发现,有些target
如上述的clean
,并非是文件名。我们可以使用虚假的目标PHONY
,将目标标记为不指向文件,当然PHONY
同样适用于文件。
语法格式:
main: a.o b.o |
值得注意的一点是:PHONY
目标总是被认为是过期的,因此将总是运行这些target
的recipe
,递归的也将总是运行包含PHONY
依赖的target
还是上述的makefile
,每次执行make
,发现都会执行main
下的recipe
frechen026@frechen026-virtual-machine:~/Desktop/MakeTest$ make |
将a.o
置为PHONY
,取消main
的PHONY
,执行make
frechen026@frechen026-virtual-machine:~/Desktop/MakeTest$ make |
由于main
的依赖target
是PHONY
的,不仅是a.o
需要重编译,main
受其牵连也要如此。
清空目录的规则
每个Makefile
中都应该写一个清空目标文件( .o )和可执行文件的规则,这不仅便于重编译,也很利于保持文件的清洁。这是一个“修养”。
确实如此!
question
一个令我感到头疼的问题是所谓目标target
和依赖更新时间的先后,如果目标target
是文件,那这不难理解,文件都有最后一次写入时间。但若是目标target
根本就不是文件或是一个根本不存在的文件,类似于上述的伪目录
处处透露出合理,却在某些时候想不通真是一件让人难过的事。这两天虽然看了看提问的智慧,可当自己想描述一个问题时却发现还是十分困难,果然,提问是门艺术。
表述为不存在的文件或者伪目标应该都是“过期的”,而依赖也是在检测自己是否是“过期的”,如果过期则执行相关的重新编译。
书写规范
变量使用
将需要重复大段使用的内容整合为一个变量,便于修改以及复用
我们可以修改makefile
文件,将a.o b.o
列为变量
objects = a.o b.o |
执行效果一样。
在makefile
中定义的变量,声明时要给予初值,而在使用时要在变量名前加上$
,最好使用()
或者{}
将变量名括起来。
隐式规则
是一种惯例,让make
自己去推导会发生什么,这些约定俗成的内容在我们不显式指出并规定时就会按照默认进行。
可以将上述makefile
进一步精简
objects = a.o b.o |
执行make
指令发现
frechen026@frechen026-virtual-machine:~/Desktop/MakeTest$ make |
GNU Make
工具已经猜测出生成a.o b.o
所需执行的指令
当然,还有很多隐式的一些规则,不加以罗列,但对于一些不是很清楚的隐式规则可能发生时,我们应该想方设法避免他,即使他真的十分简洁。
注释
写好注释应该是每个好的程序员应该有的素养,makefile
也可以书写注释,其注释采用#
,一般编辑器下应该都可以使用快捷键ctrl + /
伪目标
上面已经提及过相关内容,“伪目标”并不是一个文件,只是一个标签而已。为了避免“伪标签”和文件重名的情况,我们可以使用特使标记.PHONY
来显式的指明一个目标是“伪目标”。
伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。一个示例就是,如果你的Makefile
需要一口气生成若干个可执行文件,但你只想简单地敲一个make
完事,并且,所有的目标文件都写在一个Makefile
中,那么你可以使用“伪目标”这个特性。
所以我们可以借助“伪目标”简化shell
命令,以简单的语句执行复杂的shell
语句,前提是你的makefile
写的足够好。
后话
当然,makefile
还有很多细节值得去学习,例如函数、多文件等等,不过总感觉记录下来过于繁琐,也可能是不知道如何记录。所以碰到实际的问题还是要多参考参考下面的几篇教程……