跟我一起写 Makefile

跟我起写 Makefile


1Dpfbaiducuk068 陈皓

概述
1Dpfbaiducuk068——

什么是makefile?或许很多Winodws员都不知道这个东西那些WindowsIDE都为你做了这个工作但我觉得要作个好和professionalmakefile还是要懂这就好像现在有这么多HTML编辑器但如果你想成为个专业人士你还是要了解HTML标识含义特别在Unix下软件编译你就不能不自己写makefile了会不会写makefile个侧面说明了个人是否具备完成大型工程能力

makefile关系到了整个工程编译规则个工程中源文件不计数其按类型、功能、模块分别放在若干个目录中makefile定义了系列规则来指定哪些文件需要先编译哪些文件需要后编译哪些文件需要重新编译甚至于进行更复杂功能操作makefile就像个Shell脚本其中也可以执行操作系统命令

makefile带来好处就是——“自动化编译”旦写好只需要个make命令整个工程完全自动编译极大提高了软件开发效率make是个命令工具个解释makefile中指令命令工具般来说大多数IDE都有这个命令比如:DelphimakeVisual CnmakeLinux下GNUmake可见makefile都成为了种在工程方面编译方法

现在讲述如何写makefile文章比较少这是我想写这篇文章原因当然不同产商make各不相同也有不同语法但其本质都是在“文件依赖性”上做文章这里我仅对GNUmake进行讲述环境是RedHat Linux 8.0make版本是3.80必竟这个make是应用最为广泛也是用得最多而且其还是最遵循于IEEE 1003.2-1992 标准(POSIX.2)

在这篇文档中将以C/C源码作为我们基础所以必然涉及些关于C/C编译知识相关于这方面内容还请各位查看相关编译器文档这里所默认编译器是UNIX下GCC和CC

 

关于编译和链接
1Dpfbaiducuk068——————————

在此我想多说关于编译些规范和方法般来说无论是C、C、还是pas首先要把源文件编译成中间代码文件在Windows下也就是 .obj 文件UNIX下是 .o 文件即 Object File这个动作叫做编译(compile)然后再把大量Object File合成执行文件这个动作叫作链接(link)

编译时编译器需要是语法正确与变量声明正确对于后者通常是你需要告诉编译器头文件所在位置(头文件中应该只是声明而定义应该放在C/C文件中)只要所有语法正确编译器就可以编译出中间目标文件般来说每个源文件都应该对应于个中间目标文件(O文件或是OBJ文件)

链接时主要是链接和全局变量所以我们可以使用这些中间目标文件(O文件或是OBJ文件)来链接我们应用链接器并不管所在源文件只管中间目标文件(Object File)在大多数时候由于源文件太多编译生成中间目标文件太多而在链接时需要明显地指出中间目标文件名这对于编译很不方便所以我们要给中间目标文件打个包在Windows下这种包叫“库文件”(Library File)也就是 .lib 文件在UNIX下是Archive File也就是 .a 文件

总结源文件首先会生成中间目标文件再由中间目标文件生成执行文件在编译时编译器只检测语法、变量是否被声明如果未被声明编译器会给出个警告但可以生成Object File而在链接链接器会在所有Object File中找寻实现如果找不到那到就会报链接码(Linker Error)在VC下这种般是:Link 2001意思说是说链接器未能找到实现你需要指定Object File.

言归正传GNUmake有许多内容闲言少叙还是让我们开始吧

 

Makefile 介绍
1Dpfbaiducuk068———————

make命令执行时需要个 Makefile 文件以告诉make命令需要怎么样去编译和链接

首先我们用个示例来说明Makefile书写规则以便给大家个感兴认识这个示例来源于GNUmake使用手册在这个示例中我们工程有8个C文件和3个头文件我们要写个Makefile来告诉make命令如何编译和链接这几个文件我们规则是:
1Dpfbaiducuk068    1)如果这个工程没有编译过那么我们所有C文件都要编译并被链接
1Dpfbaiducuk068    2)如果这个工程某几个C文件被修改那么我们只编译被修改C文件并链接目标
1Dpfbaiducuk068    3)如果这个工程头文件被改变了那么我们需要编译引用了这几个头文件C文件并链接目标

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


1Dpfbaiducuk068、Makefile规则

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

    target ... : prerequisites ...
1Dpfbaiducuk068             command
1Dpfbaiducuk068             ...
1Dpfbaiducuk068             ...

    target也就是个目标文件可以是Object File也可以是执行文件还可以是个标签(Label)对于标签这种特性在后续“伪目标”章节中会有叙述

    prerequisites就是要生成那个target所需要文件或是目标

    command也就是make需要执行命令(任意Shell命令)

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

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


1Dpfbaiducuk068二、个示例

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

    edit : .o kbd.o command.o display.o \
1Dpfbaiducuk068           insert.o search.o files.o utils.o
1Dpfbaiducuk068             cc -o edit .o kbd.o command.o display.o \
1Dpfbaiducuk068                          insert.o search.o files.o utils.o

    .o : .c defs.h
1Dpfbaiducuk068             cc -c .c
1Dpfbaiducuk068    kbd.o : kbd.c defs.h command.h
1Dpfbaiducuk068             cc -c kbd.c
1Dpfbaiducuk068    command.o : command.c defs.h command.h
1Dpfbaiducuk068             cc -c command.c
1Dpfbaiducuk068    display.o : display.c defs.h buffer.h
1Dpfbaiducuk068             cc -c display.c
1Dpfbaiducuk068    insert.o : insert.c defs.h buffer.h
1Dpfbaiducuk068             cc -c insert.c
1Dpfbaiducuk068    search.o : search.c defs.h buffer.h
1Dpfbaiducuk068             cc -c search.c
1Dpfbaiducuk068    files.o : files.c defs.h buffer.h command.h
1Dpfbaiducuk068             cc -c files.c
1Dpfbaiducuk068    utils.o : utils.c defs.h
1Dpfbaiducuk068             cc -c utils.c
1Dpfbaiducuk068    clean :
1Dpfbaiducuk068             rm edit .o kbd.o command.o display.o \
1Dpfbaiducuk068                insert.o search.o files.o utils.o

反斜杠(\)是换行符意思这样比较便于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就不会自动去找文件依赖性也就不会自动执行其后所定义命令要执行其后命令就要在make命令后明显得指出这个lable名字这样方法非常有用我们可以在个makefile中定义不用编译或是和编译无关命令比如打包备份等等

三、make是如何工作

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

    1、make会在当前目录下找名字叫“Makefile”或“makefile”文件
1Dpfbaiducuk068    2、如果找到它会找文件中个目标文件(target)在上面例子中他会找到“edit”这个文件并把这个文件作为最终目标文件
1Dpfbaiducuk068    3、如果edit文件不存在或是edit所依赖后面 .o 文件文件修改时间要比edit这个文件新那么他就会执行后面所定义命令来生成edit这个文件
1Dpfbaiducuk068    4、如果edit所依赖.o文件也存在那么make会在当前文件中找目标为.o文件依赖性如果找到则再根据那个规则生成.o文件(这有点像个堆栈过程)
1Dpfbaiducuk068    5、当然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目标文件后定义命令)

而如果我们改变了“command.h”那么kdb.o、command.o和files.o都会被重编译并且edit会被重链接


1Dpfbaiducuk068四、makefile中使用变量

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

      edit : .o kbd.o command.o display.o \
1Dpfbaiducuk068                   insert.o search.o files.o utils.o
1Dpfbaiducuk068             cc -o edit .o kbd.o command.o display.o \
1Dpfbaiducuk068                         insert.o search.o files.o utils.o

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

比如我们声明个变量叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ反正不管什么啦只要能够表示obj文件就行了我们在makefile开始就这样定义:

     objects = .o kbd.o command.o display.o \
1Dpfbaiducuk068               insert.o search.o files.o utils.o

于是我们就可以很方便地在我们makefile中以“$(objects)”方式来使用这个变量了于是我们改良版makefile就变成下面这个样子:

    objects = .o kbd.o command.o display.o \
1Dpfbaiducuk068               insert.o search.o files.o utils.o

    edit : $(objects)
1Dpfbaiducuk068             cc -o edit $(objects)
1Dpfbaiducuk068    .o : .c defs.h
1Dpfbaiducuk068             cc -c .c
1Dpfbaiducuk068    kbd.o : kbd.c defs.h command.h
1Dpfbaiducuk068             cc -c kbd.c
1Dpfbaiducuk068    command.o : command.c defs.h command.h
1Dpfbaiducuk068             cc -c command.c
1Dpfbaiducuk068    display.o : display.c defs.h buffer.h
1Dpfbaiducuk068             cc -c display.c
1Dpfbaiducuk068    insert.o : insert.c defs.h buffer.h
1Dpfbaiducuk068             cc -c insert.c
1Dpfbaiducuk068    search.o : search.c defs.h buffer.h
1Dpfbaiducuk068             cc -c search.c
1Dpfbaiducuk068    files.o : files.c defs.h buffer.h command.h
1Dpfbaiducuk068             cc -c files.c
1Dpfbaiducuk068    utils.o : utils.c defs.h
1Dpfbaiducuk068             cc -c utils.c
1Dpfbaiducuk068    clean :
1Dpfbaiducuk068             rm edit $(objects)


1Dpfbaiducuk068于是如果有新 .o 文件加入我们只需简单地修改下 objects 变量就可以了

关于变量更多话题我会在后续给你道来


1Dpfbaiducuk068五、让make自动推导

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

只要make看到个[.o]文件它就会自动把[.c]文件加在依赖关系中如果make找到个whatever.o那么whatever.c就会是whatever.o依赖文件并且 cc -c whatever.c 也会被推导出来于是我们makefile再也不用写得这么复杂我们是新makefile又出炉了


1Dpfbaiducuk068    objects = .o kbd.o command.o display.o \
1Dpfbaiducuk068               insert.o search.o files.o utils.o

    edit : $(objects)
1Dpfbaiducuk068             cc -o edit $(objects)

    .o : defs.h
1Dpfbaiducuk068    kbd.o : defs.h command.h
1Dpfbaiducuk068    command.o : defs.h command.h
1Dpfbaiducuk068    display.o : defs.h buffer.h
1Dpfbaiducuk068    insert.o : defs.h buffer.h
1Dpfbaiducuk068    search.o : defs.h buffer.h
1Dpfbaiducuk068    files.o : defs.h buffer.h command.h
1Dpfbaiducuk068    utils.o : defs.h

    .PHONY : clean
1Dpfbaiducuk068    clean :
1Dpfbaiducuk068             rm edit $(objects)

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

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


1Dpfbaiducuk068六、另类风格makefile

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

    objects = .o kbd.o command.o display.o \
1Dpfbaiducuk068               insert.o search.o files.o utils.o

    edit : $(objects)
1Dpfbaiducuk068             cc -o edit $(objects)

    $(objects) : defs.h
1Dpfbaiducuk068    kbd.o command.o files.o : command.h
1Dpfbaiducuk068    display.o insert.o search.o files.o : buffer.h

    .PHONY : clean
1Dpfbaiducuk068    clean :
1Dpfbaiducuk068             rm edit $(objects)

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


1Dpfbaiducuk068七、清空目标文件规则

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

        clean:
1Dpfbaiducuk068             rm edit $(objects)

更为稳健做法是:

        .PHONY : clean
1Dpfbaiducuk068        clean :
1Dpfbaiducuk068                 -rm edit $(objects)

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


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

Tags: 

延伸阅读

最新评论

发表评论