没有合适的资源?快使用搜索试试~ 我知道了~
资源推荐
资源详情
资源评论
1.5
解剖
Makefile
1.5.1 工程管理器 make
当我们要编译成千上万个源程序文件的时候,光靠手工地使用 GCC 工具来达到目的也
许就会很没有效率,我们亟需一款能够帮助我们自动检查文件的更新情况,自动进行编译的
软件,GNU make(工程管理器 make 在不同环境有很多版本分支,比如 Qt 下的 qmake,
Windows 下的 nmake 等,下面提到的 make 指的是 Linux 下的 GNU make)就是这样
的一款软件。
而 Makefile,是 make 的配置文件,用来配置运行 make 的时候的一些相关细节,比
如指定编译选项,指定编译环境等等。一般而言,一个工程项目不管是简单还是复杂,每一
个源代码子目录都会有一个 Makefile 来管理,然后一般有个所谓的顶层 Makefile 来统一
管理所有的子目录 Makefile。
在捋起袖子准备大干一场之前,明确学习目的非常重要,因为 Makefile 的语法相对晦
涩,尤其对于没有任何 Linux 编程和 Shell 编程经验的新手而言,第一次打开 Makefile 阅
读常常有以为是乱码的幻觉!因此面对这样的东西初学者如果抱着对每一个细节“死追不放”
的心态可能会死得很惨,信心将被大大挫败,而信心和兴趣的缺失是学习最大的敌人。
假如你是实用主义者,为的是在 Linux 编程开发不被 Makefile 难倒,那我们学习
Makefile 的程度仅限于看得懂就行了,顶多有时会对某些大型项目的 Makefile 进行修改,
但绝对不需要你像对 C 语言那样达到“精通到骨子里”的程度,而这一节的内容就是为这
样的人准备的。另一方面,如果你是学院派,需要对工程管理做学术型研究,那可能出了阅
读以下内容之外还需要阅读其他专门探讨该专题的文献,但不管你是哪一类人,以下内容作
为学习 Makefile 的入门及提高的读物,应该算是这个地球上你能找得到的最贴心的资料了。
好了,下面通过一个经典例子,说明一下我们为什么需要 make 来管理工程项目:
1.5.2 概览性示例
假设我们有一个工程,这个工程总共有4个源文件,姑且叫做 a.c、b.c 以及 x.c 和 y.c
吧,他们最终将会链接生成可执行文件 image:
图1-37 由四个源文件产生的 image
在开发的过程当中,假设我们对 x.c 这个源文件进行了修改,那么,为了在最终的 image
当中体现出来,我们必须重新编译生成 x.o,然后必须重新编译链接生成 image 文件,此
过程中,其他未经修改的文件以及他们的目标文件都不需要改动:
图1-38 修改了 image 所依赖的其中一个文件
由于文件比较少,我们用肉眼就可以简单地辨别,究竟哪些要编译哪些不需要重新再搞
一遍,甚至所有文件重新编译一次也不是什么十恶不赦的事情。但是考虑一下一个由成千上
万个源文件组成的庞大工程,比如 Linux 源码,一旦我们对若干个地方进行了修改,重新
编译的文件则需要精心地挑选,否则如果整体编译必将会浪费大量时间,这个“精心挑选”
的任务,就留给 make 帮我们来实现。
现在,make 的工作目的就很清楚:编译那些需要编译的文件,那么究竟哪些文件需要
重新编译呢?这个原理也非常简单:根据文件的时间戳来进行判断。每个文件都会记录其最
近修改时间,我们只需要对比源文件及其生成的目标文件的时间戳,就可以判断他们的新旧
关系,从而决定要不要编译。比方说我们刚刚修改了 x.c 这个文件,那么他的时间戳将会被
更新为当前最新的系统时间,这样 make 通过对比就可以知道 x.c 比 x.o 要新,因此在需
要使用 x.o 的时候就会自动重新编译 x.o,这样又会导致 x.o 的时间戳比 image 要新,于
是 image 也会被自动重新编译,这种递推关系会在每一层目标-依赖之间传递。
图 1-39 Makefile 眼中的目标和依赖
在上面的例子中,image 是最终的目标,其依赖是四个可重定位文件,而对于每一个
可重定位文件而言,他们自己本身也是目标,依赖于其相对应的.c 源程序文件。在 make
的眼中,所有的文件都有这么一层一层递推的目标-依赖关系,然后通过对比目标和依赖的
时间戳来决定下一步动作,这就是 make 的最基本的工作原理。
下面从零开始,循序渐进,用几个例子将知识点一一攻破,最后看看 Linux 内核源码
中的顶层经典 Makefile,对细节查漏补缺。
1.5.3 书写格式
上面讲到,其实 make 的工作原理就是分析判断所谓的“目标-依赖”对,根据他们的
存在性和时间戳,来决定下一步动作,这个最根本最原始的工作原理其实跟什么工程管理是
没有关系的,比如我们可以写一个世界上最简单的 Makefile:
vincent@ubuntu:~$ cat Makefile -n
1 funny:
2 echo “just for fun”
vincent@ubuntu:~$ make
echo “just for fun”
just for fun
在这个最简单的 Makefile 只有两行,包含了其最核心的语法:第 1 行的 funny 被称
之为目标,因为他后面有一个冒号,冒号后面是这个目标的依赖列表,这个例子中 funny
的依赖列表为空,紧跟着第 2 行的行首是一个制表符(即 Tab 键),这个制表符很重要,不
能写成空格,更不能省略,其后紧跟着一个 Shell 语句(事实上就因为有了那个制表符,
make 才知道后面是一个 Shell 命令)。这个目标,以及其后的依赖列表(可以没有),以及
其下的 Shell 命令(可以没有),统称为一套规则。
我们在该 Makefile 所在目录执行 make 命令,结果打印一句“just for fun”。整个过
程中发生的事情是这样的:
1,make 首先判断 funny 这个目标的依赖列表是否都存在,如果是则判断他们跟目标的时
间戳关系,如果否则要确保依赖文件都存在。由于这个例子中 funny 没有依赖列表,因此
也就不需要判断他们是不是存在了。
2,判断目标 funny 是否已经存在,如果是则退出,如果否则执行下面的 Shell 命令。该例
子中 funny 显然是不存在的,因此将会执行 echo 语句,而且每次执行 echo 语句之后也都
不会产生 funny 这个文件,因此每次执行 make 都会打印一句“just for fun”。
现在,我们来改一下,将这个 Makefile 改成一个更实用一点:用来帮我们“自动”执
行编译的工作,比如上一节的 image,此时目标是 image,而其依赖则是四个.o 文件,而
且,这四个.o 文件本身也是目标,他们依赖于其对应的.c 文件,这个 Makefile 应该长成这
样:
vincent@ubuntu:~$ cat Makefile -n
1 image:a.o b.o x.o y.o
2 gcc a.o b.o x.o y.o -o image
3
4 a.o:a.c
5 gcc a.c -o a.o -c
6 b.o:b.c
7 gcc b.c -o b.o -c
8 x.o:x.c
9 gcc x.c -o x.o -c
10 y.o:y.c
11 gcc y.c -o y.o -c
这个简单的 Makefile 文件总共有 11 行,5 套规则,其中第 1 行中的 image 是第 1
个目标,冒号后面是这个目标的依赖列表(四个.o 可重定位文件)。第 2 行行首是一个制表
符,后面紧跟着一句 Shell 命令。
下面从第 4 行到第 11 行,也都是这样的目标-依赖对,及其相关的 Shell 命令。但是
这里必须注意一点:虽然这个 Makefile 总共出现了 5 个目标,但是第一个规则的目标(即
image)被称之为终极目标,终极目标指的是当你执行 make 的时候,默认生成的那个文
件。注意:如果第一个规则有多个目标,则只有第一个才是终极目标。另外,以圆点.开头
的目标不在此讨论范围内。
这个 Makefile 的工作流程是:
1,找到由终极目标构成的一套规则。(第 1 行和第 2 行)
2,如果终极目标及其依赖列表都存在,则判断他们的时间戳关系,只要目标比任何一
个依赖文件旧,就会执行其下面的 Shell 命令。
3,如果有任何一个依赖文件不存在,或者该依赖文件比该依赖文件的依赖文件要旧,
则需要执行以该依赖文件为目标的规则的 Shell 命令。(比如 a.o 如果不存在或者比 a.c 要
旧,则会找到第 4 行和第 5 行这一套规则,并执行第 5 行的 Shell 命令)
4,如果依赖文件都存在并且都最新,但是目标不存在,则执行其下面的 Shell 命令。
本例中,一开始所有的.o 文件都是不存在的,因此会执行第 5、第 7、第 9、第 11 行,
分别生成 a.o、b.o、x.o 和 y.o,等这些文件都准备妥当了,将会执行第 2 行生成最终的
目标文件 image。随后如果对任何一个源文件进行了修改(比如 x.c),执行 make 的时候
将会发现其对应的.o 文件(a.o)比该源文件(a.c)要旧,因此就会自动地重新编译(第 9
行),然后根据一样的原理,终极目标文件 image 也被重新编译。
1.5.4 变量详解
通过上面的例子,应该对 make 的工作原理及其配置文件 Makefile 的语法结构有个粗
浅的了解,但是感觉也没帮上什么忙,毕竟,写在 Makefile 里面的东西一点也没比直接在
终端敲命令省事,而且更要命的是:加入现在工程当中再加一个文件 z.c,要放在一起编译,
恐怕整个 Makefile 都需要重新修改一遍,另外,假设工程有 1000 个文件,貌似就要写 1000
套规则,这样的结论不免使我们沮丧。但事实上并不需要悲观,Makefile 提供了很多机制,
比如变量、函数等来帮助我们更好更方便地组织工作。
下面先来说说变量。
跟Shell脚本非常类似,在Makefile中也会使用“弱类型”变量(相对于C语言这种强
类型语言而言),在Makefile中变量就是一个名字(像是C语言中的宏),代表一个文本字符
串(变量的值)。在Makefile的目标、依赖、命令中引用一个变量的地方,变量会被它的值
所取代(与C语言中宏引用的方式相同,因此其他版本的make也把变量称之为“宏”)。
在Makefile中变量的特征有以下几点:
1. 变量和函数的展开(除规则的命令行以外),是在make读取Makefile文件时进行的,
这里的变量包括了使用“=”定义和使用指示符“define”定义的变量。
2. 变量可以用来代表一个文件名列表、编译选项列表、程序运行的选项参数列表、搜
索源文件的目录列表、编译输出的目录列表和所有我们能够想到的事物。
3. 变量名不能包括“:”、“#”、“=”、前置空白和尾空白的任何字符串。需要注意的是,
尽管在GNU make中没有对变量的命名有其它的限制,但定义一个包含除字母、数字和下
划线以外的变量的做法也是不可取的,因为除字母、数字和下划线以外的其它字符可能会在
以后的make版本中被赋予特殊含义,并且这样命名的变量对于一些Shell来说不能作为环
境变量使用。
4. 变量名是大小写敏感的。变量“foo”、“Foo”和“FOO”指的是三个不同的变量。
Makefile传统做法是变量名是全采用大写的方式。推荐的做法是在对于内部定义的一般变
量(例如:目标文件列表objects)使用小写方式,而对于一些参数列表(例如:编译选项
CFLAGS)采用大写方式,这并不是要求的。但需要强调一点:对于一个工程,所有Makefile
中的变量命名应保持一种风格,否则会显得你是一个蹩脚的开发者(就像代码的变量命名风
格一样),随时有被鄙视的危险。
5. 另外有一些变量名只包含了一个或者很少的几个特殊的字符(符号)。称它们为自动
化变量。像“<”、“@”、“?”、“*”、“@D”、“%F”、“^D”等等,后面会详述之。
6. 变量的引用跟Shell脚本类似,使用美元符号和圆括号,比如有个变量叫A,那么对
他的引用则是$(A),有个自动化变量叫@,则对他的引用是$(@),有个系统变量是CC则
对其引用的格式是$(CC)。对于前面两个变量而言,他们都是单字符变量,因此对他们引用
的括号可以省略,写成$A和$@。
Makefile 中有以下几种变量:
1,自定义变量,例如:
A = apple
B = I love China
C = $(A) tree
以上三个变量都是自定义变量,其中变量 A 包含了一个单词,变量 B 的值包含了三个
单词,变量 C 的值引用了变量 A 的值,因此他的值是“apple tree”。如果要将这三个变量
的值打印出来,可以这么写:
vincent@ubuntu:~$ cat Makefile -n
1 A = apple
2 B = I love China
3 C = $(A) tree
4
5 all:
6 @echo $(A) # echo
前面的
@
代表命令本身不打印出来
7 @echo $(B)
8 @echo $(C)
vincent@ubuntu:~$ make
apple
I love China
apple tree
使用自定义变量,可以将上述 Makefile 中的所有.o 文件用一个变量 OBJ 来代表:
vincent@ubuntu:~$ cat Makefile -n
1 OBJ = a.o b.o x.o y.o
2
剩余28页未读,继续阅读
资源评论
假装问路
- 粉丝: 0
- 资源: 6
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功