关于在Ubuntu的终端上⾃动编译程序,使⽤GNUmake.
注意:这是我转载来的⽂档,但是这不是全⽂,我认为先学会了这写就可以了,如果后来有需要深⼊,请⾃⾏在⽹络中查.除此⽂外,⽹上这有英⽂版的关于GNU make信息,/software/make/manual/make.html#Top.
跟我⼀起写 Makefile,陈皓 (CSDN)
概述
——
什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个⼯作,但我觉得要作⼀个好的和professional的程序员,makefile还是要懂。这就好像现在有这么多的HTML的编辑器,但如果你想成为⼀个专业⼈⼠,你还是要了解HTML的标识的含义。特别在Unix下的软件编译,你就不能不⾃⼰写makefile了,会不会写makefile,从⼀个侧⾯说明了⼀个⼈是否具备完成⼤型⼯程的能⼒。
因为,makefile关系到了整个⼯程的编译规则。⼀个⼯程中的源⽂件不计数,其按类型、功能、模块分别放在若⼲个⽬录中,makefile定义了⼀系列的规则来指定,哪些⽂件需要先编译,哪些⽂件需要后编
译,哪些⽂件需要重新编译,甚⾄于进⾏更复杂的功能操作,因为makefile就像⼀个Shell脚本⼀样,其中也可以执⾏操作系统的命令。
makefile带来的好处就是——“⾃动化编译”,⼀旦写好,只需要⼀个make命令,整个⼯程完全⾃动编译,极⼤的提⾼了软件开发的效率。make是⼀个命令⼯具,是⼀个解释makefile中指令的命令⼯具,⼀般来说,⼤多数的IDE都有这个命令,⽐如:Delphi的
make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了⼀种在⼯程⽅⾯的编译⽅法。
现在讲述如何写makefile的⽂章⽐较少,这是我想写这篇⽂章的原因。当然,不同产商的make各不相同,也有不同的语法,但其本质都是在“⽂件依赖性”上做⽂章,这⾥,我仅对GNU的make进⾏讲述,我的环境是RedHat Linux 8.0,make的版本是3.80。必竟,这个make是应⽤最为⼴泛的,也是⽤得最多的。⽽且其还是最遵循于IEEE 1003.2-1992 标准的(POSIX.2)。
在这篇⽂档中,将以C/C++的源码作为我们基础,所以必然涉及⼀些关于C/C++的编译的知识,相关于这⽅⾯的内容,还请各位查看相关的编译器的⽂档。这⾥所默认的编译器是UNIX下的GCC和CC。
关于程序的编译和链接
—gnu编译器
—————————
在此,我想多说关于程序编译的⼀些规范和⽅法,⼀般来说,⽆论是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.
好,⾔归正传,GNU的make有许多的内容,闲⾔少叙,还是让我们开始吧。
Makefile 介绍
———————
make命令执⾏时,需要⼀个 Makefile ⽂件,以告诉make命令需要怎么样的去编译和链接程序。
⾸先,我们⽤⼀个⽰例来说明Makefile的书写规则。以便给⼤家⼀个感兴认识。这个⽰例来源于GNU的make使⽤⼿册,在这个⽰例中,我们的⼯程有8个C⽂件,和3个头⽂件,我们要写⼀个Makefile来告诉make命令如何编译和链接这⼏个⽂件。我们的规则是:
1)如果这个⼯程没有编译过,那么我们的所有C⽂件都要编译并被链接。
2)如果这个⼯程的某⼏个C⽂件被修改,那么我们只编译被修改的C⽂件,并链接⽬标程序。
3)如果这个⼯程的头⽂件被改变了,那么我们需要编译引⽤了这⼏个头⽂件的C⽂件,并链接⽬标程序。
只要我们的Makefile写得够好,所有的这⼀切,我们只⽤⼀个make命令就可以完成,make命令会⾃动智能地根据当前的⽂件修改的情况来确定哪些⽂件需要重编译,从⽽⾃⼰编译所需要的⽂件和链接⽬标程序。
⼀、Makefile的规则
在讲述这个Makefile之前,还是让我们先来粗略地看⼀看Makefile的规则。
target ... : prerequisites ...
command
...
...
target也就是⼀个⽬标⽂件,可以是Object File,也可以是执⾏⽂件。还可以是⼀个标签(Label),对于标签这种特性,在后续的“伪⽬标”章节中会有叙述。
prerequisites就是,要⽣成那个target所需要的⽂件或是⽬标。
command也就是make需要执⾏的命令。(任意的Shell命令)
这是⼀个⽂件的依赖关系,也就是说,target这⼀个或多个的⽬标⽂件依赖于prerequisites中的⽂件,其⽣成规则定义在command中。说⽩⼀点就是说,prerequisites中如果有⼀个以上的⽂件⽐target⽂件要新的话,command所定义的命令就会被执⾏。这就是Makefile的规则。也就是Makefile中最核⼼的内容。
说到底,Makefile的东西就是这样⼀点,好像我的这篇⽂档也该结束了。呵呵。还不尽然,这是Makefile的主线和核⼼,但要写好⼀个Makefile还不够,我会以后⾯⼀点⼀点地结合我的⼯作经验给你慢慢到来。内容还多着呢。:)
⼆、⼀个⽰例
正如前⾯所说的,如果⼀个⼯程有3个头⽂件,和8个C⽂件,我们为了完成前⾯所述的那三个规则,我们的Makefile应该是下⾯的这个样⼦的。
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
反斜杠(\)是换⾏符的意思。这样⽐较便于Makefile的易读。我们可以把这个内容保存在⽂件为“Mak
efile”或“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”的⽂件。
2、如果到,它会⽂件中的第⼀个⽬标⽂件(target),在上⾯的例⼦中,他会到“edit”这个⽂件,并把这个⽂件作为最终的⽬标⽂件。
3、如果edit⽂件不存在,或是edit所依赖的后⾯的 .o ⽂件的⽂件修改时间要⽐edit这个⽂件新,那么,他就会执⾏后⾯所定义的命令来⽣成edit这个⽂件。
4、如果edit所依赖的.o⽂件也存在,那么make会在当前⽂件中⽬标为.o⽂件的依赖性,如果到则再根据那⼀个规则⽣成.o⽂件。(这有点像⼀个堆栈的过程)
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会被重链接。
四、makefile中使⽤变量
在上⾯的例⼦中,先让我们看看edit的规则:
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
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 = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
于是,我们就可以很⽅便地在我们的makefile中以“$(objects)”的⽅式来使⽤这个变量了,于是我们的改良版makefile就变成下⾯这个样⼦:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(objects)
于是如果有新的 .o ⽂件加⼊,我们只需简单地修改⼀下 objects 变量就可以了。
关于变量更多的话题,我会在后续给你⼀⼀道来。
五、让make⾃动推导
GNU的make很强⼤,它可以⾃动推导⽂件以及⽂件依赖关系后⾯的命令,于是我们就没必要去在每⼀个[.o]⽂件后都写上类似的命令,因为,我们的make会⾃动识别,并⾃⼰推导命令。
只要make看到⼀个[.o]⽂件,它就会⾃动的把[.c]⽂件加在依赖关系中,如果make到⼀个whatever.o,那么whatever.c,就会是whatever.o的依赖⽂件。并且 cc -c whatever.c 也会被推导出来,于是,我们的makefile再也不⽤写得这么复杂。我们的是新的makefile ⼜出炉了。
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
.PHONY : clean
clean :
rm edit $(objects)
这种⽅法,也就是make的“隐晦规则”。上⾯⽂件内容中,“.PHONY”表⽰,clean是个伪⽬标⽂件。
关于更为详细的“隐晦规则”和“伪⽬标⽂件”,我会在后续给你⼀⼀道来。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论