一文入门Makefile

发布于:2024-06-26 ⋅ 阅读:(199) ⋅ 点赞:(0)

今天我们来玩玩Makefile。

这边是借鉴的陈皓老师的《跟我一起写 Makefile》

pdf下载链接如下。

链接:https://pan.baidu.com/s/1woRq2nEkgzLv1o5uE0FZHg?pwd=mhrh 
提取码:mhrh

我们之前已经算是入门了gcc,那我们的下一站就是Makefile,上一篇我们也发现了,当我们的main.c包含了自己写的头文件,那么编译命令会变得很长,如果包含的头文件很多,那么光是敲编译命令都够花我们半条命了。

所以也有下面这个说法。

会不会写 makefile ,从一个侧面说明了一个人是否具备完成大型工程的能力。

那么Makefile到底是做什么的呢?简单来说,Makefile通过定义一系列编译规则,指导make工具如何根据源文件的修改情况来执行增量编译,从而极大地提高了开发效率,Makefile写出来也就是一个脚本文件。

使用Makefile不仅使得编译过程自动化,还带来了诸多好处:

  • 自动化处理复杂的依赖关系:Makefile能够自动处理和解析项目中各文件之间的复杂依赖关系,确保只有必要的部分被重新编译。
  • 支持跨平台构建:通过编写与平台无关的Makefile,可以在不同操作系统间实现一致的构建过程。
  • 提高开发效率:开发者只需关注源代码的编写,编译过程的大部分操作都由Makefile自动完成,减少了手动操作的错误和时间消耗。

那么废话不多说,我们直接开始先写一个Makefile吧。

先别管这里的内容是什么意思,这内容我们写到Makefile里,Makefile就是这个脚本文件的名字,没有后缀,当然了也可以叫makefile和GNUmakefile(最好不要叫这个,因为只有GNU make才能识别)。

我们写好之后要执行Makefile,就直接执行一个make命令。

然后就可以看到执行了一堆命令,这些命令是我们写在Makefile里面的。

并且生成了下面一堆中间文件。

那其实大部分情况我们是不需要中间文件的,那么我们是不是也可以把删除中间文件的命令写进Makefile里呢?那当然是可以了,因为Makefile本质上就是脚本文件,自然是可以写的。

然后我们执行

make clean

可以看到执行了我们写在Makefile里面的最后的那个语句。

接下来我们开始揭秘Makefile。

我们先看第一行test:test.o,这实际上是一个规则。这段的含义是我们需要生成test(也就是目标文件),为了生成test首先需要拥有test.o(也就是依赖文件),通过执行下面首行缩进一个tab的那个命令来生成。

如果当前目录下没有依赖文件,那么就会在Makefile里找有没有目标文件是依赖文件的规则。

那么前四个规则大家应该就能看懂了。

但是第五个clean它不是简单的规则,它是一个特殊的目标,一般用来删除编译过程中产生的中间文件,并且我们看到它是没有依赖的。

执行的时候就是输入命令 make clean。

因为clean涉及到删除,而在Linux中删除就是真删除了,不像Window里还能从回收站里找到,因此编写clean的时候我们需要格外小心,但是保不齐我们会失误,那怎么办呢?

我们可以在make clean后面加个-n选项来模拟删除,然后再次检查是否出错,此时还不是真的删除,因此还有改正的机会。

如上可以看得出来当我们执行make clean命令加上-n选项之后,它会展示出执行的删除命令,但是并没有真正执行,这时我们就可以对make clean 进行检查了。

除了直接在Makefile里写clean,更稳健的写法是下面这样的。

 其中.PHONY表示后面的clean是一个伪目标,并不是正常的目标。并且在 rm 前面加上了一个 - ,这表示当我们删除某些不存在的文件的时候忽略报错,接着往后执行。

当然还有个潜规则就是clean放在Makefile的最后面。

接着再回到我们的第一版最简单的Makefile,它是由多个规则构成的,每个规则里有目标文件,依赖文件和执行命令。在Makefile里,它默认会把第一个规则中的目标文件当成是最终目标,也就是说它不一点会执行每个规则,而是只要能够生成最终目标就行。

要指定最终目标也是可以的,在Makefile开头加上ALL:最终目标即可。像下面这样,实际上不一定非得叫ALL,原理就是Makefile会以第一个规则为最终目标,那么我放在第一行的那就是最终目标。

上面就是Makefile最基础最基础的内容了。

那接下来我们就可以更进一步了。

既然Makefile是脚本文件,那么它有变量也是正常的对叭。

一般写在文件开头,格式就是  变量名 = 变量值 ,当我们使用时是这样的 $( 变量名 ),这边需要提的是Makefile中的变量比较接近于宏定义,也就是直接替换的,没有类型。

光讲可能比较抽象,我们直接看下面的例子。

上面例子把目标文件直接赋值给了变量targetFile,我们在规则中可以使用$(targetFile)的方式取出test的这个值去使用,当然了上面只是简单地做个示范,我们还可以拿变量去装一些更复杂的东西,比如说一堆的文件(没错,一个变量能装的可不止一个文件,用空格隔开可以装多个)

那我们要给变量赋值赋上一堆文件,除了一个个手敲上去,还有别的更省力的方式那就是通配符。

比如说我们需要给一个变量赋值当前目录下所有的 .c 文件,那么我们用通配符就可以非常方便的赋值。

大家想的是下面这样的对叭。

cfile = *.c

但是实际上这样是不行的,这样在Makefile中,cfile这个变量的值就是 *.c ,而不是我们想要的所有 .c 文件,这时候就需要用上函数了,既然Makefile是脚本文件,那么有个函数也是很正常的对叭。

正确的写法是下面这样的。

cfile = $(wildcard *.c)

这个函数就是 $(wildcard  ),在括号里,wildcard后写的通配符就会被展开,就会是我们想要的效果了。

除了上面我们自己去写变量,Makefile里有一些自动化变量我们可以直接去使用的,我们挑几个常用的来说。

$@ : 本条规则中的目标

$< : 本条规则中第一个依赖,如果第一个依赖代表多个文件(后面会说怎么实现),那么$<就表示为将这多个文件一个个取出来。

$^ :本条规则中的所有依赖,用空格分隔。

$* : 可以简单理解成目标文件的名字但是不含后缀名。

$? :比目标更新的依赖,也就是最后修改时间比目标更晚的依赖。

用了上面的自动化变量,那么我们第一版的Makefile可以写成下面这样了。

现在我们改一改我们的test.c,像下面这样。包含了自己写的头文件,用了里面的方法。

对应的在Makefile里也需要修改。我们添加了对于mytool1的编译。

是可以正常make的。

然后我们发现,后面两个规则中的命令是一样的,那么实际上我们是可以对他们进行一个合并操作的,也就是说在一个规则里,可以有多个目标。可以像下面这样改写,但是由于gcc -o选项只能输出一个文件,因此在命令里不能使用 $@,但是还好-c选项可以不指定输出文件,默认就是输入文件的后缀改成 .o当输出文件了。

也是可以正常make的。

这个例子主要是想说在Makefile中,在一个规则里可以有多个目标。然后同一个目标也可以有多个规则(这边不演示了)。

然后实际上我们还有种更简单的写法,那就是模式规则。

这边直接贴出怎么写的。

看不懂什么意思没关系,我们make一下,make之后会显示出它执行的命令,然后我们再分析分析。

看的出来Makefile直接帮我们把我们需要的依赖都执行了。

这是因为上面的%可以看成是任意的东西(类似于通配符中的*),当我们的目标的依赖不存在的时候,Makefile就会在规则中寻找是否目标的依赖和哪个规则的目标一致,找到就会执行这个规则中的命令。就比如说我们上面的Makefile,需要寻找目标为test.o以及目标为mytool1.o的依赖,最终都找到了 %.o:%.c 这个规则,因为%代表任意东西,那么自然是可以匹配上的,这就是模式规则强大的地方,我们可以少写很多东西了。

不过上面这种写法有一个可以改进的点,那就是将$^改成$<,我们来看看二者的区别。

我们上面的例子简单,所以没什么差别,但是当我们的依赖变多之后,依赖嵌套依赖之后,我们就得用$<了。

使用模式规则也有个弊端,那就是模式规则会匹配所有符合条件的,因为%可以匹配任意东西,因此只要后缀符合,都会被我们的模式规则匹配到,如果我只有一部分的文件需要用模式规则中的命令怎么办呢?

这就要用到我们的静态模式规则了。

在模式规则的基础上,在前面加上需要匹配到的目标即可。

可能有小伙伴会说,这样不就又绕回去了嘛,还是要把目标的名字都写出来。那其实我们可以再静态模式规则的后面再写个模式规则,而只在静态模式规则中写上需要特殊处理的目标即可,剩下的可以都丢给后面的模式规则去兜底。

一般情况下我们不会将工具文件和我们的主文件放在同一级目录下,因此我新建个文件夹my_tools,然后将工具文件放进去,接着我们就毫不意外地发现Makefile找不到库文件了。

我们可以在Makefile中指定头文件目录,指定的方式也很简单,就像下面这样。

VPATH = 路径1:路径2……

可以指定多个路径,用冒号:隔开即可。

指定完之后仍然报错,但这就是链接阶段报的错误了。

我们需要在规则的命令中添加头文件路径。

最终是下面这样的。

可以正常运行,在Makefile执行规则命令的时候也帮我们把头文件路径加上去了。

VPATH是一个特殊变量,指明了这个特殊变量,当Makefile找不到依赖文件,也没有目标是依赖的规则,那么Makefile就会到VPATH指明的路径去寻找。

除了VPATH还有vpath,也就是小写的,但是vpath不是变量,而是关键字,使用可以参考下面这样。

看的出来vpath一次只能添加一条路径,但是和VPATH相比的话,vpath可以更细致地指定路径,可以给不同后缀的文件指定不同的路径。

至此我们就算是入门Makefile成功了,相信各位小伙伴已经能够给自己以前写的小项目编写(相对)高效的Makefile了。

如果想进一步了解Makefile的话,最好的方法是去看官方文档,当然了,也可以看看陈皓老师写的《跟我一起写 Makefile》,下载链接在本文的开头。


网站公告

今日签到

点亮在社区的每一天
去签到