【Linux】自动化构建工具-make/Makefile

个人主页zxctscl
如有转载请先通知

文章目录

  • [1. 前言](#1. 前言)
  • [2. 认识make/Makefile](#2. 认识make/Makefile)
  • [3. 了解make/Makefile原理](#3. 了解make/Makefile原理)
    • [3.1 依赖关系和依赖方法](#3.1 依赖关系和依赖方法)
    • [3.2 make检测的顺序](#3.2 make检测的顺序)
    • [3.3 PHONY:XXX](#3.3 PHONY:XXX)
  • [4. makefile内置符号](#4. makefile内置符号)

1. 前言

在上一篇中已经了解了【Linux】编译器-gcc/g++使用,这次来一起看看make/Makefile。

2. 认识make/Makefile

make是一个命令

makefile是一个文件。

先touch一个makefile/Makefile(m大小写都可以)文件:

然后进入makefile写一段代码:

未来形成的可执行程序是mytest,依赖的是test.c。就是将test.c编译形成mytest的可执行程序。

直接make编译一下:

因为版本比较低,这里提示要加上-std=c99。重新打开makefile加上就行:

此时在重新make一下就有了:

会自动形成我们要的可执行程序mytest

如果想要清理编译产生的临时文件怎么办?

再次打开makefile:

退出后直接用命令清理一下

bash 复制代码
make clean

发现mytest已经没有了:

  1. makefile文件中,保存了编译器和链接器的参数选项,并且描述了所有源文件之间的关系。

    1. make程序会读取makefile文件中的数据,然后根据规则调用编译器,汇编器,链接器产生最后的输出。
  2. Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。

    显式规则说明了,如何生成一个或多个目标文件。

  3. make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写makefile,比如源文件与目标文件之间的时间关系判断之类

  4. 在makefile中可以定义变量,当makefile被执行时,其中的变量都会被扩展到相应的引用位置上,通常使用 $(var) 表示引用变量文件指示。包含在一个makefile中引用另一个makefile,类似C语言中的include;

  5. 注释,makefile中可以使用 # 在行首表示行注释

  6. 默认的情况下,make命令会在当前目录下按顺序找寻文件名为"GNUmakefile"、"makefile"、"Makefile"的文件,

3. 了解make/Makefile原理

打开Makefile,来看看前面两行:

写好Makefile后,当我们实际是在运行make时候:对应的程序就会在当前程序找makefile,然后读取makefile里面的内容,根据依赖关系,知道test.c形成mytest这样的可执行程序,根据gcc写的依赖方法来执行。

make会根据makefile的内容,完成编译或者清理工作。

3.1 依赖关系和依赖方法

怎么理解依赖关系和依赖方法呢?

举个例子:在现实生活中求人办事,就先得和他们产生某种关系,然后才会有后面的方法。就像小明没有生活费了,向他父亲打电话,就说一句:我是你儿子。这个就表明了依赖关系。表明了依赖关系然后,小明想要做什么?所以小明重新打电话说:我是你儿子,没钱了,打点钱。就有了依赖方法。

计算机要形成某种可执行程序,就得先表明依赖关系,然后怎么具体形成可执行程序,这就要有具体依赖方法。

依赖关系和依赖方法是完成一件事情的必然要素,并非makefile特有的。

这里clean:是一种特殊的依赖关系,这个clean不依赖任何一个文件,是一种特殊情况。

所以说依赖文件列表可以为空。

3.2 make检测的顺序

在形成可执行程序的时候直接make就可以直接形成mytest,而不是执行clean?

要执行clean,就必须这样写make clean

也可以用make mytest这样去运行:

makefile它的运行推导规则是:默认从上到下,对makefile文件进行扫描,默认形成第一个目标文件。也就是只有make的时候,默认形成第一个目标文件。

如果把clean放在前面,那么默认先执行的就是clean:

发现直接make的时候先执行的clean:

默认将执行的可执行程序放在前面,这样make的时候就能直接形成了。

所以make检测的顺序是从上往下的

3.3 PHONY:XXX

当我们在程序里面不加上:

来make一下后,在make一下,发现就不行了:

在程序不被修改时,make后,默认就不会在形成新的可执行程序,它认为没有必要。

如果想要makefile里面的操作总是被执行,不要因为是最新的就拦截,就加上:.PHONY:mytest

make多少次都会执行,没有拦截。

bash 复制代码
.PHONY:XXX

XXX对应的方法,总是要被执行。

一般可执行程序不需要.PHONY:修饰,但是我们总是希望clean重新清理。

默认将clean用.PHONY:修饰:

要然清理工作总是被执行。

为什么makefile对最新的可执行程序,默认不重新形成呢?

如果在一个项目里面存在上千个源文件,每次改代码时候,可能就修改一小部分。做了改动之后,如果每一次都把所以的源文件重新编译一遍,就会带来效率的延缓。还有可能出现在没有修改情况下,一编译就重新执行,上千个文件,在编译的时候又得重新编译,又得花费很长时间。既然形成的可执行程序都是新的,那么就不需要再重新编译。

就是为了提高编译效率

那么是怎么做到不重新执行的呢?换句话说makefile怎么知道我的程序需要被编译呢?

这里有一个Modify:文件的最新修改时间:

源代码和可执行程序最近一次形成或者修改的时间一定是不一样的。

每一次都是先写源代码再形成可执行程序。

最终要判断程序是否被编译:只要对比,可执行文件最近修改的时间和源文件最近修改的时间,谁更新。

可执行程序最新就不需要再编了,源文件最新就重新编译一下。

这里源文件没有更新,就不能再编译:

来修改一下test.c的Modify时间:

bash 复制代码
touch test.c

make一下就又可以编了:

4. makefile内置符号

$:相当于取内容。

^:代表整个依赖文件列表,就是这里的code.c

@:代表目标文件,就是这里的code.exe

在编译的时候makefile会自动进行符号替换,把@替换为目标文件,^替换为code.c:

make之后,执行一下code.exe:

在把它清理掉:

把makefile如果要把它写全,就是这样的:

实际上编译器可以直接一步到位。

上面有4组依赖关系,就有依赖方法:

bash 复制代码
  1 code.exe:code.o
  2   gcc code.o -o code.exe -std=c99
  3 code.o:code.s
  4   gcc -c code.s -o code.o -std=c99
  5 code.s:code.i
  6   gcc -S code.i -o code.s -std=c99
  7 code.i:code.c
  8   gcc -E code.c -o code.i -std=c99

在make后,在makefile里面写的代码重新编译了,编译后,依此形成了临时文件,并且形成了最终的可执行程序:

运行一下:

重新进入makefile:

bash 复制代码
  1 code.exe:code.o
  2   gcc code.o -o code.exe -std=c99
  3 code.o:code.s
  4   gcc -c code.s -o code.o -std=c99
  5 code.s:code.i
  6   gcc -S code.i -o code.s -std=c99
  7 code.i:code.c
  8   gcc -E code.c -o code.i -std=c99
  9 .PHONY:clean
 10 clean:
 11   rm -f code.i code.s code.o code.exe

此时code.i code.o code.s code.exe都被清理了:

发现执行的顺序和makefile里面代码顺序是相反的。

make拿到makefile文件时候,从上往下扫描,先看到的文件是code.exe,先识别的依赖关系是code.exe:code.o,但是发现code.o并不存在,所以 gcc code.o -o code.exe -std=c99是不执行的。

然后继续找下一组依赖关系:code.o:code.s,发现code.s并不存在,所以不执行这个依赖方法。

一直这样找,直到code.i:code.c,这个code.c是存在的,那么就会执行这一组依赖关系的依赖方法: gcc -E code.c -o code.i -std=c99。那么就形成了code.icode.i形成了,再回到code.s:code.i执行他的依赖方法gcc -S code.i -o code.s -std=c99。一直像这样,直到形成code.exe

makefile/make 会自动根据文件中的依赖关系,进行自动推导,帮助我们执行所有相关的依赖方法。

这些依赖关系就像栈一样,出栈执行方法:

如果注释调最后一组依赖关系:

就会出现没有规则去制作code.icode.i不存在,那么code.s也就不存在。

如果乱序排这些依赖关系:

能正常执行:

那么如果让第一个依赖关系放在后面呢:

此时make就不能执行:

因为makefile必须把最重要的文件放在前面,总的有个开头。一定要保证最终实现的目标文件是第一个。

那么之后我们写makefile就要这样写吗?

其实没有必要这样写。直接这样写就行:

bash 复制代码
  1 bin=code.exe
  2 src=code.c
  3
  4 $(bin):$(src)
  5   gcc -o $@ $^ -std=c99
  6 .PHONY:clean
  7 clean:
  8   rm -f $(bin)

这样也是可以执行的:

这里定义的变量就相当于宏。要改形成的可执行程序,就只改前面这两行的文件名就行。

举个例子:

重新make一下:


如果不想在执行命令的时候出现提示出像下面这样的命令的内容:

不想显示出这些命令,那么就在这些命令前面加上@:

此时发现已经不显示了:

一般我们所使用不仅仅只有一个依赖方法,可以继续往后跟:


有问题请指出,大家一起进步!!!

相关推荐
Tassel_YUE39 分钟前
网络自动化04:python实现ACL匹配信息(主机与主机信息)
网络·python·自动化
€☞扫地僧☜€1 小时前
docker 拉取MySQL8.0镜像以及安装
运维·数据库·docker·容器
hjjdebug1 小时前
linux 下 signal() 函数的用法,信号类型在哪里定义的?
linux·signal
其乐无涯1 小时前
服务器技术(一)--Linux基础入门
linux·运维·服务器
Diamond技术流1 小时前
从0开始学习Linux——网络配置
linux·运维·网络·学习·安全·centos
写bug的小屁孩1 小时前
前后端交互接口(三)
运维·服务器·数据库·windows·用户界面·qt6.3
斑布斑布1 小时前
【linux学习2】linux基本命令行操作总结
linux·运维·服务器·学习
紅色彼岸花1 小时前
第六章:DNS域名解析服务器
运维·服务器
Spring_java_gg1 小时前
如何抵御 Linux 服务器黑客威胁和攻击
linux·服务器·网络·安全·web安全
✿ ༺ ོIT技术༻1 小时前
Linux:认识文件系统
linux·运维·服务器