中山野鬼 linux 下 C 编程和make的方法 (4、开始make)

终于开始make了
    make 是个命令,先谈一下,为何要make?
    首先的理由就是,你能够将前面不少gcc的命令行,汇总起来,而且一次确认,多个命令自动运行。我相信不少人说我在忽悠。这就是个“批处理”,就是加上复杂的变量替换,条件执行,也就是个脚本。没错,make首先就是个脚本分析执行的工做。
    但make有优点的地方在于依赖的检查。什么是依赖,初学者看GNU make之类的文档,别说英文版,就是中文“依赖”也够理解半天。这里重复说个生成.o文件的意义。意义就是不要对没有改动的过的C文件,进行再次编译。 这里隐含了“依赖”的概念。.o文件之因此不须要再编译,是依赖一个前提,C文件没有被改动过。可是C文件没有被改动过,不表明其#include的.h 文件就没有改动过啊。由此所谓“依赖”对于make而言,是一个操做存在的前提条件。当操做的前提条件没有刷新时,本身也就没有必要刷新。
    可是linux操做系统,只会孤立的判断每一个文件是否被改动过了。你C文件里#include了一个h文件,OS没时间帮你慢慢分析,而make也不会帮你自动阅读理解C文件。这些都是gcc的事情。得,你们把责任都推给GCC。
    GCC也很无辜,“关我毛事啊!你告诉我要编译,我就编译,你不告诉我要编译,我为何要编译。个人预编译系统只会识别#include 而后在对应的目录里找到文件,并打开,添加。这个文件有没有被修改过,第一我不知道,第二我不须要知道。若是你把执行文件删除了,我就是知道全部源文件没 有被改动过,你让我编译,难道我告诉你‘哈哈,通过个人这么这么分析,发现文件没有被修改过,因此我不会生成执行文件,若是执行文件被别人删除,请找相关 部门,寻求找回,我很智能的’”。
    如今咱们讨论下来,文件的依赖性,看来没有任何工具能够主动跳出来承担责任,此时,make跑出来,很负责的说了一句“其实我能作,但须要你,设计者,愿 意描述依赖关系,那么我make能够根据你的依赖关系进行检查”。这就是make区别其余shell下的普通脚本工具的价值。

    所以,对于make几个基本概念,如今要有:
    一、咱们用make主要是针对文件进行处理的。由于依赖关系的做用域(有效范围)是工程,工程内部有价值的依赖一般是多个文件之间的因果关系。
    二、make除了依次执行调用其余工具或命令,作所谓批处理的事情外,依赖关系的检查是他的一个重要特性。否则他跳出来主动担责任就是一种“欺骗”,准确说是“行政不做为”的表现。
    你把计划和依赖,描述在一个文件里,传递给make"。一般有两类作法。
1 $make -f filename
    filename是一个文件名。但不是潜规则要求的文件名。这样作一般用于临时性的make使用,正式状况,不须要如此折腾。潜规则有个好处,你被潜规则了,其余人也会被潜规则。这样下降你和其余人的交流成本。
    由此,另外一个作法是将你的计划,写在名为 GNUmakefile ,makefile ,Makefile这几种文件名中任意一个。恩。make的潜规则文件名也有几种写法。但潜规则中的潜规则是,咱们都用Makefile。若是老师考你 make的潜规则,均可以识别哪几个文件名,他们的依次顺序时,你能够绝不犹豫的向他竖中指。他若是问哪学的,你能够说是野鬼教的,由于他没有说是那个版 本的make。不过竖归竖,为了防止意外,你仍是要知道,GNU make是按照GNUmakefile ,makefile ,Makefile依次进行查找的,同时尽量的确保你的目录下只有Makefile一个文件。
    须要特别说明的是,GNUmakefile人如其名,只有GNU make能够识别,make也有不一样的版本和来源和C的编译器同样,只不过linux上用GNU make的仍是比较多。但仍然建议使用 Makefile这个文件名。
    有了潜规则,你就不须要如此写
1 $make Makefile

  由于当你在当前目录下存在Makefile时,make会自动查找,这就是在“默默的享受被潜规则” linux

    因为你对工程但愿作不少事情,典型的几个事情

    build :程序员

    只编译那些修改过的,或新的文件shell

    rebuild all :函数

    无论有没有修改过,我就是要所有重来,有过工程经验的程序员,我相信大多数都吃过一个苦,由于疏忽,致使build并无实实在在的把最新的代码给编译到 最终文件中去,因而总问“为何仍是不行!!”,因此没有养成正确良好的make习惯的新手,rebuild仍是很必要的,特别是一个工程调用了另外一个工 程生成的库,而当前工程并不会检查另外一个工程生成的库的变更性的时候。工具

    其实我我的的理解,之因此存在rebuild all是由于你的Makefile没有写好,理论上rebuild all是没有存在的意义的,这引伸出不建议使用IDE的话题。我常常被VC6.0搞晕。特别是有些外部库VC6.0没法自动检测依赖性关系,使得我必须 rebuild allui

    clean all:
    至少有两个做用。第一,rebuild all能够拆解成两个动做。把全部obj 和最终文件都删除,再build。第二,你想转移你的有效代码,好比打包COPY到另外一个机器时,一个大目录下总体打包压缩,更有效。可是obj和执行文 件还有不少其余中间文件,都很大,并且对于代码文本的转移没有必要。所以clean all .此时,你的目录下,只有干净的源码和一些说明文件,固然也包括Makefile,此时岂不是清爽的很?
    为了有效区分不一样的操做,make应能够以下处理。
    $make clean //那么咱们就调用clean的操做
    $make build //正常的带依赖性检测的build
    $make rebuild //咱们就调用rebuild的操做。
    由此,咱们引出了几个关于makefile的设计目标。
    一、咱们要能在makefile里面描述出依赖关系。

    二、咱们要能在makefile里面区分不一样的操做。固然一个操做,是由多个执行步骤组成的。
    三、能够make ,make build ,make rebuild,make clean spa

   
    由此咱们如今写第一个Makefile。其实很简单,就是将前面的gcc命令汇总起来,放在Makefile里。包括rm的命令。

以下: 操作系统

1 haha: #须要顶头写,表示一个操做的开始,用此来直接区分不一样操做的描述范围#自己是个注释符号
2     gcc -Iinc -c src/learn_make.c -o obj/learn_make.o
3 #须要TAB一下,否则会当成其余的含义,好比一个操做自己
4     gcc obj/learn_make.o -o bin/learn_make
5 heihei:
6     rm obj/*

这里要作几个解释 命令行

    操做:只是个人口头语,官方的说法叫“规则的目标”,我只是但愿新手理解实际就是一个操做,并且操做里面能够有不少命令 依次组成依次执行,并且make一次只会对一个操做进行操做,除非其余操做和这个操做有依赖关系。但操做一般都是有操做的结果(输出的文件),因此叫规则 的目标
    顶头写:除了描述“规则的目标”外,还有不少其余顶头写的事情,所以,不仅仅要顶头写,你还须要加上个‘:’,这样,make就能够知道,顶头写,同时存在一个‘:’则是一个操做的开始,也就是“规则的目标”。

    haha:我是在想不出什么名词,能说明“规则的目标”有什么特殊的命名方式。用haha ,heihei 是但愿你们理解,规则的目标的名字,并无什么特殊约束,你爱怎么写怎么写,但存在一些潜规则和make的规矩会让你吃苦头,只是我在边上 “haha,heihei,写这个例子时,我确实在haha,heihei"。好比一般,heihei应该用 clean来实现。同时,你执行以下命令 设计

1 $make
2 $make haha
3 $make heihei



 你会发现,make没有后面的参数时,执行了haha。不是由于haha的单词更少。而是由于haha是第一个规则。所以,一般你须要将最经常使用的,固然 未 必是build操做,放在第一个。这样能够简化你的操做。直接make就能够。但这个最经常使用的,与你和你的小组第一直觉但愿make作什么有关系。一般程 序员之间会说“这样这样后,make一下”而不会解释make what。
    你问“make what?"
    同事说:“对就是make what!"
    "make what what 啊?”
    “就是make what 啊,你what 什么 what 啊?”
    这说明两个问题,第一,make的第一个规则,尽量是大家小组的共识经常使用工做,第二,命名很重要。你和你的同伙"what"来“what"去,最多相互 怀疑智商问题。但若是你起名叫“love",而后和你的女同事说,记得tar -xvf后,make love一下。小心她告你性骚扰。

    如今回到haha上。继续make haha

1 $make haha
2 $make haha


 执行两边,没错不是笔误。你会发现,make有啥用?和普通的批处理同样,同样什么都又运行了一遍。这不是make的错。只是我挖了个坑,你不摔 一下,可 能不理解依赖怎么实现的。make要求,对Makefile里的依赖,在规则 ":"后面说明依赖关系。以下Makefile的清单。  

1 haha: src/learn_make.c  inc/learn_make.h
2     gcc -Iinc -c src/learn_make.c -o obj/learn_make.o
3     gcc obj/learn_make.o -o bin/learn_make
4 heihei:
5     rm obj/*

如今继续执行

1 $make haha

哈哈,我又哈哈了。,你会发现,虽然存在依赖关系,并且个人src/learn_make.c  inc/learn_make.h没有改动,怎么仍是全部的都在执行啊。这不是咱们的目标。此时我须要再告诉你,make 对依赖的检查是会判断目标是否存在的,即使依赖的文件没有更新,但目标(操做的输出结果,一般就是文件)不存在,仍然会继续执行。试想,你在 learn_make/下,实际的文件只有Makefile,没有haha这个文件,make天然会努力的继续工做。

    所以,若是你但愿make的一个操做,是针对文件的最终造成,并且但愿make能自动帮你检测文件是否有必要改动,则你须要明确规则的目标就是文件的名 称。不然,他如何判断目标呢?修改为这样,咱们就比较爽了吗?(须要注明,这是弱智版的说法,但愿对新手能从原理上搞清楚make的工做机制)

1 learn_make: src/learn_make.c  inc/learn_make.h
2     gcc -Iinc -c src/learn_make.c -o obj/learn_make.o
3     gcc obj/learn_make.o -o bin/learn_make
4 heihei:
5     rm obj/*


    你能够继续执行发现仍然没有改变。由于你忘了你的Makefile存哪了。你但愿判断的文件是否更新又存在哪里。因此你得改为这样 

1 bin/learn_make: src/learn_make.c  inc/learn_make.h
2     gcc -Iinc -c src/learn_make.c -o obj/learn_make.o
3     gcc obj/learn_make.o -o bin/learn_make
4 heihei:
5    rm obj/*

你能够再试试。我相信,绝大多数熟练使用make的朋友会说我“弱智”。实际的工程级的Makefile也不多这么书写。但我但愿这样的“弱智”Makefile能让新手明确 规则的目标,一般是对文件的操做。 同时,根据上面一个例子,haha: 开头的,你能够知道,make的操做,首先判断依赖的内容src/learn_make.c inc/learn_make.h是否被更新,虽然依赖即使没有被更新,但当找不到目标时,仍然会努力尝试建立目标。这个目标就是haha。惋惜haha 并非你最终的目标,learn_make也不是,由于存在路径问题,因此你必须修改成bin/learn_make

    貌似如今,虽然写法弱智,可是已经能够开始爽make了。你能够很HIGH的告诉不会make对人,我能够针对src/learn_make.c  inc/learn_make.h是否修改,让make动态的决定,是否进行后续操做。但这里有个小BUG。你尝试以下命令 

1 $rm obj/* //将learn_make.o删除
2 $make

  此时,仍然提示,目标没有被更新,而拒绝编译。有人会说,“怕什么,c文件若是改动,能更新就能够了。中间文件,无关紧要。”可是这种依赖关系不明确描述时,会产生一些莫名其妙的错误。

    例如,你存在两个C文件,当只有一个文件出现修改时,则另外一个文件不会进行编译,这是你但愿的目标。不然从新编译浪费时间啊。由于你还年轻,没有经历过编 译一个须要小时这个级别的系统。但当你将obj所有删除时,那些没有更新的C文件没法自动生成obj。此时链接成可执行程序时,就会出错。有人会说,我有 足够的大脑知道只有两个文件,而且不会删除全部.o文件。可是当你的工程足够大时,没有严格规范的Makefile的依赖关系的描述,任何一个依赖关系的 遗漏,都会致使上述rebuild(须要rebuild啊,build不能自动所有更新)的问题发生。

    easy,一个目前所学到的知识告诉你,在:后面依次加上须要依赖的文件或目标就能够了。你可能会以下修改。

1 bin/learn_make: src/learn_make.c  inc/learn_make.h obj/learn_make.o
2     gcc -Iinc -c src/learn_make.c -o obj/learn_make.o
3     gcc obj/learn_make.o -o bin/learn_make
4 heihei:
5     rm obj/*


      你run把,我能够用已经告诉你的知识说明你的错误。
    :后面,确实是依赖,问题是,你的obj/learn_make.o是由src/learn_make.c生成的。你直接说明bin /learn_make须要依赖obj/learn_make.o是否更新,这是没错的。可是若是obj/learn_make.o不存在时,你并无告 诉make怎么作。你可能会说,gcc -Iinc -c src/learn_make.c -o obj/learn_make.o 这不是摆明了写出来了吗?但你须要注意,这里的位置,是基于make认为bin/learn_make须要更新后,才出现的。而在确认是否须要更新对依赖 文件进行检查时,make就已经对obj/learn_make.o在哪的问题开始冒汗了。

    正确的作法以下,虽然个人举例都很“弱智”但这里重点强调的是Makefile中依赖关系。

 

1 bin/learn_make:  obj/learn_make.o
2     gcc obj/learn_make.o -o bin/learn_make
3 obj/learn_make.o: src/learn_make.c  inc/learn_make.h
4     gcc -Iinc -c src/learn_make.c -o obj/learn_make.o
5 heihei:
6     rm obj/*

此时,你再执行,或者执行后,删除obj/*,再执行,此时会发

1 $make

现,如今的逻辑严谨了。并且你会发现一个规律。一般Makefile的内容,得反过来读。 由于首先要处理的事情,一般在后面,而不是在前面,而书写Makefile的逻辑一般是,先说果,再描述因。另外说一句,此时你实际上存在了两个目标可操 做   

1 $make bin/learn_make
2 $make obj/learn_make.o

尝试调换Makefile里两个规则的顺序,再分别执行

 

1 $make

  你会对Makefile的规则有加深的认识。我就再也不废话了。

不过做为build命令咱们仍然存在问题。
    难道咱们make的目标,都要添加相对路径吗,  咱们如何经过build这个目标实现 bin/learn_make这个输出呢?

   同时咱们不但愿 make build会致使由于没有生成build文件,而反复作一样的动做,如上面的错误?

   在设计 build这个操做时,我先谈下clean,来解决第一个问题。为说明问题,咱们继续挖坑。在learn_work/下建立一个名为clean的文件。

1 $:>clean //建立一个空文件,叫clean
2 $ls

咱们修改Makefile的代码以下: 

1 bin/learn_make:   obj/learn_make.o
2    gcc obj/learn_make.o -o bin/learn_make
3 obj/learn_make.o:    src/learn_make.c  inc/learn_make.h
4    gcc -Iinc -c src/learn_make.c -o obj/learn_make.o
5 clean:
6     rm obj/*

执行

1 $make clean



没错,make很HIGH的告诉你,clean是最新的。由于Makefile里的操做都是默认"假设操做的结果会产生个文件。"当当前目录下,文件存在 时,就会有这种“文件没有更改,而拒绝执行的状况”。如今咱们在删除clean以前不妨考虑一下,难道每次你make clean前都须要ls一下,当前目录是否有clean文件吗?make提供了“伪目标”帮助你省掉每次都有ls一下这么繁琐的事情。以下修改 Makefile 
1 bin/learn_make:   obj/learn_make.o
2     gcc obj/learn_make.o -o bin/learn_make
3 obj/learn_make.o:    src/learn_make.c  inc/learn_make.h
4     gcc -Iinc -c src/learn_make.c -o obj/learn_make.o
5 clean:
6 .PHONY:clean
7     rm obj/*

执行 

1 $make clean

呵呵,是否是发现错误了。这个错误是由于.PHONY顶头写了,先不谈他什么意义,.PHONY须要顶头写,不顶头写,会被认为是一个操做的组成部 分。但 顶头写,又直接将clean的操做给截断了。由于咱们说过。make认为一个规则目标的执行,也就是个操做的全部执行部分,是须要【TAB】作前缀,当发 现一个顶头写的行时,则认为命令的描述截止。此时make天然认为 rm obj/* 不是clean的命令组成部分。

    正确作法,你能够在clean申明以前,或总体操做命令以后描述,例如

1 bin/learn_make:   obj/learn_make.o
2     gcc obj/learn_make.o -o bin/learn_make
3 obj/learn_make.o:    src/learn_make.c  inc/learn_make.h
4    gcc -Iinc -c src/learn_make.c -o obj/learn_make.o
5 .PHONY:clean
6 clean:
7     rm obj/*

此时运行,则会出现两种提示。正确运行或错误提示:“obj/下没有文件可删除”。如今能够简单的说一下.PHONY的做用。就是强制说明,后面的 clean是不存在输出文件的。因此clean此时变成了“伪目标”也就是“伪操做”,“伪”只是说明他不会生成一个文件,所以,不会去考虑当前路径下, 是否要判断同名文件存在与否或是是否存在依赖,而始终是埋头苦干。此时你能够删除clean这个文件了。当你确认.PHONY做用以后。

1 $rm clean

可能有小朋友就琢磨了,若是是个伪目标,能够回避检查文件的方式,那么我把bin/learn_make改为build,这样make build实际就能够处理bin/learn_make了。

    以下

1 .PHONY:build
2 build:   obj/learn_make.o
3     gcc obj/learn_make.o -o bin/learn_make
4 obj/learn_make.o:    src/learn_make.c  inc/learn_make.h
5     gcc -Iinc -c src/learn_make.c -o obj/learn_make.o
6 .PHONY:clean
7 clean:
8     rm obj/*
1 $make build
    一切正常,若是以前你make clean了。再执行
    $make build

    一切不正常,由于,又跑了一次。(我很是建议新学者,将全部错误的方式都操做一边)。由于这样作,虽然回避了build须要检测当前目录下build的问 题,但仍然解决不了bin/learn_make是否存在,是否须要依赖性检测的问题。因此正确作法应该以下

1 .PHONY:build
2 build:  bin/learn_make
3 bin/learn_make: obj/learn_make.o
4     gcc obj/learn_make.o -o bin/learn_make
5 obj/learn_make.o:    src/learn_make.c  inc/learn_make.h
6    gcc -Iinc -c src/learn_make.c -o obj/learn_make.o
7 .PHONY:clean
8 clean:
9     rm obj/*



    你执行两次make build 

1 $make build
2 $make build

没问题了吧。这是由于,咱们先用了.PHONY,让build成为一个不须要检测文件伪目标。但同时,咱们将 build的实际输出文件做为一个依赖,让build须要每次都进行检查是否须要更新。同时不要被前面的伪目标clean误导,认为伪目标必定埋头苦干, 那是基于clean没有依赖,而build存在依赖,当依赖发现不须要更新,build也就不会埋头苦干了。

    增补完整的包括rebuild的内容以下 

01 .PHONY:build
02 build:  bin/learn_make
03 bin/learn_make: obj/learn_make.o
04     gcc obj/learn_make.o -o bin/learn_make
05 obj/learn_make.o:    src/learn_make.c  inc/learn_make.h
06     gcc -Iinc -c src/learn_make.c -o obj/learn_make.o
07 rebuild: build clean
08 .PHONY:clean
09 clean:
10     rm obj/*

你们执行一下

1 $make rebuild
    哈,会出现两种可能的错。一种是只有rm obj/*被执行,另外一种,先gcc了东西,随后rm obj/*。不是我故意喜欢挖坑让大家跳,由于个人职业经历发现,寻找边界错误临界点更容易理解系统运行原理,这些错误都是我在琢磨 make的工做原理中本身挖本身跳的坑。上面这个错误,可让你明确两个道理。
    1,make对依赖的检测,是从左到右,有顺序的。你与其记住这句话,不如记住上面这个失败的例子。
    二、make呼呼的向下run不表明正确。你的Makefile的逻辑关联很重要。
    再次,分析一下两种错的缘由。
    只执行了rm obj/*,这是由于bin/learn_make通过检测,没有须要更新的。因此make直接无视,飘了过去。
    当因为src/ inc/ 下面的源码被改动了。或者obj/learn_make.o 或则bin/learn_make的 o被删除了。make认为须要从新更新bin/learn_make,所以执行了gcc部分。
    固然不管哪一种错,最终都会执行clean ,你的 obj/下 o文件没有了。

    所以,正确的作法以下:

01 .PHONY:build
02 build:  bin/learn_make
03 bin/learn_make: obj/learn_make.o
04     gcc obj/learn_make.o -o bin/learn_make
05 obj/learn_make.o:    src/learn_make.c  inc/learn_make.h
06    gcc -Iinc -c src/learn_make.c -o obj/learn_make.o
07 rebuild: clean build
08 .PHONY:clean
09 clean:
10     rm obj/*

     此处但愿你们理解一个概念。看上去rebuild是在调用了clean 和build的函数。没错你这个理解是能够。但你须要带着更新检测的思惟,这是make强大之处。真是由于存在更新检测,你就要注意观测clean ,和build是否为“伪目标”,这对理解别人的Makefile和本身书写Makefile都是有好处的。是否存在伪目标的定义,对于这个依赖关系的推 导会有不一样的结果。

    回到最近的这个错误,clean build的顺序写反的错误。甚至可能有些新手发现并无什么结果性的错误。由于我只要make rebuild两边,始终能获得最新的bin/learn_make。由于当目前的全部依赖文件都没有更新时,第一边不会GCC,但会rm,由此致使第二 次由于obj/*.o都不存在,全部再次所有重编。
    这里隐含了一个事实,你clean存在一个漏洞,只删除了o。没有删除bin下的learn_make。致使莫名其妙的新手会认为,make 弱智,须要两边make rebuild才能真正实现最新版本的执行文件。或者说,这个Makefile(若是它很大,你没有信心去理解它)有问题,你须要执行两边才能确保严格正 确(其实更本就是不正确),甚至有人会去开骂make或GCC甚至是linux的版本问题,固然也可能会考虑键盘是否老化了等各类奇怪的责任者。实际上是 Makefile自身的错误+错误,致使两次也获得了最终结果。这样的错误类型,在不少粗制滥造的应用软件里很常见,而用户只能自发的想对策。所以你须要 修改Makefile 以下 
01 .PHONY:build
02 build:  bin/learn_make
03 bin/learn_make: obj/learn_make.o
04     gcc obj/learn_make.o -o bin/learn_make
05 obj/learn_make.o:    src/learn_make.c  inc/learn_make.h
06     gcc -Iinc -c src/learn_make.c -o obj/learn_make.o
07 rebuild: clean build
08 .PHONY:clean
09 clean:
10     rm obj/*
11     rm bin/*

此时你开始执行以下命令

1 $make  //build一下,确保obj/learn_make.o bin/learn_make都存在。
2 $make //哦。make 不执行了。好爽
3 $make clean //清除全部的生成文件
4 $make //哦,又爽了,由于又开始工做了。
5 $make rebuild //哦,爽完再爽,rebuild也行了。

没错。一切都让你爽了。可是你尝试以下命令 

1 $rm obj/* //没错,只删掉obj/*下的东西。
2 $make rebuild //咱们先clean,再build

必定错了。make异常退出了。当你准备骂make不智能时,其实make已经准备好了方案。就是以下修改 

01 .PHONY:build
02 build:  bin/learn_make
03 bin/learn_make: obj/learn_make.o
04     gcc obj/learn_make.o -o bin/learn_make
05 obj/learn_make.o:    src/learn_make.c  inc/learn_make.h
06     gcc -Iinc -c src/learn_make.c -o obj/learn_make.o
07 rebuild: clean build
08 .PHONY:clean
09 clean:
10     -rm obj/*
11     -rm bin/*

此时再试一下

1 $make rebuild

虽然仍然提示rm obj/*存在错误。可是会继续作完其余的事情。够人性化吧。由此,咱们的一个完整简单的弱智版本Makefile处理完毕了。

    不过之因此最后才说 ‘-’这个由于工具没有问题,但操做不当就会有问题。必定要预防一些存在依赖关系的操做被你加上‘-’,这时你不少事情都会白作。一般只有你肯定,当该操 做即使出错,对你的总体make工做没有任何影响时,你才能使用 ‘-’。make的价值,不在于普通脚本的选择性批处理命令,而在于每一个动做的相互依赖关系的检测(一般是经过文件的依赖关系),你强迫忽视某个动做是否 错误,实际上在等同修改你的依赖关系(若是这个动做确实存在依赖关系的状况下)。

相关文章
相关标签/搜索