写makefiles(二)

本文详细介绍了makefile的各个组成部分,包括显式规则、隐含规则、变量定义、执行指令和注释。还讨论了makefile的命名、包含其他Makefile的方法、MAKEFILES变量的用途,以及如何避免在重建Makefile时的无限循环问题。通过实例展示了如何通过模板规则在多个Makefile间共享和扩展构建规则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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中的#,会被传递给执行makeshell#代表的意义由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按以下命名顺序搜寻makefileGNUmakefile, 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.mkb.mkc.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可以被诸如RCSSCCS文件重建。当makefile被改变时,make需要读入更改后的makefile文件。

读入所有的makefiles后,make会把它们每一个都当作goal target,并试图更新他们。当makefile有显式的或隐式的规则时,makefile会在必要的时候更新他们。在检查过所有的makefiles之后,如果确实有文件发生了改变,则make会重新读取他们一次。

如果makefiles中有些文件不需要被重建,并且你不想让make的隐式规则查找到它,那么你可以使用一些方法屏蔽隐式规则,例如,你可以写一个显式规则,将那些不需要被重建的makefile当作target,并给他们提供一个空的recipe语句。

如果makefile中使用双冒号修饰一个targetrecipe语句,并且省略prerequisite语句,则这条规则在make运行时一定会被执行。在make指令结束后,会重新读去这个makefile文件。所以当你把一个makefile当作上面的target时,有可能导致一个无限循环:make会不断的重建该makefile,而不再执行其他动作。因此,当对一个makefile使用双冒号语法时,make不会在运行时重建该makefile。

如果执行make指令时,没有使用-f--file选项,则make会寻找标准名称的makefilemake并不能保证这些makefile必须存在,如果标准名称的makefile不存在,但可以通过运行make来创建,那么make会根据寻找顺序创建相应的makefile

3.6 读取另外的Makefile

有时候两个makefile非常类似是很有用的,你可以用include语句将另一个makefile包含进来,从而增加更多的target和变量定义。但是,当两个makefile针对相同的target提供不同的recipe时,上面这种方法就无效了。

在主makefile中,你可以编写一套匹配规则,当一个target根据本makefile文件所包含的信息无法重建时,可以让make去另一个makefile中寻找建立规则。

例如,有一个makefile名为Makefile,它指定了如何创建一个叫作footarget,那么你可以再编写一个名叫GNUmakefilemakefile,它包含如下内容:

foo :
        frobnicate > foo
% : force
        @$(MAKE) -f Makefile $@
force: ;

当执行make foo命令时,make会读取GNUmakefile,并根据规则frobnicate > foo创建foo。当执行make bar时,makeGNUmakefile中找不到对应的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

*关于这一部分,因为会用到后面的内容,所以就暂时不列出,等后面再来补充。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值