本文总结了makefile的概念及基础语法,不涉及makefire的内置函数。比较简单,是阅读、编写makefile基础。
1、makefile可以理解为是一门新的脚本语言,或者是一种工具,它专门用于编译:项目越庞大,代码文件越多,越能体现makefile的功能之强大。
2、makefile作为一门脚本语言,所以自然是属于解析型语言。解析型语言由解析器去解析成计算机所能识别的二进制语言并被执行。makefile的解析器是make。make作为Linux操作系统上的解析器,它也是一种ELF格式的可执行文件。
运行解析器一般后面跟上一个需要被解析并执行的文件。
3、默认情况下,执行make会在当前目录下查找名字为makefile/Makefile的文件。
4、作为一门语言,很多东西都是相同的,如入口、变量、操作符、流程控制、甚至是函数、数组这些东西。
(1) 入口
创建名为makefile的文件,键入:
all:
echo helloworld
makefile的入口可以是任意的单词,只需在后面跟上冒号。这里的入口是all。”all: “作为一个入口,它相当于c/c++中的函数名,函数名是一个函数的入口,”all: “是一条规则的入口。规则可以是任何的shell命令(一条命令写在一行),包括编译c/cpp文件的gcc/g++命令,链接懂太苦命令等等。运行该makefile:
makefile是一门规则性语言,以规则为单元(相当于c/c++中以函数为单元)。默认执行的是makefile脚本文件中的第一条规则。
函数可以传参,规则也可以,规则的传参称为规则的依赖,所以makefile的规则形式还可以为:
规则名: 规则依赖列表
shell命令1
shell命令2
...
shell命令n
注意,makefile语法规定,shell命令之前必须是tab键。
(2) 变量
a. 变量的定义位置只可以在规则名下方之外的位置,因为规则名下方是用于编写shell命令的地方。一般情况下变量定义在规则的上方。
b. 变量的定义十分简单,它没有类型的限制(一切都是字符串)。注意,变量名的命名规则和c/c++中的变量命名规则一样。
VAL = 123 #定义变量
all:
echo helloworld $(VAL)
c. 访问变量需要在变量名前加’$’符号,并将变量名置于括号内。运行:
d. makefile中变量名的赋值有如下几种方式:
变量名 = 新变量值 #访问变量时再展开变量值
变量名 := 新变量值 #变量赋值时就展开变量值
变量名 += 新变量值 #给变量追加值,自动加空格来分隔变量值
变量名 ?= 新变量值 #条件赋值,即变量存在不会赋值,不存在才赋值(赋值或者说定义)
关于”=”和”:=”举例加以说明:
VAL = 123
M_VAL = "hello" $(VAL) #定义M_VAL变量时需要拼接VAL
all:
echo helloworld $(M_VAL)
VAL = 123
M_VAL := "hello" $(VAL) #定义M_VAL变量时需要拼接VAL
all:
echo helloworld $(M_VAL)
(如上两个makefile文件)M_VAL不论是以”:=”还是”=”赋值,其运行结果都是一致的:
但是在”:=”定义M_VAL的makefile文件中,将VAL变量放在M_VAL变量的定义之后:
M_VAL := "hello" $(VAL)
VAL = 123
all:
echo helloworld $(M_VAL)
运行:
可见变量VAL并不能被拼接至M_VAL中,但是以”=”形式为M_VAL赋值则可以:
M_VAL = "hello" $(VAL)
VAL = 123
all:
echo helloworld $(M_VAL)
运行:
这是因为”:=”的M_VAL是在变量赋值时展开右边赋值语句,在这之后访问的访问操作将不会有展开操作;而用”=”是在访问的时候才去展开M_VAL,也就是说,每次访问都要展开M_VAL,所以效率上相对低一点。具体该使用何种赋值方式,取决于具体的需求咯。
5、makefile的基本单元是规则,规则冒号后面可以写上依赖(如果有的话)。比如要编译产生出a.out的可执行文件,它需要main.c文件。显然如果没有main.c文件,a.out将无法产生,所以需要将main.c作为产生a.out规则的依赖。另外,依赖除了可以是文件,还可以是其他规则。先看如下makefile:
M_VAL = "hello"
all:
echo helloworld $(M_VAL)
redhat:
echo redhat
ubuntu:
echo ubuntu
centos:
echo centos
因为make默认是执行makefile的第一条规则,所以all规则后面的规则将不会得到执行:
程序员可以在make的时候指明要执行的规则名,如:
还可以将其他规则作为第一条规则的依赖:
M_VAL = "hello"
all: redhat ubuntu centos
echo helloworld $(M_VAL)
redhat:
echo redhat
ubuntu:
echo ubuntu
centos:
echo centos
不难理解,make的执行顺序依次是redhat、ubuntu、centos、all。
若规则的依赖是其他规则,那么就会去执行其他规则;若规则的依赖是文件名,那么该依赖能够得到执行的前提是该文件不存在,存在时才会执行自身的规则。当前目录下没有hello.c,上面的redhet规则修改为:
readhat: hello.c
echo redhat
运行:
提示因hello.c而停止,在当下目录创建hello.c后,就可以正常make了:
那么问题来了,如果hello.c既是一个文件,也是一条规则时候,以hello.c作为依赖,那么make命令是以文件为准还是以规则为准?
M_VAL = "hello"
all: redhat ubuntu centos
echo helloworld $(M_VAL)
redhat: hello.c
echo redhat
ubuntu:
echo ubuntu
centos:
echo centos
hello.c:
echo hello.c
运行:
由运行结果可知,hello.c目标没有得到执行,所以结论是当规则名和文件冲突时(相同),makefile会以文件名为准。但是在这种冲突的情况下程序员想以hello.c作为规则名而非文件名使用,那就需要使用伪规则:
.PHONY: hello.c
hello.c:
echo hello.c
再运行:
将规则名定义为伪规则后,在规则名和文件名冲突的情况下,makefile看到的是规则而不去理会文件。
6、makefile提供了多个内置变量
$@ 代表规则名
$< 代表第一个依赖
$^ 代表全部依赖
这些变量运用在所在规则的shell代码中,如:
redhat: hello.c
echo $@ $<
7、通配符%,相当于shell命令中的*
%.o : %.c
gcc $< -o $.o
将当下目录所有的.c文件编译成对应的.o文件。
8、去掉shell命令的显示和静态编译
(1) 在规则中的shell命令前加上’@’符可以去掉该shell语言的命令的显示(只打印shell执行结果),如:
ubuntu:
@echo ubuntu
(2) 在执行make命令时加上”-s”选项即安静编译,去掉所有shell命令的回显。
9、默认情况下make命令解析的是当下目录下的makefile文件,而”-f”选项可以指定解析当前目录下的哪个文件(该文件可以不是不取名为 makefile);”-C” 指定要解析的文件的所在目录,它将工作目录切换到被指定的的目录中。
需要注意的是,注意,”-f”命令选项也可以指定解析文件的所在目录,只要在该文件名加上具体路径,但是它不会切换工作(到该目录)而只是单纯的解析该解析文件,”-C”则会切换工作路径。工作路径跟当前的shell路径是不同的(概念),可以理解为两个进程,一个是当前的shell父进程,一个是执行shell命令的子进程,工作路径指的是子进程的。
”-f”和”-C”同样可以作为makefile文件中规则的shell命令部分,这样就可以在makefile中执行别的makefile了(简称子makefile)。
makefile文件:
VAL = hello_world
export VAL #将VAL变量导出,使其在其它字makefile可见
all:
echo all $(VAL)
make -f config.mk #-f指定make要解析的文件
config.mk文件(子makefile):
sub:
echo sub $(VAL)
子makefle要想访问makefile的变量,需要在makefile中将用export将该变量导出。运行:
10、makefile中两个常用的函数
makefile的函数都是有返回值的,其返回值等于函数的执行结果。
(1)查找指定目录下指定类型的文件wildcard
例如:查找当前目录下的所有.c文件
src = $(wildcard ./*.c)
“wildcard”是函数名,“./*.c”是函数形参,“$()”表取函数的返回值结果
(2)匹配替换patsubst
例如:将当前目录下所有.c文件替换成.o文件
obj = $(patsubst %.c, %.o, $(src))
“patsubst”是函数名,“%.c, %.o, $(src)”是函数形参,“%.c”表示替换前文件的原格式,“%.o”表示替换后的目标文件的格式,“$(src)”表示原材料,即那些需要被匹配替换的文件名
使用范例:
src = $(wildcard ./*.c)
obj = $(patsubst %.c, %.o, $(src))
target = app
$(target) : $(obj)
gcc $^ -o $@
%.o : %.c
gcc -c $< -o $@
.PHONY: clean
clean:
rm *.o $(target)