文章目录
简介
目标
了解Ubuntu下自动化编译方法Makefile。
阅读基础
熟悉计算机。
环境说明
- windows10
参考资料
- 原子文档:I.MX6ULL开发指南 第三章
- NXP官方文档:I.MX6ULL参考手册、数据手册
Makefile规则格式
Makefile 里面是由一系列的规则组成的,这些规则格式如下:
目标…… : 依赖文件集合……
命令 1
命令 2
……
更新依赖
比如下面这条规则:
main : main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
这条规则的目标是 main
,main.o、input.o 和 calcu.o 是生成 main 的依赖文件,
- 如果要更新目标 main,就必须先更新它的所有依赖文件
- 如果依赖文件中的任何一个有更新,那么目标也必须新
“更新”就是执行一遍规则中的命令列表。
用TAB无空格
命令列表中的每条命令必须以 TAB 键开始,不能使用空格!
make 命令会为 Makefile 中的每个以 TAB 开始的命令创建一个 Shell 进程去执行。
Makefile执行顺序
示例分析
main: main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
main.o: main.c
gcc -c main.c
input.o: input.c
gcc -c input.c
calcu.o: calcu.c
gcc -c calcu.c
clean:
rm *.o
rm main
上述代码中一共有 5 条规则,
- 1~2 行为第一条规则
- 3~4 行为第二条规则
- 5~6 行为第三条规则
- 7~8 行为第四条规则
- 10~12 为第五条规则
make 命令在执行这个 Makefile 的时候其执行步骤如下:
-
首先更新第一条规则中的 main,第一条规则的目标成为默认目标,只要默认目标更新了那么就认为 Makefile 的工作完成。
-
在第一次编译的时候由于 main 还不存在,因此第一条规则会执行,第一条规则依赖于文件main.o、input.o 和 calcu.o 这个三个.o 文件。
-
这三个.o 文件目前还都没有,因此必须先更新这三个文件。make 会查找以这三个.o 文件为目标的规则并执行。以 main.o为例,发现更新 main.o 的是第二条规则,因此会执行第二条规则,第二条规则里面的命令为“gcc–c main.c”,这行命令很熟悉了吧,就是不链接编译 main.c,生成 main.o,其它两个.o 文件同理。
-
最后一个规则目标是 clean,它没有依赖文件,因此会默认为依赖文件都是最新的,所以其对应的命令不会执行,当我们想要执行 clean 的话可以直接使用命令“make clean”,执行以后就会删除当前目录下所有的.o 文件以及 main,因此 clean 的功能就是完成工程的清理,“make clean”的执行过程如图所示:
从图可以看出,当执行“make clean”命令以后,前面编译出来的.o 和 main 可执行文件都被删除掉了,也就是完成了工程清理工作。
执行过程总结
我们在来总结一下 Make 的执行过程:
- make 命令会在当前目录下查找以 Makefile(makefile 其实也可以)命名的文件。
- 当找到 Makefile 文件以后就会按照 Makefile 中定义的规则去编译生成最终的目标文件。
- 当发现目标文件不存在,或者目标所依赖的文件比目标文件新(也就是最后修改时间比目标文件晚)的话就会执行后面的命令来更新目标。
这就是 make 的执行过程,make 工具就是在 Makefile 中一层一层的查找依赖关系,并执行相应的命令,编译出最终的可执行文件。
Makefile的好处就是“自动化编译”,一旦写好了Makefile文件,以后只需要一个 make 命令即可完成整个工程的编译,极大的提高了开发效率。把 make和 Makefile 和做菜类似,目标都是呈现出一场盛宴,它们之间的对比关系如表所示:
make | 做菜 | 描述 |
---|---|---|
make工具 | 厨师 | 负责将材料加工成“美味” |
gcc编译工具 | 厨具 | 加工“美味”的工具 |
Makefile | 食材 | 加工美味所需的原材料 |
最终的可执行文件 | 最终的菜肴 | 最终目的,呈现盛宴 |
除了 Makefile 的“终极目标”所在的规则以外,其它规则的顺序在 Makefile 中是没有意义的。
“终极目标”就是指在使用 make 命令的时候没有指定具体的目标时,make 默认的那个目标,它是 Makefile 文件中第一个规则的目标,如果 Makefile 中的第一个规则有多个目标,那么这些目标中的第一个目标就是 make 的“终极目标”。
Makefile变量
示例分析
跟 C 语言一样 Makefile 也支持变量的,先看一下前面的例子:
main: main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
上述 Makefile 语句中,main.o input.o 和 calcue.o 这三个依赖文件,我们输入了两遍,我们这个 Makefile 比较小,如果 Makefile 复杂的时候这种重复输入的工作就会非常费时间,而且非常容易输错。
为了解决这个问题,Makefile 加入了变量支持。不像 C 语言中的变量有 int、char等各种类型,Makefile 中的变量都是字符串!类似 C 语言中的宏。使用变量将上面的代码修改,修改以后如下所示:
#Makefile 变量的使用
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)
我们来分析一下上述Makefile,
- 第 1 行是注释,Makefile 中可以写注释,注释开头要用符号“
#
”,不能用 C 语言中的“//
”或者“/**/
”! - 第 2 行我们定义了一个变量 objects,并且给这个变量进行了赋值,其值为字符串“
main.o input.o calcu.o
” - 第 3 和 4 行使用到了变量 objects,Makefile 中变量的引用方法是“
$(变量名)
”,比如本例中的“$(objects)
”就是使用变量 objects。
在“示例代码 ”中我们在定义变量 objects 的时候使用“=”对其进行了赋值,Makefile
变量的赋值符还有其它两个“:=”和“?=”,我们来看一下这三种赋值符的区别:
赋值符“=”
使用“=”在给变量的赋值的时候,不一定要用已经定义好的值,也可以使用后面定义的值,比如如下代码:
name = wx
curname = $(name)
name = weixin
print:
@echo curname: $(curname)
我们来分析一下上述代码,
- 第 1 行定义了一个变量 name,变量值为“wx”
- 第 2 行也定义了一个变量curname,curname的变量值引用了变量name,按照我们C写语言的经验此时curname的值就是“wx”。
- 第 3 行将变量 name 的值改为了“weixin”,
- 第 5、6 行是输出变量 curname的值。
在 Makefile 要输出一串字符的话使用“echo”,就和 C 语言中的“printf”一样,第 6 行中的“echo”前面加了个“@”符号,因为 Make 在执行的过程中会自动输出命令执行过程,在命令前面加上“@”的话就不会输出命令执行过程,大家可以测试一下不加“@”的效果。使用命令“make print”来执行上述代码,结果如图:
在图中可以看到 curname 的值不是“wx”,竟然是“weixin”,也就是变量“name”最后一次赋值的结果,这就是赋值符“=”的神奇之处!借助另外一个变量,可以将变量的真实值推到后面去定义。也就是变量的真实值取决于它所引用的变量的最后一次有效值。
赋值符“:=”
测试赋值符“:=”,修改上述中的第 2 行,将其中的“=”改为“:=”,修改完成以后的代码如下:
name = wx
curname := $(name)
name = weixin
print:
@echo curname: $(curname)
修改完成以后重新执行一下 Makefile,结果如图所示:
从图中可以看到此时的 curname 是 wx,不是 weixin 了。这是因为赋值符“:=”不会使用后面定义的变量,只能使用前面已经定义好的,这就是“=”和“:=”两个的区别。
赋值符“?=”
“?=”是一个很有用的赋值符,比如下面这行代码:
curname ?= weixin
上述代码的意思就是,如果变量 curname 前面没有被赋值,那么此变量就是“weixin”,如果前面已经赋过值了,那么就使用前面赋的值。
变量追加”+=“
Makefile 中的变量是字符串,有时候我们需要给前面已经定义好的变量添加一些字符串进
去,此时就要使用到符号“+=”,比如如下所示代码:
objects = main.o inpiut.o
objects += calcu.o
一开始变量 objects 的值为“main.o input.o”,后面我们给他追加了一个“calcu.o”,因此变
量 objects 变成了“main.o input.o calcu.o”,这个就是变量的追加。
Makefile模式规则
main: main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
main.o: main.c
gcc -c main.c
input.o: input.c
gcc -c input.c
calcu.o: calcu.c
gcc -c calcu.c
clean:
rm *.o
rm main
上述 Makefile 中第 3~8 行是将对应的.c 源文件编译为.o 文件,每一个 C 文件都要写一个对应的规则,如果工程中 C 文件很多的话显然不能这么做。为此,我们可以使用 Makefile 中的模式规则,通过模式规则我们就可以使用一条规则来将所有的.c 文件编译为对应的.o 文件。
模式规则中,至少在规则的目标定定义中要包涵“%
”,否则就是一般规则,当“%
”出现在目标中的时候,目标中“%
”所代表的值决定了依赖中的“%”值。
目标中的“%
”表示对文件名的匹配,“%
”表示长度任意的非空字符串,比如“%
.c”就是所有的以.c 结尾的文件**,类似与通配符**,a.%.c 就表示以 a.开头,以.c 结束的所有文件。使用方法如下:
%.o : %.c
命令
因此上述Makefile 可以改为如下形式:
main: main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
%.o : %.c
#命令
clean:
rm *.o
rm main
第 5、6 这两行代码替代了 前面Makefile的3~8 行代码,修改以后的 Makefile 还不能运行,因为第 6 行的命令我们还没写呢,第 6 行的命令我们需要借助另外一种强大的变量—自动化变量。
Makefile自动化变量
上面讲的模式规则中,目标和依赖都是一系列的文件,每一次对模式规则进行解析的时候都会是不同的目标和依赖文件,而命令只有一行,如何通过一行命令来从不同的依赖文件中生成对应的目标?自动化变量就是完成这个功能的!
所谓自动化变量就是这种变量会把模式中所定义的一系列的文件自动的挨个取出,直至所有的符合模式的文件都取完,自动化变量只应该出现在规则的命令中,常用的自动化变量如表:
表中的 7 个自动化变量中,常用的三种:$@、$<、$^
,我们使用自动化变量来完成模式规则中的代码,最终的完整代码如下所示:
#Makefile变量的使用
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)
%.o : %.c
gcc -c $<
clean:
rm *.o
rm main
上述代码代码就是修改后的完成的 Makefile,可以看出相比之前的写法要精简了很多,核心就在于第 6、7 这两行,第 6 行使用了模式规则,第 7 行使用了自动化变量。
Makefile伪目标
Makefile 有一种特殊的目标——伪目标,一般的目标名都是要生成的文件,而伪目标不代表真正的目标名,在执行 make 命令的时候通过指定这个伪目标来执行其所在规则的定义的命令。
使用伪目标主要是为了避免 Makefile 中定义的执行命令的目标和工作目录下的实际文件出现名字冲突,有时候我们需要编写一个规则用来执行一些命令,但是这个规则不是用来创建文件的,比如在前面的“示例代码”中有如下代码用来完成清理工程的功能:
clean:
rm *.o
rm main
上述规则中并没有创建文件 clean 的命令,因此工作目录下永远都不会存在文件 clean,当我们输入“make clean”以后,后面的“rm *.o”和“rm main”总是会执行。
可是如果我们“手贱”,在工作目录下创建一个名为“clean”的文件,那就不一样了,当执行“make clean”的时候,规则因为没有依赖文件,所以目标被认为是最新的,因此后面的 rm 命令也就不会执行,我们预先设想的清理工程的功能也就无法完成。
为了避免这个问题,我们可以将 clean 声明为伪目标,声明方式如下:
.PHONY : clean
我们使用伪目标来更改前面的代码,修改完成以后如下:
#Makefile变量的使用
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)
%.o : %.c
gcc -c $<
.PHONY:clean
clean:
rm *.o
rm main
上述代码第 9 行声明 clean 为伪目标,声明 clean 为伪目标以后不管当前目录下是否存在名为“clean”的文件,输入“make clean”的话规则后面的 rm 命令都会执行。如下图:
Makefile条件判断
在 C 语言中我们通过条件判断语句来根据不同的情况来执行不同的分支,Makefile 也支持条件判断,语法有两种如下:
<条件关键字> <条件为真时执行的语句>
endif
以及:
<条件关键字> <条件为真时执行的语句>
else
<条件为假时执行的语句>
endif
其中条件关键字有 4 个:ifeq、ifneq、ifdef 和 ifndef,这四个关键字其实分为两对、ifeq 与ifneq、ifdef 与 ifndef,先来看一下 ifeq 和 ifneq,ifeq 用来判断是否相等,ifneq 就是判断是否不相等,ifeq 用法如下:
ifeq (<参数 1>, <参数 2>)
ifeq ‘<参数 1 >’,‘ <参数 2>’
ifeq “<参数 1>”, “<参数 2>”
ifeq “<参数 1>”, ‘<参数 2>’
ifeq ‘<参数 1>’, “<参数 2>”
上述用法中都是用来比较“参数 1”和“参数 2”是否相同,如果相同则为真,“参数 1”和“参数 2”可以为函数返回值。
ifneq 的用法类似,只不过 ifneq 是用来了比较“参数 1”和“参数 2”是否不相等,如果不相等的话就为真。ifdef 和 ifndef 的用法如下:
ifdef <变量名>
如果“变量名”的值非空,那么表示表达式为真,否则表达式为假。“变量名”同样可以是一个函数的返回值。ifndef 用法类似,但是含义用户 ifdef 相反。
ifndef <变量名>
Makefile函数使用
Makefile 支持函数,类似 C 语言一样,Makefile 中的函数是已经定义好的,我们直接使用,不支持我们自定义函数。make 所支持的函数不多,但是绝对够我们使用了,函数的用法如下:
$(函数名 参数集合)
或者:
{函数名 参数集合}
可以看出,
- 调用函数和调用普通变量一样,使用符号”$"来标识。
- 参数集合是函数的多个参数,参数之间以逗号“,”隔开,
- 函数名和参数之间以“空格”分隔开,函数的调用以“$”开头。
接下来我们介绍几个常用的函数,其它的函数大家可以参考《跟我一起写 Makefile》这份文档。
函数 subst
函数 subst 用来完成字符串替换,调用形式如下:
$(subst <from>,<to>,<text>)
此函数的功能是将字符串<text>中的<from>内容替换为<to>,函数返回被替换以后的字符
串,比如如下示例:
$(subst zzk,ZZK,my name is zzk)
把字符串“my name is zzk”中的“zzk”替换为“ZZK”,替换完成以后的字符串为“my name is ZZK”。
函数patsubst
函数 patsubst 用来完成模式字符串替换,使用方法如下:
$(patsubst <pattern>,<replacement>,<text>)
此函数查找字符串<text>中的单词是否符合模式<pattern>,如果匹配就用<replacement>来替换掉,<pattern>可以使用通配符“%”,表示任意长度的字符串,函数返回值就是替换后的字符串。如果中也包涵“%”,那么<replacement>中的“%”将是<pattern>中的那个“%”所代表的字符串,比如:
$(patsubst %.c,%.o,a.c b.c c.c)
将字符串“a.c b.c c.c”中的所有符合“%.c”的字符串,替换为“%.o”,替换完成以后的字符串为“a.o b.o c.o”。
函数dir
函数 dir 用来获取目录,使用方法如下:
$(dir <names…>)
此函数用来从文件名序列<names>中提取出目录部分,返回值是文件名序列<names>的目录
部分,比如:
$(dir </src/a.c>)
提取文件“/src/a.c”的目录部分,也就是“/src”。
函数notdir
函数 notdir 看名字就是知道去除文件中的目录部分,也就是提取文件名,用法如下:
$(notdir <names…>)
此函数用与从文件名序列中提取出文件名非目录部分,比如:
$(notdir </src/a.c>)
提取文件“/src/a.c”中的非目录部分,也就是文件名“a.c”。
函数foreach
foreach 函数用来完成循环,用法如下:
$(foreach <var>, <list>,<text>)
此函数的意思就是把参数<list>中的单词逐一取出来放到参数<var>中,然后再执行<text>所
包含的表达式。每次<text>都会返回一个字符串,循环的过程中,<text>中所包含的每个字符串
会以空格隔开,最后当整个循环结束时,<text>所返回的每个字符串所组成的整个字符串将会是
函数 foreach 函数的返回值。
函数wildcard
通配符“%”只能用在规则中,只有在规则中它才会展开,如果在变量定义和函数使用时,通配符不会自动展开,这个时候就要用到函数 wildcard,使用方法如下:
(wildcard PATTERN…)
比如:
$(wildcard *.c)
上面的代码是用来获取当前目录下所有的.c 文件,类似“%”。关于 Makefile 的相关内容就讲解到这里,本节只是对 Makefile 做了最基本的讲解,确保大家能够完成后续的学习,Makefile 还有大量的知识没有提到,有兴趣的可以自行参考《跟我一起写 Makefile》这份文档来深入学习 Makefile。