Makefile 基础

0x00 吐槽

最近一段时间真是命途多舛,昨天晚上感觉差点被发烧烧成傻子,到晚上2点多都烧到了39,当时真的担心自己睡过去醒了会成为白痴,那我还如何去怼人,那么多没做的事自己再也意识不到是多么可悲的事情。这段时间诸多不顺,当然这种不顺利也不仅仅是在我身上,也许生活本该这样充满波折才比较有趣,但是大条的自己却往往不去在意一些细节,让自己一错再错,比如这日常笔记就一断再断。不得不说,能坚持做一件事情真的时非常不容易,那些可以坚持的人必然具有强大的内心,而我自己又是那种外表逞强内心脆弱的人,有的时候真的很羡慕那些无忧无虑做爱做的事情的人,也许,我羡慕的一直都是我自己而不知。

0x81 make

扒wiki的事情这里就不做了,有时候想想自己把网上的东西用自己理解的语言表达出来还不一定准确,毕竟这是此情此景下的理解,过个小半年自己如果再回来看也许都不知道自己当初说了啥。这里简单的说一下make,make在英语里就是做的意思,make love啦等等,我们可以将它理解为程序的构建工具,就像webpack一样,它根据描述文件Makefile调用编译工具如GCC完成整个构建任务build,说她是个辅助工具也不为过。它有很多实现,在Linux下常用的就是GNU Make Tool。

0x82 Makefile

Makefile是一个描述文件,也可以视为配置文件,就像webpack的webpack.config.js文件,它描述了构建任务所需的必要参数和构建过程。通常make工具会自己搜寻目录下的描述文件,如makefile、Makefile等,我们只需要在工程目录下创建相应的文件即可。

0x83 Makefile 基本原则

如果你只是进行非常普通的人力重复的构建任务,那Makefile再适合不过了,对于这种状况Makefile的书写也非常简单:

1
2
target : dependencies[;command]
command

target:目标的意思,就是你要构建什么
dependencies:依赖,意为构建目标要依赖哪些东西
command:指令,满足依赖的情况下完成构建目标需要执行什么命令。命令的书写有两种格式:一种是简单命令直接跟在dependencies后面,用”;”分隔;另一种是另起一行,每行命令用tab开头以让make知道这是一组命令。

例如:

1
2
main : main.cpp
g++ -o main main.cpp

这样再工程目录下执行make,便会生成main这个二进制文件。

0x84 变量

仔细看Makefile可以发现它其实有点类似bash脚本,假设我们的二进制文件main的构建需要依赖一大堆文件,那我们在命令中也要同样书写这些重复的文件名,所以Makefile支持变量特性,可以将这些模板代码统一管理,还可以减少维护的难度:

1
2
3
4
5
6
7
8
OBJECT=main.o d1.o d2.o d3.o

main : $(OBJECT)
g++ -o main $(OBJECT)

d1.o : d1.cpp d1.h
g++ -c d1.cpp
...

当然这个OBJECT变量的值也可在make命令运行时指定,比如make OBJECT=xxx

0x85 自动推断

通过上面的例子,我们发现在生成.o中间文件时,我们都会书写cpp文件,这样就会显得特别繁琐,GNU make支持自动推断,所以下面两段脚本的作用是等同的:

1
2
3
4
d1.o : d1.cpp dd1.h
g++ -c d1.cpp

d1.o : dd1.h

0x86 文件包含

当工程变得庞大,我们的Makefile可能也会变得冗长,我们通常会想把Makefile按我们编写代码的模块一样分开书写,那就使用include关键字,包含的文件将会被make工具自动加载进来。

0x87 伪目标与更新规则

我们在用Tarball的方式自己编译源码安装软件时,通常会执行一次make clean以确保删除之前编译留下的文件来进行一次全量编译,那clean是怎么实现的呢?

这里我们先来说一下更新规则,make很智能,在你使用make进行构建任务时,他总能找到你做了修改的源文件并依次更新依赖目标,最终编译出一个新的二进制ELF或其他target,而对于那些没有改动的,它便不会去重新编译。

make的更新规则主要有两条,一是存在依赖,二是时间戳比目标新。首先来说第一条规则——存在依赖,它的意思就是target后的dependencies不能是空的,否则make会跳过这个目标,除非你显式的指定它。而我们的clean工作就是一个dependencies是空的伪target,这样在构建的过程中便不会执行对应的清扫命令,而在我们使用make clean的时候它会生效,为了方便维护我们还会添加.PHONY声明词缀来标识这件事情:

1
2
3
.PHONY : clean
clean :
-rm -f $(OBJECT) main // rm前的"-"是指即使出现执行失败的状况也不要打断这个过程,比如"No such file or directory."

至于第二条时间戳更新就比较好理解了,通常情况下只有依赖文件的时间戳比目标文件新才说明发生了更改,需要重新编译或链接。

Makefile的基本用法就是这样,很简单清晰自然,当然在比较大的工程里Makefile绝不会这么简单,写好Makefile也是一门学问,一个成型的项目的Makefile通常会充斥了大量的条件判断和可重用的函数,这些扩展后面有机会再写(估计是难了,好多有机会的坑一个都没补2333),有时间玩一下交叉编译工具CMake,当下只简单的在AndroidJni工程里写过简单的CMakeLists.txt文件,还需多多学习呀~