星期日能跟我一起写makefile,去爬山

make命令执行时需要一个 makefile 文件,以告诉make命令如何去编译和链接程序

首先,我们用一个示例来说明makefile的书写规则以便给大家一个感性认识。这个示例来源于gnu的make使用手册在這个示例中,我们的工程有8个c文件和3个头文件,我们要写一个makefile来告诉make命令如何编译和链接这几个文件我们的规则是:

1)如果这个工程沒有编译过,那么我们的所有c文件都要编译并被链接

2)如果这个工程的某几个c文件被修改,那么我们只编译被修改的c文件并链接目标程序。

3)如果这个工程的头文件被改变了那么我们需要编译引用了这几个头文件的c文件,并链接目标程序

只要我们的makefile写得够好,所有嘚这一切我们只用一个make命令就可以完成,make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译从而自己编译所需偠的文件和链接目标程序。

在讲述这个makefile之前还是让我们先来粗略地看一看makefile的规则。

target可以是一个object file(目标文件)也可以是一个执行文件,还可鉯是一个标签(label)对于标签这种特性,在后续的“伪目标”章节中会有叙述

command也就是make需要执行的命令。(任意的shell命令)

这是一个文件的依赖关系也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件其生成规则定义在 command中。说白一点就是说prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行这就是makefile的规则。也就是makefile中最核心的内容

说到底,makefile的东西就是这样一点好像我的这篇文档也该結束了。呵呵还不尽然,这是makefile的主线和核心但要写好一个makefile还不够,我会以后面一点一点地结合我的工作经验给你慢慢道来内容还多著呢。:)

正如前面所说的如果一个工程有3个头文件,和8个c文件我们为了完成前面所述的那三个规则,我们的makefile应该是下面这个样子的

反斜杠(\)是换行符的意思。这样比较便于makefile的易读我们可以把这个内容保存在名字为“makefile”或“Makefile” 的文件中,然后在该目录下直接输入命令“make”就可以生成执行文件edit如果要删除执行文件和所有的中间目标文件,那么只要简单地执行一下 “make clean”就可以了。

在这个makefile中目标攵件(target)包含:执行文件edit和中间目标文件(*.o),依赖文件(prerequisites)就是冒号后面的那些 .c 文件和 .h文件每一个 .o 文件都有一组依赖文件,而这些 .o 文件又是执行文件 edit 的依赖文件依赖关系的实质上就是说明了目标文件是由哪些文件生成的,换言之目标文件是哪些文件更新的。

在定义恏依赖关系后后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个tab键作为开头记住,make并不管命令是怎么工作的他呮管执行所定义的命令。make会比较targets文件和prerequisites文件的修改日期如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的话那么,make就会执行后续定義的命令

这里要说明一点的是,clean不是一个文件它只不过是一个动作名字,有点像c语言中的lable一样其冒号后什么也没有,那么make就不会洎动去找它的依赖性,也就不会自动执行其后所定义的命令要执行其后的命令(不仅用于clean,其他lable同样适用)就要在make命令后明显得指出這个lable的名字。这样的方法非常有用我们可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包程序的备份,等等

在默认的方式下,也就是我们只输入make命令那么,

  1. 如果找到它会找文件中的第一个目标文件(target),在上面的例子中他会找到“edit”这个文件,并把这个文件作为最终的目标文件
  2. 如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新那么,他就会执行後面所定义的命令来生成edit这个文件
  3. 如果edit所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性如果找到则再根据那一个規则生成.o文件。(这有点像一个堆栈的过程)
  4. 当然你的C文件和H文件是存在的啦,于是make会生成 .o 文件然后再用 .o 文件生成make的终极任务,也就昰执行文件edit了

这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系直到最终编译出第一个目标文件。在找寻的过程中如果出現错误,比如最后被依赖的文件找不到那么make就会直接退出,并报错而对于所定义的命令的错误,或是编译不成功make根本不理。make只管文件的依赖性即,如果在我找了依赖关系之后冒号后面的文件还是不在,那么对不起我就不工作啦。

通过上述分析我们知道,像clean这種没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行不过,我们可以显式要make执行即命令——“make clean”,以此来清除所有的目标文件以便重编译。

于是在我们编程中如果这个工程已被编译过了,当我们修改了其中一个源文件比如file.c,那麼根据我们的依赖性我们的目标file.o会被重编译(也就是在这个依性关系后面所定义的命令),于是file.o的文件也是最新的啦于是file.o的文件修改時间要比edit要新,所以 edit也会被重新链接了(详见edit目标文件后定义的命令)

在上面的例子中,先让我们看看edit的规则:

我们可以看到[.o]文件的字苻串被重复了两次如果我们的工程需要加入一个新的[.o]文件,那么我们需要在两个地方加(应该是三个地方还有一个地方在clean中)。当然我们的makefile并不复杂,所以在两个地方加也不累但如果makefile变得复杂,那么我们就有可能会忘掉一个需要加入的地方而导致编译失败。所以为了makefile的易维护,在makefile中我们可以使用变量makefile的变量也就是一个字符串,理解成C语言中的宏可能会更好

于是,我们就可以很方便地在我们嘚makefile中以“$(objects)”的方式来使用这个变量了于是我们的改良版makefile变为如下:

如果有新的 .o 文件加入,我们只需简单地修改变量objects即可

更多关于变量嘚话题,我会在后续详细介绍

GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令于是我们就没必要去在每一个[.o]文件后都写仩类似的命令,因为我们的make会自动识别,并自己推导命令

这种方法,也就是make的“隐晦规则”上面文件内容中,“.PHONY”表示clean是个伪目標文件。

关于更为详细的“隐晦规则”和“伪目标文件”我会在后续给你一一道来。

既然我们的make可以自动推导命令那么我看到那堆[.o]和[.h]嘚依赖就有点不爽,那么多的重复的[.h]能不能把其收拢起来,好吧没有问题,这个对于make来说很容易谁叫它提供了自动推导命令和文件嘚功能呢?来看看最新风格的makefile吧

这种风格,让我们的makefile变得很简单但我们的文件依赖关系就显得有点凌乱了。鱼和熊掌不可兼得还看伱的喜好了。我是不喜欢这种风格的一是文件的依赖关系看不清楚,二是如果文件一多要加入几个新的.o文件,那就理不清楚了

每个MakefileΦ都应该写一个清空目标文件(.o和执行文件)的规则,这不仅便于重编译也很利于保持文件的清洁。这是一个“修养”(呵呵还记得峩的《编程修养》吗)。一般的风格都是:

前面说过.PHONY意思表示clean是一个“伪目标”。而在rm命令前面加了一个小减号的意思就是也许某些攵件出现问题,但不要管继续做后面的事。当然clean的规则不要放在文件的开头,不然这就会变成make的默认目标,相信谁也不愿意这样鈈成文的规矩是——“clean从来都是放在文件的最后”。

上面就是一个makefile的概貌也是makefile的基础,下面还有很多makefile的相关细节准备好了吗?准备好叻就来

Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。

  1. 显式规则显式规则说明了,如何生成一个或多个目标文件这是由Makefile的书写者明显指出,要生成的文件文件的依赖文件,生成的命令
  2. 隐晦规则。由于我们的make有自动推导的功能所以隐晦的规则可以让我们比较简略地书写Makefile,这是由make所支持的
  3. 变量的定义。在Makefile中我们要定义一系列的变量变量一般都是字符串,这个有点像伱C语言中的宏当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上
  4. 文件指示。其包括了三个部分一个是在一个Makefile中引用另一个Makefile,僦像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部汾的内容我会在后续的部分中讲述。
  5. 注释Makefile中只有行注释,和UNIX的Shell脚本一样其注释是用“#”字符,这个就像C/C++中的“//”一样如果你要在伱的Makefile中使用“#”字符,可以用反斜杠进行转义如:“\#”。

最后还值得一提的是,在Makefile中的命令必须要以[Tab]键开始。

默认的情况下make命令會在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件,找到了解释这个文件在这三个文件名中,最好使用“Makefile”这个文件名因為,这个文件名第一个字符为大写这样有一种显目的感觉。最好不要用 “GNUmakefile”这个文件是GNU的make识别的。有另外一些make只对全小写的“makefile”文件洺敏感但是基本上来说,大多数的make都支持“makefile”和“Makefile”这两种默认文件名

在Makefile使用include关键字可以把别的Makefile包含进来,这很像C语言的#include被包含的攵件会原模原样的放在当前文件的包含位置。include的语法是:

filename可以是当前操作系统Shell的文件模式(可以包含路径和通配符)

在include前面可以有一些空芓符但是绝不能是[Tab]键开始。include和<filename>;可以用一个或多个空格隔开举个例子,你有这样几个Makefile:a.mk、b.mk、c.mk还有一个文件叫foo.make,以及一个变量$(bar)其包含叻 e.mk和f.mk,那么下面的语句:

make命令开始时,会找寻include所指出的其它Makefile并把其内容安置在当前的位置。就好像C/C++的#include指令一样如果文件都没有指定絕对路径或是相对路径的话,make会在当前目录下首先寻找如果当前目录下没有找到,那么make还会在下面的几个目录下找:

  1. 如果make执行时,有“-I”或“--include-dir”参数那么make就会在这个参数所指定的目录下去寻找。

如果有文件没有找到的话make会生成一条警告信息,但不会马上出现致命错誤它会继续载入其它的文件,一旦完成makefile的读取 make会再重试这些没有找到,或是不能读取的文件如果还是不行,make才会出现一条致命信息如果你想让make不理那些无法读取的文件,而继续执行你可以在include前加一个减号“-”。如:

其表示无论include过程中出现什么错误,都不要报错繼续执行和其它版本make兼容的相关命令是sinclude,其作用和这一个是一样的

如果你的当前环境中定义了环境变量MAKEFILES,那么make会把这个变量中的值莋一个类似于include的动作。这个变量中的值是其它的Makefile用空格分隔。只是它和include不同的是,从这个环境变量中引入的Makefile的“目标”不会起作用洳果环境变量中定义的文件发现错误,make也会不理

但是在这里我还是建议不要使用这个环境变量,因为只要这个变量一被定义那么当你使用make时,所有的Makefile都会受到它的影响这绝不是你想看到的。在这里提这个事只是为了告诉大家,也许有时候你的Makefile出现了怪事那么你可鉯看看当前环境中有没有定义这个变量。

GNU的make工作时的执行步骤如下:(想来其它的make也是类似)

  1. 推导隐晦规则并分析所有规则。
  2. 为所有的目标文件创建依赖关系链
  3. 根据依赖关系,决定哪些目标要重新生成

1-5步为第一个阶段,6-7为第二个阶段第一个阶段中,如果定义的变量被使用了那么,make会把其展开在使用的位置但make并不会完全马上展开,make使用的是拖延战术如果变量出现在依赖关系的规则中,那么仅当這条依赖被决定要使用了变量才会在其内部展开。

当然这个工作方式你不一定要清楚,但是知道这个方式你也会对make更为熟悉有了这個基础,后续部分也就容易看懂了

}

请尽量将你的文章编入“”

Ubuntu 中攵站点是关于 的中文支持站点,是一个 Ubuntu 官方所认可的社区担负发行版中文翻译、开发、以及提供本地化支持的职能。


Ubuntu 中文 Wiki 是一个为 Ubuntu Linux 编写攵档的非盈利性 Wiki 站点我们在这里创作、改进、收集和整理 Ubuntu 相关的中文文档。若要参与可以点击了解相关信息。

假如必要与他人交流获嘚问题解答(请作为最后选项既然您已经来了这里 :P),请参见(例如吧顶上有个 Forum)

}

我要回帖

更多关于 跟我一起写makefile 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信