3 写makefiles
3.1 makefiles包含的内容
makefiles包含五部分内容:explicit rules(显式规则),implicit rules(隐含规则),variable definitions(变量定义),directives(执行指令),comments(注释):
- explicit rules 说明何时该如何remake(重建)一个或多个文件,这些文件用来生成其对应的targets。
- implicit rules 用来说明如何根据文件类型确定编译规则,以及如何根据targets文件名来确定依赖文件。
- variable definition 表示将一个文本字符串赋值给一个变量,在之后的makefile文件中出现的相同字符串就可以用变量来代替。
- directive 说明当读取makefile时,make要执行的特定过程,包括:
- 读取另一个makefile
- 决定是否使用或忽略makefile的一部分
- 针对多行文本定义一个变量
- 注释以
#
开始,一行中#
及其之后的所有字符都会被make忽略,可以用\
分割一行为多行。当确实需要输入一个#
字符时,用\
转义:\#
#
可以出现在makefile的任何地方,在不同的地方拥有不同的含义。不可以在变量引用和函数调用中使用#
注释,因为#
会被当作字符本身来对待;在recipe
中的#
,会被传递给执行make
的shell
,#
代表的意义由shell
决定;在变量定义语句中,变量中的#
不会被忽略(即当作字符本身),但是在变量的值当中,#
代表的意义由变量实际使用时上下文决定。
makefiles使用line-based
(基于行)的语法,即使用换行符来标志一个语句的结束,GNU make并没有限制一行的长度,这只取决于你计算机内存的大小。但为了可读性,我们需要把太长的行分割成若干短行,使用 \ 转义换行符可以达到这个目的。被转义的换行符或未被转义的换行符结尾的每一行被称作physical line
(物理行,即视觉上看到的行),没有转义的换行符结尾的真正的一行语句被称作logical line
(逻辑行,即语法意义上的一行)。转义的换行符在recipe
行和non-recipe
行并不相同,关于recipe
的情况以后讨论。在non-recipe
行的转义换行符被转化成了空格符,包括其前面相邻的所有空格,以及下一行开始的所有空格,都被转化成一个空格字符,并且连续的多个转义的换行符会压缩成一个(在POSIX.2
中,对转义的换行符的这些转化全部取消了,使用.POSIX
指定target
可以使makefile
遵循POSIX.2
)。
3.2 makefile文件名
默认情况下,make
按以下命名顺序搜寻makefile
:GNUmakefile, makefile, Makefile
,这是makefile的标准文件名。
通常建议使用makefile
或者Makefile
,因为GNUmakefile
专门针对GNU make
,而其他make
使用另外两个名字。推荐使用Makefile
,因为它会出现在文件列表比较靠前的位置。如果make没有找到任何这三个名字的文件,那你必须通过命令行参数指定,使用-f
或--file
选项,其格式为-f name
或 --file=name
,你可以同时指定多个makefile
文件,但是当你使用-f
或--file
选项时,make
将不会再去查找并读取标准文件名的makefile
。
3.3 包含其它的Makefile
include
语句告诉make
在继续读取当前makefile
文件之前,先去读取include
包含的makefile
,其语法如下:
inclued filenames ...
当filenames为空时,不包含任何文件,且不报错。语句的开头可以包含空格,且会被忽略,但是不能使用tab(或.RECIPEPREFIX
指定的其他字符)开头。filenames之间使用空格隔开(数量不限),可以使用通配符,可以用#
在结尾进行注释。
例如,有三个.mk
文件,a.mk
,b.mk
,c.mk
,以及可扩展为bish bash
的变量$(bar),那么下面的表达式
include foo *.mk $(bar)
等效于
include foo a.mk b.mk c.mk bish bash
当make
处理到include
语句时,暂停读取当前文件,转而依次读取include
列表文件,全部读取结束后,才返回继续从include
下面的语句开始读取。
使用include的一种情形是,有若干程序在不同的文件夹中有自己的makefile,当需要使用一个统一的变量定义或者处理规则时,使用include将这些文件统一到一个makefile文件中来。
另一种情形是,你可以把源文件列表放在一个文件中,通过include
将文件列表读进来,从而自动生成某一个target对应的prerequisites(依赖项),通常这样会使makefile更清晰。
如果指定的文件名不是以 \
开始,且在当前目录下未找到指定文件,那么make会在下面几个目录中搜索:
- 使用
-I
或--include-dir
指定的目录 /usr/gnu/include,/usr/local/include,/usr/include
等
如果在这些目录中都没有找到指定文件,将会有一个警告信息,但并不会终止程序,makefile会继续读取剩余的include
文件。当makefile文件读取结束,make
会尝试重建过时的或者不存在的target
文件,如果重建失败,make
才会把未找到指定文件当作致命错误。
如果要忽略include
产生的文件未找到错误,可以使用-include
,它与include
功能完全一样,除了不产生任何错误信息:
-include filenames ...
有些make实现使用sinclude
代替-include
。
3.4 变量MAKEFILES
如果环境变量MAKEFILES
被定义,那make
会在读取其他makefile
之前,先读取MAKEFILES
列表中的makefile
文件。default goal
(默认目标)不能定义在这些makefile
中,且未找到MAKEFILES
列表中的文件并不会报错。
MAKEFILES
的主要作用是make
在递归调用时进行数据交互。通常MAKEFILES
不要在top-level invocation
(顶层调用)之前被设置,因为这样会让外部环境污染你想要执行的makefile
。然而,当你在没有特定的makefile
时直接运行make
命令,MAKEFILES
中的makefile
有助于内建的隐含规则更好的工作(例如,定义搜索路径)。
有人试图在登录时或编写makefile
时设置MAKEFILES
环境变量,最好不要这么做,因为其他任何人都无法使用这些makefiles
。最好在makefile
中显式使用inculde
语句。
3.5 如何重建Makefile
有时makefile
可以被诸如RCS
或SCCS
文件重建。当makefile
被改变时,make
需要读入更改后的makefile
文件。
读入所有的makefiles
后,make
会把它们每一个都当作goal target
,并试图更新他们。当makefile有显式的或隐式的规则时,makefile
会在必要的时候更新他们。在检查过所有的makefiles
之后,如果确实有文件发生了改变,则make
会重新读取他们一次。
如果makefiles
中有些文件不需要被重建,并且你不想让make
的隐式规则查找到它,那么你可以使用一些方法屏蔽隐式规则,例如,你可以写一个显式规则,将那些不需要被重建的makefile
当作target
,并给他们提供一个空的recipe
语句。
如果makefile
中使用双冒号修饰一个target
的recipe
语句,并且省略prerequisite
语句,则这条规则在make
运行时一定会被执行。在make
指令结束后,会重新读去这个makefile
文件。所以当你把一个makefile当作上面的target
时,有可能导致一个无限循环:make
会不断的重建该makefile
,而不再执行其他动作。因此,当对一个makefile使用双冒号语法时,make不会在运行时重建该makefile。
如果执行make
指令时,没有使用-f
或--file
选项,则make
会寻找标准名称的makefile
。make
并不能保证这些makefile
必须存在,如果标准名称的makefile
不存在,但可以通过运行make
来创建,那么make
会根据寻找顺序创建相应的makefile
。
3.6 读取另外的Makefile
有时候两个makefile
非常类似是很有用的,你可以用include
语句将另一个makefile
包含进来,从而增加更多的target
和变量定义。但是,当两个makefile
针对相同的target
提供不同的recipe
时,上面这种方法就无效了。
在主makefile
中,你可以编写一套匹配规则,当一个target
根据本makefile
文件所包含的信息无法重建时,可以让make
去另一个makefile
中寻找建立规则。
例如,有一个makefile
名为Makefile
,它指定了如何创建一个叫作foo
的target
,那么你可以再编写一个名叫GNUmakefile
的makefile
,它包含如下内容:
foo :
frobnicate > foo
% : force
@$(MAKE) -f Makefile $@
force: ;
当执行make foo
命令时,make
会读取GNUmakefile
,并根据规则frobnicate > foo
创建foo
。当执行make bar
时,make
在GNUmakefile
中找不到对应的target
,因此make
会使用模板规则生成make -f Makefile bar
。如果Makefile
提供了更新bar
的规则,make
就会使用这个规则。对于其它任何GNUmakefile
无法创建的target
,都可以按这个方式创建。
上面的模板规则中,%
匹配任意target
,并提供了force
作为prerequisite
,从而保证即使target
存在时,这条规则依然会被执行。因为我们为force
提供了空的recipe
,其阻止make
使用隐含规则查找并创建force
,所以force
始终是不存在的。否则force
会根据上面的模板规则被创建,并且其依赖于force
自己,这会进入一个死循环。
3.7 make如何读取Makefile
*关于这一部分,因为会用到后面的内容,所以就暂时不列出,等后面再来补充。