Makefile详解
其中的内容大多来自《GNUmake中文手册》3.8版徐海兵译,有小部分是我的理解:
在开始之前,我们要了解一些基本概念:
编译:把高级语言转化成.o文件
链接:把多个.o文件和库文件链接成一个可执行程序,“ld”命令
静态库:Linux下的静态库是.a文件,“ar”命令,程序可以把依赖的.a文件加到自己的程序中,这样,程序的运行就不需要外部的库函数了,但缺点是体积比较大,且依赖的任何一个静态库文件发生变化就要重新编译源程序。
动态库:Linux下的动态库是.so文件,程序无需连接时就把依赖的库文件连接进自己的程序中,这样体积小,但程序的运行需要依赖外部库函数。
make的基本原则:
1.如果所有的源文件没有被编译过,则对各个源文件编译并连接,生成最后的可执行程序;
2.每一个在上次执行make之后修改过的源代码文件在本次执行make时将会被重新编译;
3.头文件在上一次执行make之后被修改,则所有包含此头文件的源文件在本次执行make时将会被重新编译。
Makefile的基本语法:
TARGET:PREREQUISITES
COMMAND:
TARGET:规则的目标。通常是程序中间或者最后需要生成的文件名。可以是.o文件、也可以是最后的可执行程序的文件名。另外,目标也可以是一个make执行的动作的名称,如目标“clean”,称这样的目标是“伪目标”。
PREREQUISITES:规则的依赖。生成规则目标所需要的文件名列表。通常一个目标依赖于一个或者多个文件。
COMMAND:规则的命令行。任意的shell命令或者可在shell下执行的程序。
注意:每一个COMMAND必须以[Tab]字符开始,[Tab]字符告诉make此行是一个命令行。这是书写Makefile最容易产生的错误。
make原理:
当在shell提示符下输入“make”命令以后。make读取当前目录下的Makefile文件,并将
Makefile文件中的第一个目标作为其“终极目标”,开始处理第一个规则(终极目标所在的规则)。由于该目标依赖于其他目标,所以接下来一层层地去寻找终极目标的依赖文件所在的规则并执行。当终极目标的规则被完全的展开以后,make将从最后一个被展开的规则处开始执行,之后处理倒数第二个规则,......依次回退。最后一步执行的就是终极目标所在的规则。整个过程就类似于C语言中的递归实现一样。同样,如果在shell提示符下输入“makexxx”命令后,make会在Makefile文件中查找xxx目标,然后再递归的查找依赖的目标执行。因此如果xxx目标没有依赖另一个目标,那个目标的命令就不会被执行,所以我们“make”的时候,目标“clean”的动作“rm”就没有被执行。
例:
#samplemakefile
#定义变量
object= hello1.o hello2.o hello3.o
hello:$(object)
gcc-o hello $(object)
hello1.o:hello1.chello.h
gcc-c hello1.c
hello2.o:hello2.chello.h
gcc-c hello2.c
hello3.o:hello3.chello.h
gcc-c hello3.c
#伪目标
.PHONY:clean
clean:
rm$(object) hello
上例中的“.PHONY: clean ”表示将“clean”目标声明为伪目标,一般情况下这句可以省略,但如果当前工作目录下存在文件“clean”时就不能省略了,否则,我们执行“makeclean”时,规则没有依赖文件,所以目标被认为是最新的而不去执行规则作定义的命令,因此命令“rm”将不会被执行!
几点注意:
1.
在书写时,一个较长行可以使用反斜线(\)分解为多行,注意:反斜线之后不能有空格。
2.
Makefile中“#”字符后的内容被作为是注释(和shell脚本一样)。如果一行的第一个非空字符为“#”,那么此行 为注释行。注释行的结尾如果存在反斜线(\)那么下一行也被作为注释行。
注意:推荐在书写Makefile时将注释作为独立的一 行!
当在Makefile中需要使用字符“#”时,可以使用“\#”来实现,其表示将“#”作为一字符而不是注释的开始标志
3.
如果命令前有“-”则表示如果该命令执行错误(如rm了一个不存在的文件)则忽略,继续往下执行(不推荐这样做!我们可以用rm-f xxx代替-rm xxx)。
4.
makefile中可以使用“includexxx”来包含其他的makefile文件,和c语言是一样的。
5.
默认情况下,make会输出它执行的每一条shell命令,如“echohello”会输出:
echohello
hello
第一行是命令本身,第二行才是命令的执行结果
我们可以在命令前加“@”禁止输出命令,只输出执行结果
6.Makefile中对“$”表示对变量或者函数的引用,如果我们的规则需要“$”,需要书写两个连续的(“$$”)。如果需要统配符最为字符,可以这样:\*,\?等
7.
默认的情况下,make会在工作目录(执行make的目录)下按照文件名顺序寻找文件名的顺序:
GNUmakefile-->makefile-->Makefile的makefile文件,推荐使用“makefile”和“Makefile”,
其中更推荐“Makefile ”,因为首字母大写而比较显著,一般在一个目录中和当前目录的一些重要文件(README,Chagelist等)靠近,在寻找时会比较容易的发现它。
我们也可以自己指定makefile文件名,需要通过make的“-f xxx”或者“--file xxx”选项。也可以通过多个“-f”或者“--file”选项来指定多个需要读取的makefile文件,多个makefile文件将会被按照指定的顺序进行连接并被make解析执行。当通过 “-f”“--file”指定make读取makefile的文件时,make就不再自动查找这三个标准命名的makefile文件。
8.
定义变量时如果:object= *.o,那么$(object)只表示“*.o”而不是所有以.o结尾的文件!如果需要变量“objects”代表当前目录下的所有的.o文件,则需要是用 函数“wildcard”来实现:objects= $(wildcard *.o)。
make常用命令行参数:
-w:
在进入一个目录时输出:
make:Entering directory xxx.
在离开一个目录时输出:
make:Leaving directory xxx.
1.定义变量:
Makefile中的变量大小写敏感,Makefile传统做法是变量名是全采用大写的方式。
推荐的做法是在对于内部定义定义的一般变量(例如:目标文件列表objects)使用小写方式, 而对于一些参数列表 (例如:编译选项CFLAGS)采用大写方式。
变量定义完后可以“$()”或“${}”方式引用
特殊变量:“$<”“$@”“$?”“$*”
“=”,“:=”,“”在定义变量时的区别:
“=”表示如果一个变量引用了另一个变量,那么它在定义时不会展开,在引用时才展开。它的优点是可以引用在当前行之后定义的变量。
“:=”表示如果一个变量引用了另一个变量,那么它在定义时就直接展开,得到一个不包含任何其他引用的字符串。缺点是不能引用之后定义的变量。如果引用一个未定义的变量,则为空
“?=”表示如果此变量在之前没有定义,就赋值,否则保持原值
例:
a = $(b)
b = good
target:
@echo$(a)
和:
a := $(b)
b = good
target:
@echo$(a)
第一个输出”good“,第二个输出”“
追加变量:
通常对于一个通用变量在定义之后的其他一个地方,需要给它的值进行追加。在Makefile中使用“+=”(追加方式)来实现对一个变量值的追加操作。像下边那样:
objects+= another.o
有些需要注意的地方:
1.
如果被追加值的变量之前没有定义,那么,“+=”会自动变成“=”,此变量就被定义为一 个递归展开式的变量。
2.
如果之前存在这个变量定义,那么“+=”就继承之前定义时的变量风格(可参考5.2两种变量定义 一节)
variable := value
variable += more
等价与:
variable := value
variable := $(variable) more
variable = value
variable += more
等价与:
temp = value
variable = $(temp) more
我们可以用define来定义一个多行变量:
“define”定义变量的语法格式:以指示符“define”开始,“endif”结束,之间的所有内容就是所定义变量的值。所要定义的变量名字指示符“define”的同一行之后;指示符所在行的下一行开始一直到“end”所在行的上一行之间的若干行,是变量的值定义。
definetwo-lines
echofoo
echo$(bar)
endef
注意,define定义等价与“=”定义,只不过定义了多行。
上面的例子等价与:
two-lines= echo foo; echo $(bar)
Makefile中可以使用所有的环境变量,它们作为默认已定义的变量,$(xxx)便可引用,如echo$(PATH)
如果在上层makefile中定义了一个变量,那么它只在这个makefile中有效,如果要让它在子makefile中仍然有效,需要:exportxxx
Makefile条件语句:
条件语句的一般格式为:
ifeq …
xxx
endif
或
ifeq …
xxx
else …
yyy
endif
合法的ifeq格式:
ifeq (ARG1, ARG2)
ifeq ‘ARG1’ ‘ARG2‘
ifeq ”ARG1” “ARG2“
合法的ifneq格式:
ifneq (ARG1, ARG2)
ifneq ‘ARG1’ ‘ARG2‘
ifneq ”ARG1” “ARG2“
ifdef用来判断一个变量是否定义:ifdef xxx
注意:只有在xxx没有定义或者这样定义:xxx=时ifdef才是false,其他均为true,如:
bar =
foo =$(bar)
ifdef foo
@echoyes
else
@echono
endif
输出:yes,虽然展开后foo=空
ifndef用来判断一个变量是否没有定义:ifndef xxx,和ifdef正好相反
Makefile函数:
Makefile中引用函数:$(函数名参数)
注意:上面的函数名应该是make内嵌的函数名。对于用户自己的函数需要通过make的“call”函数来间接调用;参数之间用逗号分割
常用函数简介:
$(substFROM,TO,TEXT)
函数名称:字符串替换函数—subst。
函数功能:把字串“TEXT”中的“FROM”字符替换为“TO”
。
返回值:替换后的新字符串。
示例:
$(substee,EE,feet on the street)
替换“feeton the street”中的“ee”为“EE”
,结果是新的字符串“fEEton the strEEt”
。