本次实验将介绍 make 的递归执行及其过程中变量、命令行参数的传递规则。
知识点
- make 的递归执行示例
- 递归执行过程中变量的传递
- 测试 MAKELEVEL 环境变量
- 命令行参数和变量的传递
本课程项目完成过程中将学习:
- make 的 -w 选项
- makefile 中使用 $(MAKE)
- 递归执行过程中的变量传递
- export 和 unexport
项目结构
本章实验涉及到的代码文件位于 /home/project/make_example-master/chapter8
目录下,请在 Terminal 中通过 cd
命令切换至该目录后再进行实验学习。
chapter8
的目录结构如下:
├── flags #用于测试 MAKEFLAGS 变量
│ ├── dir_a
│ │ └── makefile
│ └── makefile
├── level #用于测试 MAKELEVEL 变量
│ ├── dir_a
│ │ ├── dir_b
│ │ │ └── makefile
│ │ └── makefile
│ └── makefile
├── recur #用于测试 make 的递归调用
│ ├── dir_a
│ │ └── makefile
│ ├── dir_b
│ │ └── makefile
│ └── makefile
└── vari #用于测试 make 递归调用的变量传递
├── dir_a
│ └── makefile
├── dir_b
│ └── makefile
├── dir_c
│ └── makefile
├── export.mk
├── makefile
└── spec.mk
1️⃣ make 的递归执行示例
make 的递归过程是指在 makefile 中使用 make
作为命令来执行本身或其它 makefile 文件。 在实际项目中,我们经常需要用到 make 的递归调用,这样可以简化每个模块的 makefile 设计与调试,便于项目管理。
chapter8/recur/
中的 makefile 用来演示一个简单的递归调用过程,主要内容如下:
#this is a makefile for recursion test
subdir := dir_a dir_b
.PHONY:clean all $(subdir)
all:$(subdir)
@echo "final target finish!"
$(subdir):
cd $@;$(MAKE)
从内容上看,最终目标 all
依赖于两个依赖项,依赖项的规则命令是进入子目录并调用底层的 makefile。
说明:
subdir
变量使用了:=
进行赋值,这是直接展开赋值,即 make 读到此行时就直接将subidr
文本固定为后面赋值的内容。我们会在后续章节继续讲解:=
与=
的差别。对简单的 makefile 而言,推荐大家尽量使用:=
的赋值方式。.PHONY
中也引用了变量subdir
,这样做没有任何问题。- 观察依赖项的规则命令,进入子目录后使用了
$(MAKE)
而非 make,这样做的好处是保证执行上下层 makefile 的make
都是同一个程序。
当前目录下的两个子目录 dir_a
和 dir_b
中所包含的文件内容是相同的,其内容如下:
.PHONY:show
show:
@echo "target " $@ "@" $(PWD)
从内容上可以看到 make 对 makefile 的执行结果都是更新目录时间戳,并打印当前执行路径。
现在进入 recur
目录并执行 make
命令。
cd recur/;make
注意: 上面的命令行中 cd
命令后面跟的路径应该视情况而定,默认的情况下当前的目录应该是 /home/project/make_example-master/chapter8
,若你的当前目录不是此目录,建议在 cd
命令后跟绝对路径 /home/project/make_example-master/chapter8/recur
进行跳转,完成之后再执行 make
命令
Terminal 的输出结果如下图:
分析打印内容可知,make 先后进入 dir_a
和 dir_b
目录,执行目录下的 makefile,并完成终极目标的重建。除了预期打印之外,终端上还多出了几行 make 进出目录的打印信息,这其实是由 -w
选项来控制的。
现在我们在顶层 make 也加上 -w
并观察执行效果。
make -w
Terminal 的输出结果如图所示。
可以看到 Terminal 的输出内容增加了 make 进入和离开 recur
目录的记录。
2️⃣ 递归执行过程中变量的传递
在 make 的递归执行过程中,上层的变量默认状态下是不会传递给下层 makefile 的。
通过 chapter8/vari/
目录下的 makefile 可对变量传递的过程进行验证,makefile 的内容如下:
#this is a makefile for recursion test
subdir := dir_a dir_b
.PHONY:clean all $(subdir)
all:$(subdir)
@echo "final target finish!"
$(subdir):
cd $@;$(MAKE)
可以看出与上一个实验中的内容一致,进入两个子目录并调用 $(MAKE)
之后打印完成消息。
dir_a/makefile
的内容如下:
.PHONY:show
show:
@echo "target " $@ "@" $(PWD)
@echo "vari subdir is " $(subdir)
该 makefile 的主要作用是打印当前目录信息,同时打印出子文件的信息。
dir_b/makefile
内容与上面的 makefile 内容相似,但多出了一个 subdir
的定义,主要内容如下:
.PHONY:show
subdir := none
show:
@echo "target " $@ "@" $(PWD)
@echo "vari subdir is " $(subdir)
现在执行打印,看看子目录的 subdir
内容:
cd ../vari/;make
Terminal 的输出结果如图所示:
从dir_a的执行可以看出 顶层的 subdir
变量并没有传递至下一层的 makefile 文件中。
使用export
传递变量
如果想要将上层变量往下层传递,则需要用到 export
关键字,格式如下:
export VARIABLE ...
当前目录下的 export.mk
文件演示了 export
的用法,它的内容与 makefile 一致,只是在定义完 subdir
之后多了下面的这一行:
export subdir
现在执行下面的命令并观察输出结果。
make -f export.mk
Terminal 的输出结果的结果如下图:
可以看到这次 dir_a
目录下的 makefile 可以成功继承上层传递的 subdir
变量,但 dir_b
目录下的 makefile 打印出来依然为「none」,这是因为低层的 makefile 对变量定义具有更高的优先级。
为了解决上面的问题,可以再 make
命令之后添加选项 -e
来取消底层的高优先级。
make -f export.mk -e
Terminal 的输出结果如下图:
可以看出 dir_a
与 dir_b
中的 makefile 都成功的继承了上层传递的 subdir
变量。
unexport变量
如果不希望变量再往下传递,可以使用 unexport
关键字,使用方式与 export
一致。当 unexport
和 export
作用于同一个变量时,以最后声明的 unexport/export
为准,请大家课后自行测试 unexport
的用法。‘
比如把上面的export.mk加一个unexport subdir
#this is a makefile for recursion test
subdir := dir_a dir_b
export subdir
unexport subdir
.PHONY:clean all $(subdir)
all:$(subdir)
@echo "final target finish!"
$(subdir):
cd $@;$(MAKE)
下面的dir_a就传不到了
两个默认传递的环境变量
需要注意的是有两个变量默认会传递给下层的makefile
,它们是$(SHELL)
和$(MAKEFLAGS)
变量。
$(SHELL)
的作用:指明要使用何种shell
程序执行规则命令。$(MAKEFLAGS)
则是用来传递make
的命令行选项和参数,
当前目录下的 spec.mk
文件演示了这两个变量的传递,内容如下:
#this is a makefile for special vari in recursion test
subdir := dir_c
.PHONY:all $(subdir)
all:$(subdir)
@echo "finished!"
$(subdir):
cd $@;$(MAKE)
可以看到 spec.mk
会进入 dir_c
为子目录并执行 $(MAKE)
。
dir_c
目录下的 makefile 文件的主要内容如下:
.PHONY:show
show:
@echo "SHELL is " $(SHELL)
@echo "MAKEFLAGS is " $(MAKEFLAGS)
@echo "subdir is " $(subdir)
Terminal 的输出结果如下图所示:
可见 SHELL
和 MAKEFLAGS
变量默认是有值的.
如何证明他们是从上层传递下来的而不是 make 的默认值呢?只需要在顶层调用 make 时修改他们的值即可:
Terminal 的输出结果如下图所示:
在上个章节的实验已经测试过 SHELL
变量和 -i
和 -k
选项的作用,我们可以简单的回顾一下,-i
可以让 make 忽略在执行过程中所产生的错误,然后继续向下执行,而 -k
选项则是忽略因依赖不存在所导致的错误。
3️⃣ 测试 MAKELEVEL 环境变量
变量 MAKELEVEL
表明当前的调用深度,每一级的递归调用中,MAKELEVEL
的值都会发生变化, 最上层值为 0,每往下一层加 1。 chapter8/level/makefile
演示了 MAKELEVEL
的变化,level/
目录下还有两层子目录 dir_a
和 dir_b
,每一层的 makefile 都会打印当前 MAKELEVEL
的值。
现在进入 level
目录并执行 makefile,通过 Terminal 的输出结果观察 MAKELEVEL
的变化。
、
cd ../level/;make
Terminal 的输出结果如图所示:
有些项目中需要利用 MAKELEVEL
变量的特性来进行条件编译,大家可以自己设计实验使用 MAKELEVEL
。
4️⃣ 命令行参数和变量的传递
在 make 的递归执行过程中,最上层 make 的命令行选项会被自动通过环境变量 MAKEFLAGS
传递给子 make 进程。如前面的实验,通过命令行指定了 -i
和 -k
选项,MAKEFLAGS
的值会被自动设定为「ki」。
注意:
-w
选项默认会被传递给子make
。- 有几个特殊的命令行选项不会被记录进变量,它们是
-C
、-f
、-o
和-W
。
顶层 chapter8/flags/makefile
内容如下:
#this is a makefile for MAKEFLAGS test
subdir := dir_a
.PHONY:all $(subdir)
all:$(subdir)
@echo "root dir finished!"
$(subdir):
@echo "MAKEFLAGS before subdir is : " $(MAKEFLAGS)
cd $@;$(MAKE)
从内容上可以看到它会在进入 subdir
之前打印 MAKEFLAGS
变量,底层 makefile 也同样会打印 MAKEFLAGS
变量。
现在执行 make
命令,观察 Terminal 的输出结果。
make
注意:当前默认是在 level
目录下,所以 就可直接使用 make
命令,若你当前的目录不在 level
下则需要使用 cd
命令跳转至 level
后再执行 make
命令。
可见顶层 MAKEFLAGS
变量为空,底层自动添加了 -w
选项。
现在尝试传入不同参数来进行测试。先将 makefile 拷贝为test.mk
,再用 -f
选项指定执行 test.mk
,并添加其它参数:
cp makefile test.mk;make -f test.mk -i -k SHELL=bash
Terminal 的输出结果如图所示:
可见顶层 makefile 中,MAKEFLAGS
变量为 ik -- SHELL=bash
,-f
选项没有添加进来。底层 makefile 中,MAKEFLAGS 变量为 ikw -- SHELL=bash
,除了多出默认需要传递的-w
选项外,其它部分与顶层传参一致。
本章节测试了 make 的递归执行及其过程中变量、命令行参数的传递规则。