在linux开发中,应用程序的编译基本都采用GNU的make工具,而make搭配Makefile来实现工程代码的编译,在越是大型复杂的项目中,make的强悍之处越是明显。在使用了一段时间make后,对其用法进行分析。本文是在学习了陈皓的“跟我一块儿学Makefile”文章后,对本身学习的小结。linux
如今,咱们有一个C++的项目须要进行编译,项目包含3个头文件,3个.cpp文件,分别是main.cpp,main.h,read.cpp,read.h,write.cpp,write.h,下面就是咱们的Makefile了:c++
咱们须要生成的目标是可执行文件test,test由中间目标文件.o连接生成,.o文件根据依赖关系由相应的头文件和.cpp源文件编译而成,伪目标clean用于清除目标文件和.o文件。函数
在个人Makefile中,咱们发现编译工具cc即便不指明为g++也能够正常编译,可是若是是在目标文件连接时不指明的话,会有提示找不到相关的库,那关于这个cc究竟是根据什么来选择编译工具的呢?经过查阅资料和测试发现,编译程序cc通常默认的都是gcc,当咱们对源文件编译时,对于.c文件,gcc和g++分别识别为c和c++程序,对于.cpp文件,二者都会识别为c++程序,因此在编译时咱们彻底能够用默认cc来编译.c和.cpp文件(固然对于其余平台或嵌入式中仍是要指定对应的编译工具,防止出错)。可是连接时,须要对.o中间文件连接库生成可执行程序,g++会连接c++的库,gcc连接c的库,因此若是连接时须要指明编译器。在日常使用中,考虑到的易维护和防出错,都是统一将编译和连接的编译器都指明为g++。工具
好,经过这个Makefile实现了咱们的功能需求,可是咱们发现,这种Makefile虽然逻辑简单,可是一旦当项目体积很是大时,写Makefile会是一件很是痛苦的事,须要写几百个中间目标文件的依赖关系,OK,别担忧,GNU的make很强大,它能够经过隐晦规则进行自动推导。学习
GNU的make工具存在隐晦规则,只要make看到一个.o文件,他会默认把对应的.c或.cpp(后面讨论时会都用.c文件来讨论)文件加到依赖关系中,好比若是有一个main.o,那么他会默认把main.c加到其依赖关系中,而且make会对其进行自动推导,包括文件的依赖关系和后面的命令。那么,咱们又能够获得一个新的Makefile了。测试
这样咱们获得了一个看起来更简洁的Makefile,咱们不用书写每一个中间目标文件的编译指令了,由于make会自动推导,可是这样貌似只是部分下降了咱们的劳动力,对于几百个源文件的程序,咱们仍是须要写出来每一个文件隐晦规则以外的依赖关系,虽然不用写编译指令,但仍是很是多,并且这样的Makefile通用性并不高,若是咱们写了个其余的测试程序,这个Makefile咱们仍是须要更改许多,OK,make这个工具的静态模式和自动变量能够帮咱们解决这个问题。spa
Make中的静态模式的语法以下:3d
<targets ...>: <target-pattern>: <prereq-patterns ...> blog
<commands> 接口
....
targets定义了一系列的目标文件,能够有通配符。是目标的一个集合。
target-parrtern是指明了targets的模式,也就是的目标集模式。
prereq-parrterns是目标的依赖模式,它对target-parrtern造成的模式再进行一次依赖目标的定义。
举个例子,对于前面的objects目标集合,<target-parrtern>定义成“%.o”,意思是咱们的<target>集合中都是以“.o”结尾的,而若是咱们的<prereq-parrterns>定义成“%.c”,意思是对<target-parrtern>所造成的目标集进行二次定义,其计算方法是,取<target-parrtern>模式中的“%”(也就是去掉了[.o]这个结尾),并为其加上[.c]这个结尾,造成的新集合。如main.o经过模式取到的依赖目标就是main.c文件。静态模式帮助咱们完成将几百个源文件用一种通用的模式来表明,咱们能够不用写出全部中间文件的依赖关系,只要用一个静态模式就OK了。
接下来有个问题,在上述的模式规则中,目标和依赖文件都是一系例的文件,那么咱们如何书写一个命令来完成从不一样的依赖文件生成相应的目标?由于在每一次的对模式规则的解析时,都会是不一样的目标和依赖文件。这就须要make 的自动化变量。
Make的自动化变量。 所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至全部的符合模式的文件都取完了。这种自动化变量只应出如今规则的命令中。
下面是全部的自动化变量及其说明:
$@
表示规则中的目标文件集。在模式规则中,若是有多个目标,那么,"$@"就是匹配于
目标中模式定义的集合。
$%
仅当目标是函数库文件中,表示规则中的目标成员名。例如,若是一个目标是"foo.a (bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。若是目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。
$<
依赖目标中的第一个目标名字。若是依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
$?
全部比目标新的依赖目标的集合。以空格分隔。
$^
全部的依赖目标的集合。以空格分隔。若是在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
$+
这个变量很像"$^",也是全部依赖目标的集合。只是它不去除重复的依赖目标。
$*
这个变量表示目标模式中"%"及其以前的部分。若是目标是"dir/a.foo.b",而且目标的模式是"a.%.b",那么,"$*"的值就是"dir/a.foo"。这个变量对于构造有关联的文件名是比较有较。若是目标中没有模式的定义,那么"$*"也就不能被推导出,可是,若是目标文件的后缀是make所识别的,那么"$*"就是除了后缀的那一部分。例如:若是目标是"foo.c",由于".c"是make所能识别的后缀名,因此,"$*"的值就是"foo"。这个特性是GNU make的, 颇有可能不兼容于其它版本的make,因此,你应该尽可能避免使用"$*",除非是在隐含规则或是静态模式中。若是目标中的后缀是make所不能识别的,那么"$*"就是空值。
更新后的Makefile以下:
OK,写到这里,咱们的Makefile已经很是很是简洁了,可是这里面存在两个问题,第一个问题是,咱们全部的目标文件的依赖关系都只有相应的源文件,并无其余所需头文件的依赖关系,好比main.o的依赖关系包含了main.cpp,main.h,read.h,write.h,在首次编译时,依赖关系会找到相应的相应的头文件,可是以后若是修改了某个头文件,则再次编译头文件并不会更新,因此这种方式中缺乏头文件的依赖关系,一种解决方法是每次从新编译时,先清空以前的编译目标文件就OK了,还有一种方法是创建.c文件的依赖关系(下次再写)。
第二个问题是,咱们发现,咱们仍是要写出全部的目标文件集,一样的,对于几百个中间文件的项目来讲,这也是很头疼的事,咱们能够经过通配符和一些函数来解决这个问题。
咱们的需求是让make本身找到当前目录下全部的源文件,而且对这些源文件根据依赖关系进行编译处理,实现彻底的自动化。首先咱们须要用通配符*来找到全部的源文件,并对应生成全部源文件对应的中间目标文件,这主要经过patsubst函数来实现,该函数是模式字符串替换函数(对于全部的函数能够查询后文的函数表),帮咱们将依赖目标集的全部源文件对应生成.o文件。
Makefile写到这里,应该是简洁性和通用性都比较高的了,可是,如今的makefile在实际中可用性不高,由于实际项目中源文件每每放置在多个文件夹中,并且还会设置许多编译参数,因此下面将会写一个体现源文件结构复杂度和编译器参数的makefile。
如今,咱们假设,咱们的read.cpp,read.h在当前目录的read目录下,write.cpp和write.h在当前目录下的write目录下,那么咱们新的makefile又出炉了。
原有的wildcard是匹配当前目录下全部的源文件,可是有多个文件夹,就没法匹配到了,因而在这里咱们就可使用VPATH这个变量(这个make默认识别的变量用于在当前目录下查找文件查找不到时,默认到VPATH路径下查找),同时还须要使用一个函数forreach。因为这里只匹配了源文件,相应的头文件的位置没有包含,因此还须要包含一下头文件。
OK,到这里,貌似咱们已经写出了一个比较好的Makefile,可是,实际的编译参数咱们都还没涉及,下面就具体分析一下,GNU下的编译工具的编译参数。
有时咱们须要连接第三方库,那么如何连接库,如何使用库中的函数?
首先,在源代码中,咱们须要包含所使用的库的接口函数的头文件;
其次,在Makefile中,咱们须要在头文件和库的搜索路径中分别加入所需头文件和库的路径。假设库和库的头文件在路径/mnt/hgfs/share/miniupnp/miniupnpc-1.9.20160209路径下,那么包含方式
头文件:-I/mnt/hgfs/share/miniupnp/miniupnpc-1.9.20160209 //加入头文件路路径
库:-L/mnt/hgfs/share/miniupnp/miniupnpc-1.9.20160209 //加入库文件路径
若是库的名字是libminiupnpc.a,那么在连接时的参数就是 -lminiupnpc
相似于下图:
结束。