【多目录makefile递归编译示例】【三】递归逻辑分析

上一篇:【多目录makefile递归编译示例】【二】顶层Makefile/子目录makefile


递归及makefile执行的逻辑是什么样的呢?

本文展开详细分析顶层Makefile、包含子目录的目录makefile、不包含子目录的目录makefile的执行逻辑、递归的核心、最后回顾下目标生成先后过程。

说明:以下makefile分析代码右侧已标注 (0)、(1)、(2)、(3)、(4)、(5)、(6)

1. 顶层Makefile

顶层目录文件如下:
在这里插入图片描述

代码如下:

tgt := test																(0)
$(tgt):$(sub_dir_objs) $(cur_objs)	#test:a/bi.a b/bi.a m.o 			(1)
	gcc -o $@ $^					#gcc -o test a/bi.a b/bi.a m.o	(4)
%bi.a:%								#a/bi.a:a b/bi.a:b					(2)
	make -C $< -f makefile			#make -C a -f makefile			(5)
									#make -C b -f makefile
%o:%c								#m.o:m.c							(3)
	gcc -c $< -o $@					#gcc -c m.c -o m.o				(6)

(1)行:本层makefile(Makefile)的任务$tgt(test)见(0)依赖于本层的目录的目标(a/bi.a b/bi.a)和本层的c文件目标(m.o),只要本层的目录目标和c文件目标都已经生成,通过命令(4)

gcc -o $@ $^ 				# $@ = $(tgt), $^ = $(sub_dir_objs) $(cur_objs)

就可以完成本层makefile(Makefile)的任务$tgt(test),任务分工和执行方法都已明确。但是这些依赖都生成了吗?未必。那么命令(4)就得等着,make先去解决依赖的生成。先分析简单的c文件目标(3)。

(3)行:本层的c文件目标(m.o)依赖于本层的c文件(m.c),通过命令(6)

gcc -c $< -o $@				# $@ = %o = m.o,$< = %c = m.c

就可以完成本层的c文件目标(m.o)。任务分工和执行方法都是明确的,而且本层的c文件(m.c)作为依赖项都已存在,完美!命令(6)就可以执行,交出m.o,对(1)而言,(3)任务已经完成。剩下相对复杂的目录的目标(2)了。

(2)行:本层的目录的目标(a/bi.a b/bi.a),它们都代表了自己目录包含的所有子目录或文件的一个可链接的信息总和,至于这个总和的交付物bi.a是怎么生成的,这个任务,得交给目录中的makefile(makefile)去完成了,已经不是本层makefile(Makefile)的主要任务了,虽然本层makefile(Makefile)也会协助完成,执行命令(5)

make -C $< -f makefile		# $< = a 时命令为: make -C a -f makefile

比如要交付a/bi.a,就执行make -C a -f makefile,意思是进入a目录并执行这里的makefile。而a目录下的makefile的第一个目标就是bi.a而不是test了。(当然,这里是有目录的,a目录交付的就会是a/bi.a,b目录交付的就会是b/bi.a)这正是本层makefile(Makefile)需要本层目录完成的目标(a/bi.a b/bi.a),本层makefile(Makefile)和本层目录的对接接口算对齐了。

2. 包含子目录的目录makefile

a目录文件如下如下:
在这里插入图片描述
代码如下:

tgt := bi.a																(0)
$(tgt):$(sub_dir_objs) $(cur_objs)	#a/bi.a: c/bi.a a.o 				(1)
	ld -r $^ -o $@					#ld -r a.o c/bi.a -o a/bi.a  (4)
%bi.a:%								#c/bi.a:c							(2)
	make -C $< -f makefile			#make -C c -f makefile		 (5)
%o:%c								#a.o:a.c							(3)
	gcc -c $< -o $@					#gcc -c a.c -o a.o			 (6)

(1)行:本层makefile(makefile)的任务$tgt(bi.a)见(0)依赖于本层的目录的目标(c/bi.a)和本层的c文件目标(a.o),只要本层的目录目标和c文件目标都已经生成,通过命令(4)

ld -r $^ -o $@				# $@ = $(tgt), $^ = $(sub_dir_objs) $(cur_objs)

就可以完成本层makefile(makefile)的任务$tgt(bi.a),任务分工和执行方法都已明确。但是这些依赖都生成了吗?未必。那么命令(4)就得等着,make先去解决依赖的生成。先分析简单的c文件目标(3)。

(3)行:本层的c文件目标(a.o)依赖于本层的c文件(a.c),通过命令(6)

gcc -c $< -o $@ 			# $@ = %o = a.o,$< = %c = a.c

就可以完成本层的c文件目标(a.o)。任务分工和执行方法都是明确的,而且本层的c文件(a.c)作为依赖项都已存在,完美,命令(6)就可以执行,交出a.o,对(1)而言,(3)任务已经完成。剩下相对复杂的目录的目标(2)了。

(2)行:本层的目录的目标(c/bi.a),它们都代表了自己目录包含的所有子目录或文件的一个可链接的信息总和,至于这个总和的交付物bi.a是怎么生成的,这个任务,得交给目录中的makefile(c/makefile)去完成了,已经不是本层makefile(a/makefile)的主要任务了,虽然本层makefile(a/makefile)也会协助完成,执行命令(5)

make -C $< -f makefile		# $< = c 命令为 make -C c -f makefile

要交付c/bi.a,就执行make -C c -f makefile,意思是进入c目录并执行这里的makefile。而c目录下的makefile的第一个目标也是bi.a。(同样加上目录,c目录交付的就会是c/bi.a(a目下的视角,如果从示例根目录视角为a/c/bi.a)这正是本层makefile(a/makefile)需要本层目录(c)完成的目标(c/bi.a),本层makefile(a/makefile)和本层目录(c)的对接接口也对齐了。

3. 不包含子目录的目录makefile

c目录文件如下如下:
在这里插入图片描述
代码如下:

tgt := bi.a																(0)
$(tgt):$(sub_dir_objs) $(cur_objs)	#c/bi.a: [] c1.o c.o 				(1)
	ld -r $^ -o $@					#ld -r c1.o c.o -o c/bi.a 		(4)
%bi.a:%								# []:[]							(2)
	make -C $< -f makefile			#make -C c -f makefile			(5)
%o:%c								#c1.o:c1.c c.o:c.c					(3)
	gcc -c $< -o $@					#gcc -c c1.c -o c1.o			(6)
									#gcc -c c.c -o c.o		

(1)行:本层makefile(c/makefile)的任务$tgt(bi.a)见(0)依赖于本层的目录的目标(没有子目录时为空)和本层的c文件目标(c1.o、c.o),只要本层的目录目标(没有子目录时为空)和c文件目标都已经生成,通过命令(4)

ld -r $^ -o $@				# $@ = $(tgt) 而 $^ = $(sub_dir_objs) $(cur_objs)

就可以完成本层makefile(makefile)的任务$tgt(bi.a)。但是这些依赖都生成了吗?未必,那么命令(4)就得等着,make先去解决依赖的生成。先分析简单的c文件目标(3)。

(3)行:本层的c文件目标(c1.o和c.o)依赖于本层的c文件(c1.c、c.c),通过命令(6)

gcc -c $< -o $@ 			# gcc -c c1.c -o c1.o 和 gcc -c c.c -o c.o

就可以完成本层的c文件目标(c.o、c1.o)。因为c文件依赖项c1.c、c.c同样完美的存在了,命令(6)就可以执行,交出c.o、c1.o,对(1)而言,(3)任务已经完成。剩下目录的目标(2)了。

(2)行:本层没有子目录,目标和依赖为空,(5)不需要执行了,打道回府。其实(1)在看依赖时就会过滤掉“[空]”这个依赖。这里进入c目录make完退出c目录后,就能完成c/bi.a的交付了;同样的进入b目录make完退出b目录后,也能完成b/bi.a的交付了。

4. 递归的秘密

4.1 dir/bi.a:dir

为什么要设计为将一个目录下的聚合目标依赖于这个目录?示例中%bi.a:%,例如顶层目录为a/bi.a:a b/bi.a:b。

%bi.a:%
	make -C $< -f makefile

这样设计,实际上目录作为依赖,实现了自动化变量的传递,$<是第一个依赖文件,就是%bi.a:%中的%。目录,它作为一个参数传递到“make -C $< -f makefile”命令中,该命令完成进入子目录并执行子目录的makefile,子目录的makefile完成的目标和上层makefile的依赖项(如a/bi.a)保持 一致,就完成了目录与子目录的接口对接,这是递归的基础。

4.2 目录和子目录的行为保持一致

通过以上顶层Makefile包含子目录的目录makefile不包含子目录的目录makefile 3个场景的分析,我们发现他们都有一些共同的行为,就是目录的任务拆解:每个目录包括顶层都会将目标拆解为.c的目标.o和依赖目录的bi.a,这个行为在每一层的目录都是一样的,都是借助wildcard函数获取本层的.c文件,利用find或ls命令获取本层的目录文件。递归的本质就是要利用行为的一致和不断的重复。

4.3 递归的结束

没有子目录的目录就会首先完成它作为上层的目标,它没有需要额外的依赖需要完成,直接执行命令进行编译就可以交付自己的目标,从而返回到它的上层目录,如示例中c目录和b目录,它们是递归返回的2个点:
在这里插入图片描述

5. 目标生成先后过程

在这里插入图片描述

  • 进入根目录
    • 进入b目录:①:b.c -> b.o ②:b.o-> bi.a
    • 进入a目录:③:a.c -> a.o
      • 进入c目录:④:c1.c -> c1.o c.c -> c.o ⑤ c1.o c.o -> bi.a
    • 进入a目录:⑥:a.o c/bi.a -> bi.a
  • 进入根目录:⑦:m.c -> m.o ⑧:m.o b/bi.a a/bi.a -> test

下一篇:【多目录makefile递归编译示例】【四】makefile疑难知识点(ifdef、伪目标、FORCE、让make停下来、“:=”和“=”区别、$$)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值