9 make递归调用

260 阅读3分钟

本次实验将介绍 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。

说明

  1. subdir 变量使用了 := 进行赋值,这是直接展开赋值,即 make 读到此行时就直接将 subidr 文本固定为后面赋值的内容。我们会在后续章节继续讲解 :== 的差别。对简单的 makefile 而言,推荐大家尽量使用 := 的赋值方式。
  2. .PHONY 中也引用了变量 subdir,这样做没有任何问题。
  3. 观察依赖项的规则命令,进入子目录后使用了 $(MAKE) 而非 make,这样做的好处是保证执行上下层 makefile 的 make 都是同一个程序。

当前目录下的两个子目录 dir_adir_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_adir_b 目录,执行目录下的 makefile,并完成终极目标的重建。除了预期打印之外,终端上还多出了几行 make 进出目录的打印信息,这其实是由 -w 选项来控制的。

make在进行递归调用时会自动传递w选项给下层make\color{red}{make 在进行递归调用时会自动传递 `-w` 选项给下层 make。}

现在我们在顶层 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_adir_b 中的 makefile 都成功的继承了上层传递的 subdir 变量。


unexport变量

如果不希望变量再往下传递,可以使用 unexport 关键字,使用方式与 export 一致。当 unexportexport 作用于同一个变量时,以最后声明的 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就传不到了

图片.png


两个默认传递的环境变量

需要注意的是有两个变量默认会传递给下层的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 的输出结果如下图所示:

可见 SHELLMAKEFLAGS 变量默认是有值的.

如何证明他们是从上层传递下来的而不是 make 的默认值呢?只需要在顶层调用 make 时修改他们的值即可:

Terminal 的输出结果如下图所示:

在上个章节的实验已经测试过 SHELL 变量和 -i-k 选项的作用,我们可以简单的回顾一下,-i 可以让 make 忽略在执行过程中所产生的错误,然后继续向下执行,而 -k 选项则是忽略因依赖不存在所导致的错误。


3️⃣ 测试 MAKELEVEL 环境变量

变量 MAKELEVEL 表明当前的调用深度,每一级的递归调用中,MAKELEVEL 的值都会发生变化, 最上层值为 0,每往下一层加 1。 chapter8/level/makefile 演示了 MAKELEVEL 的变化,level/ 目录下还有两层子目录 dir_adir_b,每一层的 makefile 都会打印当前 MAKELEVEL 的值。

现在进入 level 目录并执行 makefile,通过 Terminal 的输出结果观察 MAKELEVEL 的变化。 、

cd ../level/;make

Terminal 的输出结果如图所示:

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

4️⃣ 命令行参数和变量的传递

在 make 的递归执行过程中,最上层 make 的命令行选项会被自动通过环境变量 MAKEFLAGS 传递给子 make 进程。如前面的实验,通过命令行指定了 -i-k 选项,MAKEFLAGS 的值会被自动设定为「ki」。

注意

  1. -w 选项默认会被传递给子 make
  2. 有几个特殊的命令行选项不会被记录进变量,它们是 -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 命令。

图片.png

可见顶层 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 的递归执行及其过程中变量、命令行参数的传递规则。