Linux项目自动化构建工具-make/Makefile
1. 背景介绍
- 会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力
- 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
- makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率
- make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法
- make是一个命令,Makefile是一个当前目录下的一个文件,两个搭配使用,完成项目自动化构建
2. 先见一见Makefile是如何工作的?
- 在当前目录下,创建一个Makefile文件(Makefile的首字母大小写均可)
- 编写Makefile文件,如下:
mycode:mycode.c # 依赖关系
gcc mycode.c -o mycode # 依赖方法
clean: # 依赖关系
rm -f mycode # 依赖方法
- 有了Makefile,编译项目我们不用再gcc命令了,可以直接make
- make命令会自动帮我们在当期目录下找Makefile,然后根据Makefile内部的依赖关系和依赖方法,执行对应的依赖方法,帮我们生成对应的可执行程序
- make之后我们再进行make它就不允许我们make了,这个时候我们需要rm清理可执行程序,但是rm风险太大,我们容易误删文件
- 所以我们的Makefile它不仅支持我们编译项目,也支持我们清理项目
- 示例:
- 通过示例,我们现在只需要知道make是一个命令,Makefile是一个当前目录下的文件
- 我们现在有如下三个问题,解决了这三个问题我们就可以大致了解make/Makefile了
- 什么是依赖关系、依赖方法?
- 为什么make之后我们就不能再make了?
- 为什么make直接就可以生成可执行程序,而清理需要make clean?
3. 依赖关系和依赖方法
- 帮助理解:比如,生活中,到了月底我们需要问父母要生活费了,具体是怎么要的呢——①先要有正确依赖关系,你要生活费肯定是向自己父母,你向一个陌生人要他是不会给你的;②再有正确的依赖方法:有了依赖关系还不可以,还需要正确的方法,即你需要打电话给你父母,说到月底呢,需要1000的生活费,这样你父母才会打钱了。如果你说现在游戏出了一个新皮肤,需要1000,你的父母一般不会打钱给你的。
- 依赖关系和依赖方法两者缺一不可
- 在现实生活中我们做任何事情,一定是先有一个正确的关系,然后再有正确的方法才能完成
- Makefile书写的一般格式:例如我们要把mycode.c编译形成一个可执行程序mycode
- 先写正确的依赖关系:mycode,它依赖mycode.c——》mycode:mycode.c
- 再写正确的依赖方法:【tab】gcc mycode.c -o mycode
- 注意:依赖方法的书写格式一定要tab开头,不能是四个空格
4. make的工作原理
为了方便讲解make的工作原理,我们这里将Makefile写复杂一些:
mycode:mycode.o
gcc mycode.o -o mycode
mycode.o:mycode.s
gcc -c mycode.s -o mycode.o
mycode.s:mycode.i
gcc -S mycode.i -o mycode.s
mycode.i:mycode.c
gcc -E mycode.c -o mycode.i
运行实例:
- make之后确实生成了mycode的可执行程序,也同时生成了mycode.i、mycode.s、mycode.o的临时文件这我们能理解,但是为什么make执行的过程和我们在Makefile中我们编写的顺序是相反的呢?这就需要我们理解make的原理了。
- make是如何工作的,在默认的方式下,即我们只输入make命令。那么,
- make会在当前目录下找名字叫“Makefile”或“makefile”的文件
- 如果找到,它会找文件中的第一个目标文件,并把这个文件作为最终的目标文件。在上面的例子中,他会找mycode这个文件作为最终的目标文件
- 如果mycode文件不存在,那么,它就会根据它的依赖关系和依赖方法来生成mycode这个文件
- mycode依赖mycode.o,如果当前目录下mycode.o不存在,那么make会先去生成mycode.o,只有在当前目录形成了mycode.o,然后才能再回来,根据mycode.o形成mycode
- mycode.o有自己对应的依赖关系和依赖方法,可是mycode.o又依赖mycode.s,mycode.s在当前目录也不存在,所以make又需要在Makefile中扫描,形成对应的mycode.s
- mycode.s也有自己对应的依赖关系和依赖方法,形成mycode.s,mycode.s依赖于mycode.i,mycode.i在当前目录也没有,所以继续扫描,mycode.i依赖mycode.c,mycode.c在当前目录存在,他就可以根据mycode.c的关系形成mycode.i了,形成mycode.i之后,没有结束,我最终的目标是要形成mycode
- 要mycode就要有mycode.o,要mycode.o就要有mycode.s,要mycode.s就要有mycode.i,要mycode.i就要有mycode.c,有mycode.c还有依赖方法,所以先形成mycode.i,然后再逆向执行直到形成最终的目标文件mycode
- 在整个的执行过程中,make在扫描Makefile文件时,它会优先根据依赖关系,找依赖关系中他所依赖的的文件是否在当前目录存在,如果不存在,我们的make会类似于递归式的去进行先去形成你的依赖文件,回过头来再根据依赖文件形成目标文件,这个过程就是递归的过程,mycode.c就是递归的出口,而整个的结构,在保存这些依赖关系的时候就是栈的结构
- 这种基于递归,基于栈的这样结构的依赖关系推导,我们称为Makefile依赖关系的自动化推导(.i,.o,.s的编写顺序可以乱序,只要能找到就行,但是不建议这样做)
- 在我们make自动推导Makefile中依赖关系时,make会一层一层的去找文件的依赖关系,直到最终编译出第一个目标文件
- 注意:找找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或编译不成功,make根本不理——即make只管文件的依赖性
- 归纳:上面我们所讲的一切就是一句话make会自动推导Makefile中的依赖关系,推导过程是栈式结构
5. make的项目清理
- 我们现在已经知道怎么通过Makefile形成可执行程序了,但还有一个问题怎么清理它和一些临时文件?
- Makefile编写:
mycode:mycode.c
gcc mycode.c -o mycode
# 清理项目
.PHONY:clean
clean:
rm -rf mycode
- 示例:
- 工程是需要被清理的
- 清理项目的依赖关系为空,不需要依赖其他任何文件,是独立存在的,所以我们直接clean:即可
- 为什么make直接就可以生成可执行程序,而清理需要make clean?
因为make它是自顶向下去扫描Makefile,他把你所需要形成的第一个目标文件充当为make的默认动作,即谁是第一个目标文件,直接make就是谁- 所以建议把你想要形成的可执行程序放到Makefile的最前面,把清理动作放到最后面
- 如果我们想要指定名称的执行某个依赖关系与它所匹配的依赖方法,make后直接跟目标文件即可
- 如我们上面的例子,clear没有被第一个目标文件直接或间接关联,那么他后面所定义的命令就不会自动执行,不过,我们可以显示要make执行。即命令——“make clean”,以此来清理项目,以便重新编译
- 我们发现make第一次编译完之后,如果源代码没有修改,之后再make他就不允许再编译了。——因为源文件没有修改,再编译也是形成一样的可执行程序,所以为了提高编译效率,没有必要再编译
- 那make是怎么做到这一点的?
- 一定是源文件形成可执行,先有源文件,才有可执行
- 一般而言,源文件的最近修改时间比可执行文件要老
- 所以如果我们更改了源文件,历史上有可执行,那么源文件的最近修改时间,一定比可执行程序要新
- make只需要比较,可执行程序的最近修改时间和源文件的最近修改时间
- 可执行新于源文件,不需要重新编译
- 可执行老于源文件,需要重新编译
- 那make又是如何判断源文件和可执行文件的新旧?
- Linux中stat指令可以直接查看源文件和可执行文件对应的时间问题
- 一般对一个文件来说有三种时间:Access、Modify、Change
- Access:文件最近被访问的时间(原理上增删改查都算访问文件,所以几乎任何操作都会修改它,它更改的频率很高。但在实际中他更改的频率并没有我们想的这么高。)
- 文件 =文件内容 + 文件属性,我们把对文件内容做修改的这种动作叫做Modify,把对文件属性做修改的这种动作叫做Change
- Modify:文件内容最近被修改的时间
- Change:文件属性最近被修改的时间
- 我们把这三个时间一秒记住,简称ACM时间
- 这三个时间不一定是割裂的,有时候我们多个时间可能会被同时修改
- 一般我们文件内容改了,文件属性一般也会跟着改(Linux自动帮我们改)——因为文件内容改了,文件属性中的文件大小size自然也改。但是文件属性改了,文件内容不一定改
- 通过实例帮助我们理解这三个时间:
- 文件属性改了,这三个时间中只有Change变了,Access和Modify没变
- 文件内容改了,这三个时间中一定Modify和Change都变了,但是Access可能不变
- 修改文件内容,打开文件访问了,为什么Access可能不变?
Linux系统早期时确实只要访问文件Access就会变,但是后来Linux系统的设计者发现Access更改的频率太高了,而文件存在硬盘中,所以一旦更新Access就意味着要在硬盘中修改这个文件的属性,而硬盘是计算机中的一个外部设备(比较慢)。当我们整个系统被多个用户使用时,一定会有大量的文件的Access做更新,这些所有更新都要写到硬盘上,都要访问外设,最后就会变非常慢,所以它不利于Linux系统整机效率的提高,所以在现在比较新的Linux版本中,Access的更新策略被改了,不再是直接你访问就改,而是根据你Modify和Change的更新次数还有他最近文件被访问次数(计数器)是否满足要求才会改(比如,可能访问3,4次才会改一次Access),这样减少了Access改变的次数,从而提高整机效率!- 如果我们不想这样,想让文件的更新时间变成最新的,该怎么做?——我们可以通过touch命令手动更新文件的最新时间
- touch后如果跟的是已经存在的文件,他会把文件的所有时间更新为最新
- 了解了时间我们就可以验证make会根据源文件和目标文件的新旧(通过将Modify时间转换为时间戳(现行增长)作对比,看谁新谁旧),判断是否需要重新执行依赖关系进行编译!
验证:
- 源文件比可执行旧,就不再编译了
- 源文件比可执行新,再次编译
- 从以上结论我们又可知,依赖关系不一定总是执行的! 他是有前提条件的。
- 但是如果我们想让对应的依赖关系,总是被执行该怎么办?
- 我们可以将目标文件设置为伪目标,我们把用.PHONY后面修饰的目标文件称为伪目标。
- 伪目标的特性是,依赖关系和依赖方法总是被执行!
- 不建议把形成可执行文件的目标文件设置为伪目标,用它修饰了确实每次编译都是最新的,但是有时候我就是想逼你去了清理项目,才能再编译形成可执行,因为在不清理旧的可执行文件在旧的可执行文件中新增,可能会导致老的问题依旧存在!
- 但建议将清理动作设置为伪目标,因为清理的动作我们想让它总是被执行
6. Makefile的特殊符号
标签:文件,依赖,make,Makefile,编译,Linux,mycode From: https://blog.csdn.net/wangjiushun/article/details/141758390
- $@——代表依赖关系所对应的目标文件,即冒号左侧的所有内容
- $^——代表依赖关系冒号右侧的所有内容
- @——我们发现make命令的时候,他会把它所匹配的依赖方法回显出来,如果不想回显,可以在依赖方法前带@符号