2.5 Make
2.5.1 什么是 make?
当你写一个简单的程序,只有一到两个源文件的时候,输入
% cc file1.c file2.c
就没什么问题,但如果有很多源文件就会很烦人──编译的时间也会很长。
一个方法就是使用目标文件,只在源文件有改变的情况下才重新编译源文件。 因此你可以这样做:
% cc file1.o file2.o ... file37.c ...
上次编译后,file37.c 发生了改变,但其他文 件没有。这样做可以让编译过程快很多,但是也不能解决累人的输入问题。
或者我们可以使用一个 shell script 来解决输入问题,但是也需要重新编译 所有文件,在大型项目上很没有效率。
如果有成百上千的源文件的话怎么办?如果我们在与很多人合作写程序,别人 对源文件进行了修改,又没有告诉你,该怎么办?
也许我们可以把以上两种方法结合,写一种像 shell script 一样的东西。这 种文件包含某种技巧可以决定什么时候该对源文件进行编译。现在所有我们要的就是 一个程序可以懂得这种技巧,因为要懂得这种技巧,shell 还没那么大的能耐。
这个程序就叫 make。它读入一个文件,叫 makefile,这个文件决定了源文件之间的依赖关系。而且 决定了源文件什么时候该编译什么时候不应该编译。例如,某个规则可以说 “ 如果 fromboz.o 比 fromboz.c 要旧, 意思就是有人修改了 fromboz.c,因此我们需要重新编译这 个文件。”这个 makefile 还有规则通知 make 该 怎么 重新编译源文件,因此 make 是一个强大得多的工具。
makefile 通常和相关的源文件保存在同一个目录下,可以叫做 makefile,Makefile 或者 MAKEFILE。大多数程序员会使用 Makefile 这个名字,因为这样可以让这个文件被放在目录列 表的顶端,可以很容易得看见。 [1]
2.5.2 使用 make 的例子
这是一个非常简单的 make 文件:
foo: foo.c cc -o foo foo.c
包含两行,一行是依赖关系,一行是执行动作。
依赖关系的那一行包含了程序的名字 (叫做 target),紧跟着一个冒号,然后是空格,最后是源文件的 名字。当 make读入这一行的时候,会检查 foo 是否存在;如果存在,就比较 foo 和 foo.c 最后的修改时间有什 么不同。如果 foo 不存在,或者比 foo.c 要旧,就检查执行动作那一行看看该怎么做。换句话 说,就是 foo.c 需要重新编译的时候该怎么办。
执行动作那一行以一个 tab (按下 tab) 开始,然后是你在命令行下产生 foo 所执行的命令。如果 foo 过期了,或者不存在,make 就会 执行这个命令来产生 foo。换句话说,这就是重新编译 foo.c 的规则。
因此,当你输入 make 时,它会确定 foo 和 foo.c 在修改时间上是否同 步。这个原则可以在 Makefile 里扩展到成百上千的目标文 件上──实际上,在 FreeBSD 里,你只要在合适的目录下输入 make world 就可以编译整个操作系统!
makefile 另一个有用的特点就是目标文件不一定就是程序。例如,我们可以 有这样的 make 文件。
foo: foo.c cc -o foo foo.c install: cp foo /home/me
我们可以输入如下的命令告诉 make 该执行哪个目标:
% make target
make 会只执行这个目标而忽略其他的目标。例如,如果 我们输入 make foo,就只有 foo 被执行,必要的情况下重新编译 foo 而不会继续执行 install 这个目标。
如果我们只是输入 make 这个命令,make 总会寻找 第一个目标,并且在执行完以后就不管其他的目标了。例如,如果我们输入 make foo,make 就会转到 foo 这个目标,在必要的情况下重新编译 foo,而不会执行 install 目标, 然后就停止了。
一定要注意,install 这个目标不依赖任何其他 的东西!这意味着我们一旦输入 make install,这个目标 下的所有命令都将被执行。这种情况下,foo 将被安装到用 户的家目录下。应用程序的 makefile 正是这样写的,以便程序在正确编译后可以被 安装到正确的目录。
要尝试解释的话会比较容易让人混淆。如果你不太懂 make 是如何工作的,最好的办法就是先写一个简单的程序例如 “hello world” 以及和上面的例子相同的 make 文件再去实验。然后 再进一步,使用多个源文件,或者让你的源文件包含一个头文件。 touch 命令在这里就非常有用了──它能让在不改变文件内 容的情况下改变文件的日期。
2.5.3 Make 和 include-文件
C 源码的开头经常有一系列被包含的头文件,例如 stdio.h。有一些是系统级的 头文件,有一些是你正在写的项目的头文件:
#include <stdio.h> #include "foo.h" int main(....
要确定在你的 foo.h 被改变之后,这个文件也会被重 新编译,就要在你的 Makefile 这样写:
foo: foo.c foo.h
当你的项目变得越来越大,你自己的头文件越来越多的时候,要追踪所有这些 头文件和所有依赖它的文件会是一件痛苦的事情。如果你改变了其中一个头文件,却 忘了重新编译所有依赖它的源文件,结果会是很失望的。gcc 有一个选项可以分析你的源文件然后产生一个头文件的列表和它的依赖关系: -MM
。
如果你把下面的内容加到你的 Makefile 里面:
depend: gcc -E -MM *.c > .depend
然后运行 make depend,就会产生一个 .depend,里面包含了目标文件,C 文件和头文件的列表:
foo.o: foo.c foo.h
如果你改变了 foo.h,下一次运行 make 的时候,所有依赖 foo.h 的文件 就会被重新编译。
每一次你增加一个头文件的时候,别忘了运行一次 make depend。
2.5.4 FreeBSD 的 Makefile 文件
写 Makefile 文件可以是很难的一件事情。幸运的是,像 FreeBSD 这样基于 BSD 的系统,系统本身就自带了一些非常强大的 Makefile 文件。一个很好的例子就 是 FreeBSD 的 ports 系统。这里列出了一个典型的 ports 的 Makefile 的重要部分:
MASTER_SITES= ftp://freefall.cdrom.com/pub/FreeBSD/LOCAL_PORTS/ DISTFILES= scheme-microcode+dist-7.3-freebsd.tgz .include <bsd.port.mk>
现在,如果我们进入这个 port 的目录然后输入 make,就有如下的步骤发生:
检查这个 port 的源文件在系统中是否存在。
如果不存在,根据
MASTER_SITES
指定的 URL,系统 将会使用 FTP 连接下载这个源文件。计算源文件的校检码,然后与先前被记录的,已知的,完好的源文件校检 码对比。这样做就是要确认源文件在传送过程中没有损坏。
执行任何让源文件可以在 FreeBSD 上运行的修改──这叫做 打补丁。
完成源文件需要的特殊的配置。(很多 UNIX® 程序在被编译的时候都会 检测自己正在哪个版本的 UNIX 上面以及哪些可选的 UNIX 特性是存在的 ──这一步里面,在 FreeBSD 的 ports 框架中这些程序将会取得这些信息。
编译程序的源代码。实际上,我们将进入到源代码解压后生成的目录中, 然后再执行 make──程序自身的 make 文件包含了编 译程序所需要的信息。
现在我们有了一个编译好的程序。如果我们愿意,可以测试一下;当我们 对程序满意了以后,就输入 make install。这个命令 将把程序自身还有任何它依赖的文件都复制到正确的位置;在一个 包数据库 中会加上一条记录以便我们改变想法的时候可 以很简单的删除掉这个 port。
现在我想你一定会同意,对于一个只有四行的脚本,这让人印象非常深刻!
秘密就在最后一行,这一行告诉 make 去找系统级的 make 文件,叫做 bsd.port.mk。要忽略这一行很简单,但是 就是这一行做了所有聪明的工作──有人已经写好了一个 make 文件,让 make 去做刚才提到的步骤 (加上一些我没提到的,包括对可能 的错误的处理)。而且任何人都可以在自己的 make 文件中简单的加上一行命令来使 用这个文件!
如果你想看看这些系统级的 make 文件,可以到 /usr/share/mk 里面找找。不过最好等到你对 make 文件有 了一点点感性的经验以后再去看。因为这些文件都非常复杂(而且如果你在看的时 候,最好准备一大杯浓浓的绿茶!)。
2.5.5 更多 make 的高级用法
Make 是一个非常强大的工具,甚至还可以做比以上提到 的更复杂的工作。不幸的是,make 有不同的版本,各个版本之 间差别还很大。学习使用这个命令的最好的方法可能就是阅读文档──希望这一 章能够给你的学习打一个基础。
FreeBSD 自带的 make 叫做 Berkeley make; /usr/share/doc/psd/12.make 是一个教程。要看这个教程, 在那个目录中执行
% zmore paper.ascii.gz
Ports 中的很多应用程序使用 GNU make,它 包含一个非常棒的 “info” 页面的集合。如果你安装了任何一个这样 的 port,GNU make 就会自动被安装为 gmake 命令。当然你也可以用正常的 port 或 package 的方式 来安装。
要查看 GNU make 的 info 页面,你必须编辑 /usr/local/info 路径下的 dir 文 件,在其中添加一个条目。需要添加的内容可以是这样
* Make: (make). The GNU Make utility.
一旦你完成编辑,就可以输入命令 info 然后从菜 单中选择 make (或者在 Emacs 中,输入 C-h i)。
备注
[1] | 程序员不用 MAKEFILE 这样的形式。因为全部大 写的文件名经常是文档文件的名字比如 README。 |