LV090-嵌套执行
在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的 Makefile ,这有利于让我们的 Makefile 变得更加地简洁,而不至于把所有的东西全部写在一个 Makefile 中,这样不仅方便管理,而且可以迅速发现模块中的问题。
一、嵌套执行make
1. 语法格式
我们有一个子目录叫 subdir ,这个目录下有个 Makefile 文件,来指明了这个目录下文件的编译规则。这个时候,在总的 Makefile 中嵌套使用的语法格式如下:
# 1.写法一
subsystem:
cd subdir && $(MAKE)
# 2.写法二
# CURDIR 此变量代表 make 的工作目录, -C 选项会使make命令进入指定目录下
subsystem
$(MAKE) -C subdir定义 $(MAKE) 宏变量的意思是,也许我们的 make 需要一些参数,所以定义成一个变量比较利于维护。这两个例子的意思都是先进入“subdir”目录,然后执行 make 命令。
当命令执行到上述的规则时,程序会进入到子目录中执行 make 。我们把这个最外层的 Makefile 叫做“总控 Makefile”。
2. 使用实例
2.1 文件准备
- 目录结构
sumu@virtual-machine:~/hk/alpha$ tree
.
├── Makefile
└── sub
└── Makefile- sub/Makefile
# 子目录 Makefile
all:
@echo "--- 子 Make: 开始构建 VAR=$(VAR) ---"
@echo "--- 子 Make: 构建完成 ---"
clean:
@echo "--- 子 Make: 清理完成 ---"
.PHONY: all clean- Makefile
# 主 Makefile
VAR := sumu
all:
@echo "=== 主 Make: 开始构建 VAR=$(VAR) ==="
@$(MAKE) -C sub
@echo "=== 主 Make: 构建完成 ==="
clean:
@echo "=== 主 Make: 开始清理 ==="
@$(MAKE) -C sub clean
@echo "=== 主 Make: 清理完成 ==="
.PHONY: all clean setup2.2 测试结果
sumu@virtual-machine:~/hk/alpha$ make
=== 主 Make: 开始构建 VAR=sumu ===
make[1]: 进入目录“/home/sumu/hk/alpha/sub”
--- 子 Make: 开始构建 VAR= ---
--- 子 Make: 构建完成 ---
make[1]: 离开目录“/home/sumu/hk/alpha/sub”
=== 主 Make: 构建完成 ===
sumu@virtual-machine:~/hk/alpha$ make clean
=== 主 Make: 开始清理 ===
make[1]: 进入目录“/home/sumu/hk/alpha/sub”
--- 子 Make: 清理完成 ---
make[1]: 离开目录“/home/sumu/hk/alpha/sub”
=== 主 Make: 清理完成 ===可以看到,子makefile也被执行了,但是我们定义的变量没有自动传递到下层makefile。
二、递归深度
1. 什么是递归深度
在嵌套执行 make 时,make 命令会递归地进入子目录并执行子 Makefile。每进入一层子目录,递归深度就增加一层。例如:
# 主 Makefile (深度 0)
subsystem:
$(MAKE) -C subdir # 进入子目录 (深度 1)如果子目录中还有子目录需要嵌套执行,就会形成多层递归。这里有一个系统变量“MAKELEVEL”,这个变量会记录了我们的当前 Makefile 的调用层数。顶层的MAKELEVEL的值为“0” 、下一级时为“1” 、再下一级为“2”......
2. 递归深度显示
当使用 -w 参数或 -C 参数时,make 会显示当前工作目录,从输出中可以看到递归深度:
make: 进入目录“/home/sumu/project” # 深度 0
make[1]: 进入目录“/home/sumu/project/lib” # 深度 1
make[2]: 进入目录“/home/sumu/project/lib/utils” # 深度 2其中 make[N] 中的 N 表示当前递归深度(从 1 开始计数)。
3. 递归深度的限制
Make 本身没有硬性的递归深度限制,但在实际使用中需要注意:
- 过深的递归会导致性能问题,每一层都会启动新的 make 进程
- 过多的嵌套层次会使 Makefile 难以维护和调试
- 操作系统可能对进程数或递归深度有限制
4. 控制递归深度
为了控制递归深度,建议:
(1)限制嵌套层数:通常建议嵌套不超过 3-4 层
# 建议的嵌套结构
# .
# ├── Makefile (主控)
# ├── lib/
# │ └── Makefile (第二层)
# └── modules/
# └── Makefile (第二层)(2)使用变量控制:可以在总控 Makefile 中定义最大深度,然后获取当前深度(可以自己定义变量同统计,也可以使用 MAKELEVEL),使用条件判断进行限制。
# 定义最大递归深度
MAX_DEPTH := 3
current_depth := 0
subsystem:
@$(MAKE) -C subdir DEPTH=$(shell $$(($(current_depth) + 1)))(3)避免不必要的嵌套:如果子目录较少,可以考虑在总控 Makefile 中直接处理
5. 注意事项
MAKELEVEL变量:Make 自动设置此变量来表示当前递归深度,可以在 Makefile 中读取判断- 递归深度为 0 时,表示是最外层的总控 Makefile
- 在调试时,可以使用
make -n(-n选项仅输出执行命令序列,但并不执行。)查看完整的执行流程而不实际递归
三、变量传递
1. 怎么传递变量?
总控 Makefile 的变量可以传递到下级的 Makefile 中,但是不会覆盖下层的 Makefile 中所定义的变量,除非指定了 -e 参数。如果需要传递变量到下级 Makefile 中,那么我们可以使用这样的声明方式来声明变量:
export <variable ...>如果这个变量不需要传递到下级,可以这样声明:
unexport <variable ...>如果我们需要传递所有的变量到下一级,那么我们可以直接使用 export :
export【注意】有两个变量,一个是 SHELL ,一个是 MAKEFLAGS ,这两个变量不管是否 export ,其总是要传递到下层 Makefile 中,特别是 MAKEFILES 变量,其中包含了 make 的参数信息,如果我们执 行“总控 Makefile”时有 make 参数或是在上层 Makefile 中定义了这个变量,那么 MAKEFLAGS 变量将会 是这些参数,并会传递到下层 Makefile 中,这是一个系统级的环境变量。但是 make 命令中的有几个参数并不往下传递,它们是 -C 、 -f 、 -h 、 -o 和 -W 。如果我们不想往下层传递参数,可以这样做:
subsystem:
cd subdir && $(MAKE) MAKEFLAGS=2. 相关 make 参数
-w 或是 --print-directory 会在 make 的过程中输出一些信息,让我们看到目前的工作目录。比如,如果我们的下级 make 目录是 /home/hk/gnu/make ,如果我们使用 make -w 来执行,那么当进入该目录时,我们会看到:
make: Entering directory /home/hk/gnu/make'.而在完成下层 make 后离开目录时,我们会看到:
make: Leaving directory /home/hk/gnu/make'.当我们使用 -C 参数来指定 make 下层 Makefile 时, -w 会被自动打开的。如果参数中有 -s ( --slient ) 或是 --no-print-directory ,那么, -w 总是失效的。
3. 使用实例
3.1 准备文件
3.1.1 目录结构
首先来看一下文件结构如下:
.
├── fun1
│ ├── fun1.c
│ └── Makefile
├── fun2
│ ├── fun2.c
│ └── Makefile
├── include
│ └── myinclude.h
├── main
│ ├── main.c
│ └── Makefile
├── Makefile
└── obj
└── Makefile3.1.2 main 目录
- ./main/main.c
#include "myinclude.h"
void printfun1();
void printfun2();
int main(int argc, const char *argv[])
{
printfun1();
printfun2();
printf("end main\n");
return 0;
}- ./main/Makefile
../$(OBJS_DIR)/main.o:main.c
$(CC) -c $^ -I ../$(H_DIR) -o $@3.1.3 fun1目录
- ./fun1/fun1.c
#include <stdio.h>
void printfun1()
{
printf("this is fun1!\n");
}- ./fun1/Makefile
../$(OBJS_DIR)/fun1.o:fun1.c
$(CC) -c $^ -o $@3.1.4 fun2 目录
- ./fun2/fun2.c
#include <stdio.h>
void printfun2()
{
printf("this is fun2!\n");
}- ./fun2/Makefile
../$(OBJS_DIR)/fun2.o:fun2.c
$(CC) -c $^ -o $@3.1.5 include 目录
- ./include/myinclude.h
#include <stdio.h>3.1.6 obj 目录
../$(BIN_DIR)/$(BIN):$(OBJS)
$(CC) -o $@ $^3.1.7 总控 Makefile
CC=gcc
SUBDIRS=fun1 \
fun2 \
main \
obj
OBJS=fun1.o fun2.o main.o
BIN=myapp
OBJS_DIR=obj
BIN_DIR=bin
H_DIR=include
export CC OBJS BIN OBJS_DIR BIN_DIR H_DIR
all:CHECK_DIR $(SUBDIRS)
CHECK_DIR:
mkdir -p $(BIN_DIR)
$(SUBDIRS):ECHO
make -C $@
ECHO:
@echo $(SUBDIRS)
@echo begin compile
.PHONY: clean clean-o
clean: clean-o
@rm -rf $(BIN_DIR)
clean-o:
@$(RM) $(OBJS_DIR)/*.o3.2 运行测试
然后执行 make ,会看到有以下输出信息:
mkdir -p bin
fun1 fun2 main obj
begin compile
make -C fun1
make[1]: 进入目录“/mnt/hgfs/sharedfiles/2Linux/test/LV02/05Makefile/maketest/fun1”
gcc -c fun1.c -o ../obj/fun1.o
make[1]: 离开目录“/mnt/hgfs/sharedfiles/2Linux/test/LV02/05Makefile/maketest/fun1”
make -C fun2
make[1]: 进入目录“/mnt/hgfs/sharedfiles/2Linux/test/LV02/05Makefile/maketest/fun2”
gcc -c fun2.c -o ../obj/fun2.o
make[1]: 离开目录“/mnt/hgfs/sharedfiles/2Linux/test/LV02/05Makefile/maketest/fun2”
make -C main
make[1]: 进入目录“/mnt/hgfs/sharedfiles/2Linux/test/LV02/05Makefile/maketest/main”
gcc -c main.c -I ../include -o ../obj/main.o
make[1]: 离开目录“/mnt/hgfs/sharedfiles/2Linux/test/LV02/05Makefile/maketest/main”
make -C obj
make[1]: 进入目录“/mnt/hgfs/sharedfiles/2Linux/test/LV02/05Makefile/maketest/obj”
gcc -o ../bin/myapp fun1.o fun2.o main.o
make[1]: 离开目录“/mnt/hgfs/sharedfiles/2Linux/test/LV02/05Makefile/maketest/obj”我们最终生成目标文件为 main ,并且,它将会被存放到 ./bin 目录下。