makefile编写规则

    技术2026-08-31  5

    Makefile书写格式

    在大型程序开发时,通常会用到Makefile进行构建(build)。Makefile文件一般规定了源文件怎么去编译(compile),其内部的书写规则如下:

    <target> : <prerequisites> [tab] <commands>

    上面第一行冒号前面的部分,叫做目标(target),冒号后面的部分叫做前置条件(prerequisites);第二行必须由一个tab键起首,后面跟着命令(commands)。

    目标是必需的,不可省略;前置条件和命令都是可选的,但是两者之中必须至少存在一个。

    目标target

    一个目标(target)就可构成一条命令。目标通常是文件名,来指明Make命令所要构建的对象。它既可以是一个文件名,也可以是多个,之间用空格分隔即可。

    除了文件名,目标还可以是某个操作的名字,这称为"伪目标"(phony target)。如:

    clean: rm *.o

    上面的目标clean,不是一个文件名,而是一个操作的名字,属于“伪目标”,作用是删除所有.o文件。

    $ make clean

    但是,如果当前目录中,正好有一个文件叫做clean,那么这个命令不会执行。因为Make发现clean文件已经存在,就认为没有必要重新构建了,就不会执行指定的rm命令。

    为了避免这种情况发生,可以明确声明clean是"伪目标",写法如下

    .PHONY : clean clean: rm *.o temp

    声明clean是"伪目标"之后,make就不会去检查是否存在一个叫做clean的文件,而是每次运行都执行对应的命令。类似于.PHONY的内置名还有许多,此处暂且不表。

    如果Make命令运行时没有指定目标,默认会执行Makefile文件的第一个目标。

    $ make

    此代码只执行Makefile文件的第一个目标,当然,若此目标中有前置条件,会先满足前置条件而后执行。

    前置条件prerequisites

    前置条件通常也是一组文件名,之间用空格分隔。它指定了目标是否重新构建的判断标准:只要有一个前置文件不存在,或者有过更新(前置文件的last-modification时间戳比目标的时间戳新),目标就需要重新构建。 比如如下命令:

    target.txt : request.txt cp request.txt target.txt

    上面代码中,构建 target.txt 的前置条件是 request.txt 。如果当前目录中,request.txt 已经存在,那么命令make target.txt可以正常运行,否则必须再写一条规则,来生成 request.txt 。

    request.txt: echo "this is the source" > request.txt

    上面代码中,request.txt后面没有前置条件,就意味着它跟其他文件都无关,只要这个文件还不存在,每次调用make request.txt,它都会生成。

    $ make target.txt $ make target.txt

    上面命令连续执行两次make target.txt。第一次执行会先新建 request.txt,然后再新建 target.txt。第二次执行,Make发现 request.txt 没有变动(时间戳早于 target.txt),就不会执行任何操作,target.txt 也不会重新生成。

    如果需要生成多个文件,往往采用:

    source : file1 file2 file3

    在这个代码中,source是一个伪目标,只有三个前置条件(文件),没有任何命令,这样,直接使用命令make source就可以一次性生成三个文件,方便了一些。

    命令commands

    命令(commands)表示如何更新目标文件,由一行或多行的Shell命令组成。它是构建目标的具体指令,它的运行结果往往就是生成目标文件。

    每行命令都是在一个单独的shell中运行,这些shell之间没有关系,因此,对如下代码:

    test: export foo = bar echo "foo=[$$foo]"

    执行(make test)后,并不能取到foo的值。因为两行命令是在两个不同的进程之间运行的。要想达到预期效果,可以将两个命令写在一行,中间使用分号隔开,或者在换行符前加反斜杠转义,或者加上 .ONESHELL 命令

    #将两个命令写在一行,中间使用分号隔开 test: export foo=bar; echo "foo=[$$foo]" #在换行符前加反斜杠转义 test: export foo=bar; \ echo "foo=[$$foo]" #加上.ONESHELL:命令 .ONESHELL: var-kept: export foo=bar; echo "foo=[$$foo]"

    注意:每行命令前必须要有一个tab键,如果想用其他键,可以使用内部变量.RECIPEPREFIX声明。

    .RECIPEPREFIX = > all: > echo Hello, world

    上面的代码即用.RECIPEPREFIX指定了大于号(>)替代tab键。所以,每一行命令的起首变成了大于号,而不是tab键。

    Makefile语法

    1.注释 #在Makefile中表示注释 2.回声(echoing) 正常情况下,make会打印每条命令,然后再执行, 此机制叫做回声(echoing),比如

    test: #just for a test

    执行make test,会出现如下结果:

    $ make test #just for a test

    而如果在命令的前面加上@,就可以关闭回声,再次执行make test 就不会有任何输出。 由于在构建过程中,需要了解当前在执行哪条命令,所以通常只在注释和纯显示的echo命令前面加上@。

    3.通配符 与bash一致,主要有*,?,以及[…]和~。

    通配符作用?代表一个任意字符*代表0个或任意多个字符[ ]代表括号中的一个字符[-]-代表一个范围[^]^代表逻辑非,类似正则表达式规则

    4.模糊匹配 Makefile中可以对文件名进行类似正则运算的匹配,主要的匹配符号是%。 5.变量和赋值符 Makefile中允许使用等号来自定义变量。

    txt = hello world test: @echo $(txt)

    上面的代码中, 变量txt等于hello world,调用时,变量需要放在$()之中。 调用shell变量,需要在美元符号之前再加一个美元符号,因为make命令会对美元符号转义。

    test: @echo $$HOME

    事实上,Makefile中一共提供了四个赋值运算符(=、:=、?=、+=)

    var = value # 在执行时扩展,允许递归扩展 var := value #在定义时扩展 var ?= value #只有在该变量值为空时才设置值 var += value #将值追加到变量的尾端

    6.内置变量 make命令中有一系列的内置变量,比如$(CC)指向当前使用的编译器,$(MAKE)指向当前使用的Make命令,更多可见其手册。 7.自动变量 (1)$@ $@指代当前目标,就是Make命令当前构建的那个目标。

    a.txt b.txt: touch $@

    等同于

    a.txt b.txt: touch a.txt ; touch b.txt

    (2)$< $<指代第一个前置条件。比如,规则为 t: p1 p2,那么$< 就指代p1。 (3)$? $? 指代比目标更新的所有前置条件,之间以空格分隔。比如,规则为 t: p1 p2,其中 p2 的时间戳比 t 新,$?就指代p2。 (4)$^ $^ 指代所有前置条件,之间以空格分隔。比如,规则为 t: p1 p2,那么 $^ 就指代 p1 p2 。 (5)$* $* 指代匹配符 % 匹配的部分, 比如% 匹配 f1.txt 中的f1 ,$* 就表示 f1。 (6)$(@D) 和 $(@F) $(@D) 和 $(@F) 分别指向 $@ 的目录名和文件名。比如,$@是 src/input.c,那么$(@D) 的值为 src ,$(@F) 的值为 input.c。 (7)$(<D) 和 $(<F) $(<D) 和 $(<F) 分别指向 $< 的目录名和文件名。 8.判断与循环 使用bash语法完成判断与循环。

    ifeq($(CC),gcc) libs=$(libs_for_gcc) else libs=$(normal_libs) endif

    上面的代码判断当前编译器是否是gcc,并指定不同的库文件 循环语句

    LIST = one two three all: for i in $(LIST); do \ echo $$i; \ done

    9.函数 Makefile中可以使用许多预定义的函数,使用的方法为

    var = $(func_name arg1,arg2,...) #返回值=$(函数名 实参)

    Makefile中预定义函数有很多,包括shell,wildcard,subsr等函数,详见其文档。 由于Makefile中自定义函数本质上是一个多行变量,无法直接调用,因此暂按下不表。

    Processed: 0.009, SQL: 12