Makefile教程 (一) —— 如何编写一个简单的Makefile

在详细介绍Makefile之前,我们先来看一下一个Makefile文件长啥样,对它有一个直观的认识。下面是一个Makefile的例子,摘自 https://makefiletutorial.com/:

blah: blah.o
	gcc blah.o -o blah

blah.o: blah.c
	gcc -c blah.c -o blah.o

blah.c:
	echo "int main() { return 0; }" > blah.c

clean:
	rm -f *.o

假设我们在上述Makefile的同一目录下执行make命令,将会得到以下输出:

make
echo "int main() { return 0; }" > blah.c
gcc -c blah.c -o blah.o
gcc blah.o -o blah

从输出结果可以看出,执行make指令实际上执行了3条shell命令,这些命令都是我们在Makefile中声明过的。

通常,Makefile的作用就是用于生成可执行文件,其内容描述了源代码、目标文件、链接库、可执行文件之间的关系,描述了如何由源代码生成最终的可执行文件。

Makefile的格式

Makefile是由一系列 rule 组成的文件,它通常用来构建一个程序。Makefile的第一个 rule 是 default rule

rule的格式如下:

target1 target2 target3 : prerequisite1 prerequisite2
	command1
	command2
	command3

其中:

  • command行必须以tab开头
  • 可以使用#开启一行注释
  • 如果一行写不完可以使用\符号结束当前行,然后在下一行继续写

一条rule可以看做是一个任务,target可以理解为该任务的任务名(target的值可以是最终生成的目标文件的名称,也可以是其他自定义名称),prerequisites可以理解为执行该任务所需要的文件的文件名列表,commands可以理解为完成该任务需要执行的shell命令。

target不一定非得是文件名,但是prerequisite是文件名

例如一开始的例子中,存在4条rule。

以第一条rule为例,其任务名是blah,执行的指令是gcc blah.o -o blah,显然执行这条指令需要blah.o这个文件,因此prerequisite是blah.o

上述rule的格式中,target可以有多个。当target有多个时,就说明这些target对应的任务所需要的prerequisite相同,所需要的command也相同,所以说:

target1 target2: pre1 pre2
	cmd1
	cmd2

就等价于:

target1: pre1 pre2
	cmd1
	cmd2
target2: pre1 pre2
	cmd1
	cmd2

如何使用Makefile?

使用Makefile的方法就是在其同一目录下执行如下命令:

make [target]

其中,target参数是可选的,make target来执行Makefile中某一条特定的任务;当我们没有指定target,直接调用make时,会执行 default rule,也就是Makefile中第一个rule。

make的执行过程

当使用make指令执行某一任务时,会先确保该任务的prerequisite都已经被满足或达到最新版本,也就是依赖检查(Dependency Checking),然后才能执行该任务的command。

下面结合一个例子来分析:

count_words: count_words.o lexer.o -lfl
	gcc count_words.o lexer.o -lfl -o count_words
count_words.o: count_words.c
	gcc -c count_words.c
lexer.o: lexer.c
	gcc -c lexer.c
lexer.c: lexer.l
	flex -t lexer.l > lexer.c

对于上述Makefile,假设我们执行make命令,那么根据前面说的,它会先执行第一个任务(default rule)。
从左到右依次检查第一个任务的prerequisite是不是已经满足或达到最新版本:

  • count_words.o:发现该文件名是第2个任务的target名称,且count_words.o文件不存在,于是需要先转到第2个任务
    • 检查第2个任务的prerequisite是否满足或是最新,发现count_words.c没有对应的任务,且该文件存在没有被编译过,所以执行gcc -c count_words.c命令,生成count_words.o
  • lexer.o:同理,先执行第4个任务生成lexer.c,再执行第3个任务生成lexer.o
  • -lfl:gcc的-l选项用于指定在链接时必须引入的系统库。对于-l<NAME>,gcc首先寻找一个名为libNAME.so的文件,如果存在使用该文件;否则寻找一个名为libNAME.a的文件。这里找到的是libfl.a库文件。

上述3个prerequisite都满足且达到最新版本后,才执行第1个任务的command。

注意: 并非每次执行make命令都会导致重新编译,如果一个任务的prerequisite没有更新的话,那么就不会再次执行该任务的command。
同样以上面的Makefile为例,假设count_words.c文件没有发生更新,当我们第二次执行make命令时,同样先走到第1个任务,检查到它的prerequisite中的count_words.o,发现该文件存在且对应第2个任务,于是检查第2个任务,发现第二个任务的prerequisite对应的count_words.c没有更新,因此没必要重新编译,直接认定count_words.o这个prerequisite已经符合条件了,继续检查下一个prerequisite。从make指令的输入可以看到第二次执行make不会打印gcc -c count_words.c

Phony Target

所谓phony target,就是那些target的值不表示一个文件的target。例如本文一开始的例子中,clean就是一个phony target,它不代表任何文件;blah, blah.o, blah.c就不是phony target。

对于本文一开始的例子,我们可以通过make clean执行clean任务,清除掉目标文件。

但是,如果考虑这样一种特殊情况。当前目录下存在一个名为clean的文件的话,多次执行make clean会怎么样?下面我们试验一下:

touch clean
➜  make clean
make: 'clean' is up to date.

可以看到,当创建clean文件后,第一次执行make clean就会提示'clean' is up to date.。这是因为,当存在clean文件时,make会把clean当做一个文件名,而这个文件名对应的rule没有任何prerequisite,也就是不依赖其他任何文件才能生成clean文件,因此clean任何时候都是最新的。

那么,如何避免上述问题呢?显然我们仍然希望make clean能清除掉当前目录下的目标文件。

这时候phony rule就派上用场了,我们可以使用Makefile内置的一个特殊的rule——phony rule——来解决该问题。该rule的target名称是.PHONY,将clean声明为.PHONY的prerequisite即可表示clean不是一个文件名:

.PHONY: clean
# ...
clean:
	rm -rf *.o

现在执行make clean便会得到我们想要的输出:

make clean
rm -f blah.o blah.c blah

一般来讲,应该尽量把那些不对应任何文件的target名声明在phony rule里面,以防止出现'xxx' is up to date. 的情况。

下面是Makefile中的一些标准的phony target:
在这里插入图片描述

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值