在 Linux 环境下编程,make 工具的使用以及编写 Makefile 文件是很重要的。当然,make 工具对在 Windows 环境下编程的程序员也是有用的。
Makefile 文件描述了整个工程的编译、连接等规则。其中包括:工程中的哪些源文件需要编译以及如何编译、需要创建那些中间文件以及如何创建这些中间文件、如何最后产生我们想要得可执行文件。尽管看起来可能是很复杂的事情,但是为工程编写 Makefile 的好处是能够使用一行命令来完成“自动化编译”,一旦提供了正确的 Makefile,编译整个工程你所要做的唯一的一件事就是输入 make 命令。整个工程完全自动编译,极大提高了效率。
make 是一个命令工具,它解释 Makefile 中的规则。在 Makefile 文件中描述了整个工程所有文件的编译顺序、编译规则。Makefile 有自己的书写格式、关键字、函数。而且在 Makefile 中可以使用系统 shell 所提供的任何命令来完成想要的工作。Makefile 在绝大多数的 IDE 开发环境中都在使用,已经成为一种工程的编译方法。
当对工程中的若干源文件修改以后,只需要重新执行 make 命令,就会自动根据修改情况完成源文件的对应目标文件的更新、中间文件的更新、最终的可执行程序的更新。make 通过比较对应文件的最后修改时间,来决定哪些文件需要更新、那些文件不需要更新。对需要更新的文件 make 就执行相应的命令来重建它,对于不需要重建的文件 make 什么也不做。
Makefile 文件的规则包含了文件之间的依赖关系和更新此规则目标所需要的命令,如下所示:
make 程序根据规则的依赖关系,决定是否执行规则所定义的命令的过程我们称之为执行规则。
通常,make 在执行命令行之前会把要执行的命令行输出到标准输出设备,我们称之为回显。但是,如果规则的命令行以字符“@”开始,则 make 在执行这个命令时就不会回显这个将要被执行的命令。
如果使用 make 的命令行参数“-n”或“--just-print”,那么 make 执行时只显示所要执行的命令(也包括了使用“@”字符开始的命令),但不会真正的去执行这些命令。
为了忽略一些无关命令执行失败的情况,我们可以在命令之前加一个减号“-”,来告诉 make 忽略此命令的执行失败。
默认的情况下,make 执行的是 Makefile 中的第一个规则,此规则的第一个目标称之为终极目标(就是一个 Makefile 最终需要更新或者创建的目标)。
好了,我们现在以“也谈在 .NET 平台上使用 Scala 语言(续)”这篇文章中的例子来简要说明了一下 Makefile 文件的编写方法。
在上图中,第一行的五个文件是源程序文件,最后一行的两个文件是我们最终要生成的目标文件。其余四个文件都是编译过程中产生的中间文件。
第1行到第8行定义了一些变量供以后使用。变量的引用方法是 $(变量名),我们在第8行中已经看到了。
第10行到第11行定义了一个模式规则,指明了如何从 .cs 文件生成 .dll 文件。在模式规则中,目标是一个带有模式字符“%”的文件,使用模式来匹配目标文件。文件名中的模式字符“%”可以匹配任何非空字符串,除模式字符以外的部分要求一致。例如:%.dll 匹配所有以 .dll 结尾的文件。模式规则的依赖文件中同样可以使用“%”,依赖文件中模式字符“%”的取值情况由目标中的“%”来决定。例如:对于模式规则 %.dll: %.cs ,它表示的含义是:所有的 .dll 文件依赖于对应的 .cs 文件。
在该模式规则的命令中,使用了以下自动化变量:
- $@: 规则的目标文件名
- $<: 规则的第一个依赖文件名
第13行定义的规则中的 all 是我们的终极目标。这个规则没有命令部分,仅仅是为了生成或更新它的依赖:dotnet.exe 和 CsTest.exe。
第15行到第16行定义的规则指明了如何从 dotnet.msil 生成 dotnet.exe。
第18行到第19行定义的规则指明了如何从 CsTest.cs 生成 CsTest.exe。
第21行到第22行定义的规则指明了如何从 dotnet.scala 生成 dotnet.msil。
第24行到第26行定义的规则用来运行编译好的目标文件。
第28行到第29行定义的规则用来清除当前目录下编译生成的所有文件。目标 clean 没有任何依赖文件,它只有一个目的,就是通过这个目标名来执行它所定义的命令。Makefile 文件中那些没有任何依赖只有执行动作的目标称为伪目标。可输入 make clean 来执行 clean 目标所定义的命令。
让我们来试试看吧:
可以看到,一切正常,所有的文件都按我们设想的进行编译:
- 调用 gmcs 三次编译出三个 .dll 文件,使用第10行到第11行定义的模式规则。
- 调用 scalac-net 编译出 dotnet.msil 文件,使用第21行到第22行定义的规则。
- 调用 ilasm 汇编出 dotnet.exe 文件,使用第15行到第16行定义的规则。
- 调用 gmcs 编译出 CsTest.exe 文件,使用第18行到第19行定义的规则。
让我们继续吧:
可以看出,第一个 make 命令什么也不干,因为所有的目标已经是最新的了。然后假设我们修改了 CsTest.cs 源程序文件,再次使用make 命令,正如我们所希望的,它仅仅重新编译 CsTest.cs 文件。最后,我们使用了 -n 参数执行 make -n clean命令,它仅仅显示这条要执行的命令,而不实际执行,所以 .exe 文件还在,没有被删除。
通过以上的学习,你应该已经初步了解如何编写 Makefile 文件。如需进一步学习,推荐:GUN make 中文手册。该手册非常长,讲的内容非常多,从中可以看出 make 工具的功能是非常强大的。
但是,我相信这篇文章中讲解的内容大多数情况下已经足够使用了,要相信“80/20法则”。而且,作为一个优秀的程序员,在面对一个复杂问题时,应该是寻求一种尽可能简单、直接并且高效的处理方式来解决,而不是将一个简单问题在实现上复杂化。如果想在简单问题上突出自己使用某种语言的熟练程度,是一种非常愚蠢、且不成熟的行为。
在 Windows 操作系统下可以用 nmake 命令来代替 make 工具,其 Makefile 文件基本上和 Linux 下的一致。
当然,下面两个东东有时候也是有用的:
对了,本文中的所有源代码可以到 http://bitbucket.org/ben.skyiv/scalanet/downloads/ 页面下载。
也可以使用 hg clone http://bitbucket.org/ben.skyiv/scalanet/ 命令下载。
关于 hg ,请参阅 Mercurial 备忘录。