📄 00000001.htm
字号:
<HTML><HEAD> <TITLE>BBS水木清华站∶精华区</TITLE></HEAD><BODY><CENTER><H1>BBS水木清华站∶精华区</H1></CENTER>发信人: raner (毕设好无聊呀!), 信区: Linux <BR>标 题: 如何写一个简单的Makefile mini-HOWTO <BR>发信站: BBS 水木清华站 (Fri Jun 19 09:05:43 1998) <BR> <BR>发信人: hey (吟风·魂之利刃), 信区: Unix <BR>标 题: [HOWTO] 如何写一个简单的Makefile <BR>发信站: 华南网木棉站 (Thu Jun 18 09:51:56 1998), 转信 <BR> <BR>第一章 如何写一个简单的Makefile <BR> <BR> 描述档案(Description File) <BR> <BR> 检查附属档案(Dependency Checking) <BR> <BR> 重建最小化(Minimizing Rebuilds) <BR> <BR>引用make (Invoking make) <BR> <BR> 语法的基本规则(Basic Rules of Syntax) <BR> <BR>当我们在提示符号之下下一个命令: <BR> <BR>$ make program <BR> <BR>就是说你要去”make”一个新版本□而且通常是最新版本□的程式. 如果这个程式 <BR>是一个执行档,你所下的这个命令意思就是说你想要完成有所必须的编译 <BR>(compiling)与连结(linking),然后糟出一一个执行档. 你可以使用make来使这些程 <BR>序自动化,不必不断键入为数可观的gcc(or cc)这些编译器指令. <BR> <BR>当我们讨论make的时候,我们把我们所要建造的程式(program)称做目标(target). <BR>程式是由一个或一个以上的档案汇集在一起所建造出来的,这些档案的关系分为必备 <BR>档案(prerequisites)与附属档案(dependents). 每一个构成程式的档案依序有他们 <BR>自己的必备档案和附属档案. <BR> <BR>例如,你藉由连结建造了可执行档. 一旦你的原始档(source file)或标头档(head <BR>file)改变了,你就必须再连结新的可执行档之前重新编译目的档(object file). 每 <BR>一个原始档都是一个目的档的必备档案. <BR> <BR>Make的优点就是它对附属的阶层关系是非常敏感的,像是原始档->目的档,目的档-> <BR>可执行档. 你负责在描述档(description file)中指定一些附属档案,这个描述档的 <BR>档名通常为makefile 或是Makefile. 但是make也知道自己所在的执行环境,它也会 <BR>自己决定许多它自己的附属档案. make会利用档案的档名,这些档案最近修改的时 <BR>间,和一些内建的规则,决定编译时要使用哪些档案与如何去建立它们. 在这样的技 <BR>术背景之下,之前所秀的那个简单的make指令会保证在阶层中所有建造目标时必须存 <BR>在的部分都会被更新. <BR> <BR> <BR> <BR>描述档案(Description File) <BR> <BR>假设你写了一个程式,程式由以下部分所组成: <BR> <BR>*用C语言写的原始档□ main.c iodat.c dorun.c <BR> <BR>*用组合语言写的程式码□lo.s ,此档案被C写成的原始档所呼叫 <BR> <BR>*一组位于 /usr/fred/lib/crtn.a 之中的函式库常式(library routine) <BR> <BR>如果你用手一一下指令建造这个程式,你会在提示符号下打入: <BR> <BR>$cc □c main.c <BR> <BR>$cc □c iodat.c <BR> <BR>$cc □c dorun.c <BR> <BR>$as □0 lo.o lo.s <BR> <BR>$cc □o program main.o iodat.o dorun.o lo.o /usr/fred/lib/crtn.a <BR> <BR>当然你也可以在一行cc命令之内就做好编译,组译,连结的工作(要下很长的一串指 <BR>令),但是在实际的程式设计环境下这是很少发生的(因为指令实在是又长又复杂),因 <BR>为以下原因: 首先,每一个原始档都可能被不同的人所建立或测试. 第二,一个大程 <BR>式会花掉好几小时的编译工作,所以程式设计师一般都会尽可能的使用已经存在的目 <BR>的档而不要再重新编译(可以节省时间). <BR> <BR>现在让我们来看看如何透过描述档下指令给make. 我们建立了一个新的档案叫做 <BR>makefile,这个档案和所有的原始码放在同一个目录之下. 为了方便起见,这个描述 <BR>档中的每一个指令和附属档案都明显的打出来(后面的章节会告诉你不用写的那么详 <BR>细也可以),很多对make来说都是不需要的. 这个描述档的内容如下: <BR> <BR> 1.program : main.o iodat.o dorun.o lo.o /usr/fred/lib/crtn.a <BR> 2.cc □o program main.o iodat.o dorun.o lo.o <BR> /usr/fred/lib/ctrn.a <BR> 3.main.o : main.c <BR> 4.cc □c main.c <BR> 5.iodat.o : iodat.c <BR> 6.cc □c iodat.c <BR> 7.dorun.o : dorun.c <BR> 8.cc □c dorun.c <BR> 9.lo.o : lo.s <BR> 10.as □0 lo.o lo.s <BR> <BR>在每一行左边的数字并不属于描述档的一部份,只是为了待会解说方便 <BR> <BR>这个描述档中包含了五个项目(或说是进入点)(entry). 每一个项目由一个含有冒号 <BR>(:)(叫做附属列[dependency line]或是规则列[rules line]),和一个或一个以上以 <BR>tab(4个字元空白)开头的命令列(command line). 在附属行那个冒号左边的叫做目 <BR>标(target);冒号左边的就是目标的必备档案. 受tab影响的(tab-indented)命令列, <BR>告诉make如何从他们的必须档案中建造出目标.从上面的描述档来看,第1列说明了 <BR>program这个目标依靠main.o,iodat.o,dorun.o,lo.o这些目的档,还有依靠函式库 <BR>/usr/fred/lib/crtn.a .第2列指定了从必备档案制造program这个目标档案所必须 <BR>下的编译器指令.(这些档案都是目的档与函式库,所以实际上并不用编译,只呼叫了 <BR>连结器(linker)而已). 假设program这的目标档案不存在,你可以下这个指令: <BR> <BR>$make program <BR> <BR>make会去执行第二行的命令. 如果其中一个目的档不在该怎么办呢? 你能够把这个 <BR>目的档当作参数传给make(例如:没有main.o ,你可以下指令$make main.o ,就可以 <BR>得到main.o这个档案),但是几乎不必这样做. Make最重要的贡献就在于它有能力可 <BR>以自己决定什么东西必须被建立(例如:在建立program时,如果少了main.o,则他会根 <BR>据附属列所指定的内容,自己建立main.o这个档案). <BR> <BR> <BR> <BR>检查附属档案(Dependency Checking) <BR> <BR>当你要求make去建造program这个目标时,make会去参考前面所列出的那一个描述档, <BR>但是,第二列的编译器指令并不会立刻就执行. make所做的动作应该如下: 首 <BR>先,make先去检查目录下是否有program这个档案,如果有的话,make会去检查main.o <BR>, iodat.o , dorun.o , lo.o , 还有/usr/fred/lib/crtn.a 这些档案,看看这些档 <BR>案有没有比program这个档案更新(更新的意思是说,这些档案比program这个档案建 <BR>造的时间更晚). 这个动作非常容易,因为作业系统会储存每一个档案最近被修改的 <BR>时间,你只要下一个指令ls □l就可以看到这个讯息. 如果program的建造时间比它 <BR>所有的必备档案的最近修改时间还要晚,make会决定不再重新建造program这个档案, <BR>然后不会发出认何指令就离开(跳回提示符号下). 但是在make下这个决定之前,它还 <BR>会做一些检查: make会根据附属列所描述的必备档案,去检查每一个 .o档案的必备 <BR>档案是否有更新的情形. <BR> <BR>例如,从第3列就可以看出main.o的建造必须依靠main.c. 因此,如果再main.o被建造 <BR>之后,main.c才又被修改,则make就会去执行第4列的指令重新建造一个新的main.o. <BR>只有在program的必备档都被检查而且更新过(这必备档的必备档也要被检查且更新 <BR>过. 例如:main.o是program的必备档,main.c是main.o的必备档). make才会去执行 <BR>第2列的指令建造program这个目标档案. 假设自从上一次建造program这个档案之 <BR>后,iodat.c是唯一被更新过的档案,所以当我们再次执行$make program <BR> <BR>之后,make所发出的编译器指令实际上只有 <BR> <BR>cc □c main.c <BR> <BR>cc □o program main.o iodat.o dorun.o lo.o /usr/fred/lib/crtn.a <BR> <BR>这两行指令而已. <BR> <BR>make命令执行以后,会在标准输出上印出它所发出的指令,因此当你使用make的时候, <BR>你可以从你的萤幕上看到它所发出的命令的顺序. <BR> <BR>总而言之,一个程式的建造包含了顺序正确的指令链结(chain). 一般而言,你只要要 <BR>求make去建造链结中最后的那个档案即可. make会透过附属档案链结(你在描述档中 <BR>所指定的那些必备档案所构成的树状结构构成附属档案链结),自己回朔追踪(traces <BR>back,也就是往树状结构的叶部方向)这个链结,然后找出哪些指令必须被执行. 最 <BR>后,make会慢慢在链结中前进(moves forward,就是往数状结构的根部移动),执行每 <BR>个建造目标所必须要有的指令直到目标建立完成(或被更新). 因为这种特性,make是 <BR>一个使用反项链结法(backward-chaining:在人工智慧领域中,一种搜索问题答案的 <BR>方法,它的搜索方向是由目标状态开始,然后向初始状态前进,最后再慢慢回来)这个 <BR>技巧最有名的例子,这个技巧通常仅使用在像是Prolog语言这一类大家比较不知道的 <BR>环境上. <BR> <BR> <BR> <BR>重建最小化(Minimizing Rebuilds) <BR> <BR>现在我们来讨论一个可以以各种不同版本形式存在的程式(通常是不同平台,或是不 <BR>同作业系统,或是要分散(release)给不同层级使用者的版本),这是一个可以告诉你 <BR>make如何节省你的时间,而且可以避免混淆的例子,比前的例子更复杂一点. 假设你 <BR>写了一个可以绘出资料的程式,它可以在终端机(文字模式)或是图形介面(例如:X <BR>window)之下执行. 涉及到计算和档案处理的部分在两个版本之中都相同,而且你把 <BR>它们都存放在basic.c这个档案中. 处理文字模式下使用者输入的程式放在prompt.c <BR>之中,而处理图形介面上使用者输入的程式放在window.c之中. <BR> <BR>因此,这个程式可以以两种不同的版本被发行(release),当你想要建立这个程式时, <BR>你可以选择要建立你觉得最适合你现在工作环境的版本. 以文字模式下的版本来说, <BR>你可以由basic.c与prompt.c这两个档案来产生plot_prompt这个执行档. 对图形介 <BR>面的版本来说,你就可以使用basic.c与window.c这两个档案来产生叫做plot_win的 <BR>执行档. 以下产生这两种版本所使用的眠述档: <BR> <BR>plot_prompt : basic.o prompt.o <BR> <BR>cc □o plot_prompt basic.o prompt.o <BR> <BR>plot_win : basic.o window.o <BR> <BR>cc □o plot_win basic.o window.o <BR> <BR>basic.o : basic.c <BR> <BR>cc □c basic.c <BR> <BR>prompt.o : prompt.c <BR> <BR>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -