makefile教程9

一、实验介绍--Make 递归执行

本次实验将介绍 make 的递归执行及其过程中变量、命令行参数的传递规则。

1.1 实验内容

1.make的递归执行示例

  1. 递归执行过程中变量的传递
  2. 测试MAKELEVEL环境变量
  3. 命令行参数和变量的传递

1.2 实验知识点

1.make-w选项可以打印make进入和离开目录的信息。

2.makefile中通常使用$(MAKE)递归执行下层makefile,以确保上下层的make程序一致。

  1. 递归执行过程中,顶层文件的变量默认不会传给下层文件。
  2. 可以使用export进行变量传递,也可以使用unexport取消传递。
  3. 当上层变量和下层变量定义有冲突时,下层优先级更高,也可以使用-e选项取消下层的定义。6.$(SHELL)变量和$(MAKEFLAGS)变量默认会传给下层文件。7.$(MAKELEVEL)可以显示make当前的文件层级。8.$(MAKEFLAGS)变量会记录make的命令行选项和参数。

1.3 实验环境

Ubuntu系统, GNU gcc工具,GNU make工具

1.4 适合人群

本课程难度为中等,适合已经初步了解 makefile 规则的学员进行学习。

1.5 代码获取

可以通过以下命令获取代码:

$ git clone https://github.com/darmac/make_example.git

   
   

二、实验原理

依据 makefile 的基本规则进行正反向实验,学习和理解规则的使用方式。

三、开发准备

进入实验楼课程即可。

四、项目文件结构


   
   
  1. .
  2. ├── flags:用于测试 MAKEFLAGS 变量
  3. │ ├── dir_a
  4. │ │ └── makefile
  5. │ └── makefile
  6. ├── level:用于测试 MAKELEVEL 变量
  7. │ ├── dir_a
  8. │ │ ├── dir_b
  9. │ │ │ └── makefile
  10. │ │ └── makefile
  11. │ └── makefile
  12. ├── Readme.md
  13. ├── recur:用于测试 make 的递归调用
  14. │ ├── dir_a
  15. │ │ └── makefile
  16. │ ├── dir_b
  17. │ │ └── makefile
  18. │ └── makefile
  19. └── vari:用于测试 make 递归调用的变量传递
  20. ├── dir_a
  21. │ └── makefile
  22. ├── dir_b
  23. │ └── makefile
  24. ├── dir_c
  25. │ └── makefile
  26. ├── export.mk
  27. ├── makefile
  28. └── spec.mk

五、实验步骤

5.1make的递归执行示例

5.1.1 抓取源代码

使用如下 cmd 获取 GitHub 源代码并进入相应章节:


   
   
  1. cd ~/Code/
  2. git clone https://github.com/darmac/make_example.git
  3. cd make_example/chapter8
5.1.2 测试make的递归调用

make 的递归过程是指:在makefile中使用make作为命令来执行本身或其它makefile文件。

在实际项目中,我们经常需要用到make的递归调用,这样可以简化每个模块的makefile设计与调试,便于项目管理。

chapter8/recur/目录演示了一个简单的递归调用过程,主目录makefile内容如下:


   
   
  1. #this is a makefile for recursion test
  2. subdir := dir_a dir_b
  3. . PHONY:clean all $(subdir)
  4. all:$(subdir)
  5. @echo "final target finish!"
  6. $(subdir):
  7. cd $@;$(MAKE)

终极目标all依赖于两个依赖项,依赖项的规则命令是进入子目录并调用底层的makefile

这里有三个地方需要说明:

1)subdir变量使用了:=进行赋值,这是直接展开赋值,即make读到此行时就直接将subidr文本固定为后面赋值的内容。我们会在后续章节继续讲解:==的差别。对简单的makefile而言,推荐大家尽量使用:=的赋值方式。

2).PHONY中也引用了变量subdir,这样做没有任何问题。

3)观察依赖项的规则命令,进入子目录后使用了$(MAKE)而非make,这样做的好处是保证执行上下层makefilemake都是同一个程序。

两个子目录下的makefile文件内容相同,都是:


   
   
  1. .PHONY :show
  2. show:
  3. @ echo "target " $@ "@" $(PWD)

所以子目录的执行过程是更新目录时间戳,并打印当前执行路径。

现在进入recur目录并执行make

cd recur/;make

   
   

终端打印:


   
   
  1. cd dir_a;make
  2. make[1]: Entering directory `/home/shiyanlou/Code/make_example/chapter8/recur/dir_a '
  3. target show @ /home/shiyanlou/Code/make_example/chapter8/recur/dir_a
  4. make[1]: Leaving directory `/home/shiyanlou/Code/make_example/chapter8/recur/dir_a'
  5. cd dir_b;make
  6. make[1]: Entering directory `/home/shiyanlou/Code/make_example/chapter8/recur/dir_b '
  7. target show @ /home/shiyanlou/Code/make_example/chapter8/recur/dir_b
  8. make[1]: Leaving directory `/home/shiyanlou/Code/make_example/chapter8/recur/dir_b'
  9. final target finish!

分析打印内容可知,make 先后进入dir_adir_b目录执行目录下的makefile,并完成终极目标的重建。

除了预期打印之外,终端上还多出了几行make进出目录的打印信息,这其实是由-w选项来控制的。

make在进行递归调用时会自动传递-w选项给下层make

我们在顶层make也加上-w确认一下效果:

make -w

   
   

终端打印:


   
   
  1. make: Entering directory `/home/shiyanlou/Code/make_example/chapter8/recur '
  2. cd dir_a;make
  3. make[1]: Entering directory `/home/shiyanlou/Code/make_example/chapter8/recur/dir_a'
  4. target show @ /home/shiyanlou/Code/make_example/chapter8/recur/dir_a
  5. make[1]: Leaving directory `/home/shiyanlou/Code/make_example/chapter8/recur/dir_a '
  6. cd dir_b;make
  7. make[1]: Entering directory `/home/shiyanlou/Code/make_example/chapter8/recur/dir_b'
  8. target show @ /home/shiyanlou/Code/make_example/chapter8/recur/dir_b
  9. make[1]: Leaving directory `/home/shiyanlou/Code/make_example/chapter8/recur/dir_b '
  10. final target finish!
  11. make: Leaving directory `/home/shiyanlou/Code/make_example/chapter8/recur'

可知make在执行顶层makefile和执行完毕之后都会打印进出当前目录的信息。

实验过程如下图所示:

5.1

5.2 递归执行过程中变量的传递

5.2.1 变量传递

make的递归执行过程中,上层的变量默认状态下是不会传递给下层makefile的。

chapter8/vari/目录下的内容演示了变量传递的过程,先看看chapter8/vari/makefile文件:


   
   
  1. #this is a makefile for recursion test
  2. subdir := dir_a dir_b
  3. . PHONY:clean all $(subdir)
  4. all:$(subdir)
  5. @echo "final target finish!"
  6. $(subdir):
  7. cd $@;$(MAKE)

与上一个实验中的内容一致,进入两个子目录并调用$(MAKE)之后打印完成消息。

dir_a/makefile的内容如下:


   
   
  1. . PHONY:show
  2. show:
  3. @echo "target " $@ "@" $(PWD)
  4. @echo "vari subdir is " $(subdir)

打印当前目录,并打印subdir变量的内容。

dir_b/makefile内容相似,但多出了一个subdir的定义:


   
   
  1. . PHONY:show
  2. subdir := none
  3. show:
  4. @echo "target " $@ "@" $(PWD)
  5. @echo "vari subdir is " $(subdir)

执行打印,看看子目录的subdir内容:

cd ../vari/;make

   
   

终端打印:


   
   
  1. cd dir_a;make
  2. make[1]: Entering directory `/home/shiyanlou/Code/make_example/chapter8/vari/dir_a '
  3. target show @ /home/shiyanlou/Code/make_example/chapter8/vari/dir_a
  4. vari subdir is
  5. make[1]: Leaving directory `/home/shiyanlou/Code/make_example/chapter8/vari/dir_a'
  6. cd dir_b;make
  7. make[1]: Entering directory `/home/shiyanlou/Code/make_example/chapter8/vari/dir_b '
  8. target show @ /home/shiyanlou/Code/make_example/chapter8/vari/dir_b
  9. vari subdir is none
  10. make[1]: Leaving directory `/home/shiyanlou/Code/make_example/chapter8/vari/dir_b'
  11. final target finish!

可见上层的subdir变量没有传递到下层makefile中。

5.2.2 使用export传递变量

如果想要将上层变量往下层传递,需要用到export关键字,格式为:

export VARIABLE ...

   
   

export.mk文件演示了export的用法,它的内容与makefile一致,只是在定义完subdir之后多了一行:

export subdir

   
   

执行 export.mk 文件:

make -f export.mk

   
   

终端打印:


   
   
  1. cd dir_a;make
  2. make[1]: Entering directory `/home/shiyanlou/Code/make_example/chapter8/vari/dir_a'
  3. target show @ /home/shiyanlou/Code/make_example/chapter8/ vari/dir_a
  4. vari subdir is dir_a dir_b
  5. make[ 1]: Leaving directory `/home/shiyanlou/Code/make_example/chapter8/vari/dir_a'
  6. cd dir_b;make
  7. make[1]: Entering directory `/home/shiyanlou/Code/make_example/chapter8/ vari/dir_b '
  8. target show @ /home/shiyanlou/Code/make_example/chapter8/vari/dir_b
  9. vari subdir is none
  10. make[1]: Leaving directory `/home/shiyanlou/Code/make_example/chapter8/vari/dir_b'
  11. final target finish!

这次dir_a目录下的makefile可以成功继承上层传递的subdir变量,

dir_b目录下的makefile打印出来依然为“none”,这是因为低层的makefile对变量定义具有更高的优先级。

可以使用-e选项来取消低层的高优先级:

make -f export.mk -e

   
   

终端打印:


   
   
  1. cd dir_a;make
  2. make[1]: Entering directory `/home/shiyanlou/Code/make_example/chapter8/vari/dir_a '
  3. target show @ /home/shiyanlou/Code/make_example/chapter8/vari/dir_a
  4. vari subdir is dir_a dir_b
  5. make[1]: Leaving directory `/home/shiyanlou/Code/make_example/chapter8/vari/dir_a'
  6. cd dir_b;make
  7. make[1]: Entering directory `/home/shiyanlou/Code/make_example/chapter8/vari/dir_b '
  8. target show @ /home/shiyanlou/Code/make_example/chapter8/vari/dir_b
  9. vari subdir is dir_a dir_b
  10. make[1]: Leaving directory `/home/shiyanlou/Code/make_example/chapter8/vari/dir_b'
  11. final target finish!

可以看出,现在subdir可以顺利传递给每个子目录的makefile

如果不希望变量再往下传递,可以使用unexport关键字,格式与export一致。

unexportexport作用于同一个变量时,以最后声明的unexport/export为准,请大家课后自行测试unexport的用法。

5.2.3 两个默认传递的环境变量

需要注意的是有两个变量默认会传递给下层的makefile,它们是$(SHELL)$(MAKEFLAGS)变量。

之前的实验已经说明了$(SHELL)的作用:指明要使用何种shell程序执行规则命令。

$(MAKEFLAGS)则是用来传递make的命令行选项和参数,实验5.4会对这个变量做进一步说明。

spec.mk文件演示了这两个变量的传递,内容如下:


   
   
  1. #this is a makefile for special vari in recursion test
  2. subdir := dir_c
  3. . PHONY:all $(subdir)
  4. all:$(subdir)
  5. @echo "finished!"
  6. $(subdir):
  7. cd $@;$(MAKE)

spec.mk会进入dir_c为子目录并执行$(MAKE)

子目录makefile内容如下:


   
   
  1. . PHONY:show
  2. show:
  3. @echo "SHELL is " $(SHELL)
  4. @echo "MAKEFLAGS is " $(MAKEFLAGS)
  5. @echo "subdir is " $(subdir)

因此子目录的执行内容就是打印三个变量的值。

执行spec.mk 进行测试:

make -f spec.mk

   
   

终端打印:


   
   
  1. cd dir_c;make
  2. make[1]: Entering directory `/home/shiyanlou/Code/make_example/chapter8/vari/dir_c '
  3. SHELL is /bin/sh
  4. MAKEFLAGS is w
  5. subdir is
  6. make[1]: Leaving directory `/home/shiyanlou/Code/make_example/chapter8/vari/dir_c'
  7. finished!

可见SHELLMAKEFLAGS变量默认是有值的,如何证明他们是从上层传递下来的而不是make的默认值呢?

只需要在顶层调用make时修改他们的值即可:

make -f spec.mk SHELL=bash -i -k

   
   

终端打印:


   
   
  1. cd dir_c;make
  2. make[1]: Entering directory `/home/shiyanlou/Code/make_example/chapter8/vari/dir_c '
  3. SHELL is bash
  4. MAKEFLAGS is wki -- SHELL=bash
  5. subdir is
  6. make[1]: Leaving directory `/home/shiyanlou/Code/make_example/chapter8/vari/dir_c'
  7. finished!

上个章节的实验已经测试过SHELL变量和-i``-k选项的作用,大家可以自行复习一下。

此时两个变量都得到了更新。

实验过程如下图所示:

5.2A

5.2B

5.3 测试MAKELEVEL环境变量

变量MAKELEVEL表明当前的调用深度,每一级的递归调用中,MAKELEVEL的值都会发生变化,

最上层值为0,每往下一层加1

chapter8/level/makefile演示了MAKELEVEL的变化,level/目录下还有两层子目录dir_adir_b

每一层的makefile都会打印当前MAKELEVEL的值。

进入level目录并执行makefile,确认MAKELEVEL的变化:

cd ../level/;make

   
   

终端打印:


   
   
  1. cd dir_a;make
  2. make[1]: Entering directory `/home/shiyanlou/Code/make_example/chapter8/level/dir_a '
  3. cd dir_b;make
  4. make[2]: Entering directory `/home/shiyanlou/Code/make_example/chapter8/level/dir_a/dir_b'
  5. this is level: 2
  6. dir_b finished!
  7. make[2]: Leaving directory `/home/shiyanlou/Code/make_example/chapter8/level/dir_a/dir_b '
  8. this is level: 1
  9. dir_a finished!
  10. make[1]: Leaving directory `/home/shiyanlou/Code/make_example/chapter8/level/dir_a'
  11. this is level: 0
  12. root dir finished!

由打印可以看出dir_b目录下MAKELEVEL值为2dir_a值为1level目录值为0

有些项目中需要利用MAKELEVEL变量的特性来进行条件编译,大家可以自己设计实验使用MAKELEVEL

实验过程如下图所示:

5.3

5.4 命令行参数和变量的传递

make的递归执行过程中,最上层make的命令行选项会被自动通过环境变量MAKEFLAGS传递给子make进程。

如前面的实验,通过命令行指定了-i-k选项,MAKEFLAGS的值会被自动设定为“ki”

需要注意的是:

1)-w选项默认会被传递给子make

2)有几个特殊的命令行选项不会被记录进变量,它们是-C -f -o-W

顶层makefile内容如下:


   
   
  1. #this is a makefile for MAKEFLAGS test
  2. subdir := dir_a
  3. . PHONY:all $(subdir)
  4. all:$(subdir)
  5. @echo "root dir finished!"
  6. $(subdir):
  7. @echo "MAKEFLAGS before subdir is : " $(MAKEFLAGS)
  8. cd $@;$(MAKE)

它会在进入subdir之前打印MAKEFLAGS变量,底层makefile也同样会打印MAKEFLAGS变量。

执行make

cd ../flags/;make

   
   

终端打印:


   
   
  1. MAKEFLAGS before subdir is :
  2. cd dir_a;make
  3. make[1]: Entering directory `/home/shiyanlou/Code/make_example/chapter8/flags/dir_a '
  4. MAKEFLAGS in dir_a is : w
  5. dir_a finished!
  6. make[1]: Leaving directory `/home/shiyanlou/Code/make_example/chapter8/flags/dir_a'
  7. root dir finished!

可见顶层MAKEFLAGS变量为空,底层自动添加了-w选项。

现在可以利用这些打印测试传入不同参数。

先将makefile拷贝为test.mk,再用-f选项指定执行test.mk,并添加其它参数:

cp makefile test.mk;make -f test.mk -i -k SHELL=bash

   
   

终端打印如下:


   
   
  1. MAKEFLAGS before subdir is : ki -- SHELL=bash
  2. cd dir_a;make
  3. make[1]: Entering directory `/home/shiyanlou/Code/make_example/chapter8/flags/dir_a '
  4. MAKEFLAGS in dir_a is : wki -- SHELL=bash
  5. dir_a finished!
  6. make[1]: Leaving directory `/home/shiyanlou/Code/make_example/chapter8/flags/dir_a'
  7. root dir finished!

可见顶层makefile中,MAKEFLAGS变量为“ik -- SHELL=bash”-f选项没有添加进来。

底层makefile中,MAKEFLAGS变量为“ikw -- SHELL=bash”,除了多出默认需要传递的-w选项外,其它部分与顶层传参一致。

实验过程如下图所示:

5.4

六、实验总结

本次实验测试了make的递归执行及其过程中变量、命令行参数的传递规则。

七、课后习题

1.请测试exportunexport作用于同一个变量时,make的处理方式。

2.请使用MAKELEVEL变量进行编译流程控制。

3.请查找资料并测试$(MAKE)make在命令行参数的传递处理上有何区别并设计实验进行测试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值