【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一下:


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

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

此时发现已经不显示了:

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


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

相关推荐
A小辣椒14 小时前
TShark:Wireshark CLI 功能
linux
A小辣椒18 小时前
TShark:基础知识
linux
AlfredZhao20 小时前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao1 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
大树882 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质2 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式