LV02-05-Makefile-06-文件包含和嵌套执行

本文主要是makefile——文件包含和嵌套执行相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
开发板 正点原子 i.MX6ULL Linux阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
STM32开发板 正点原子战舰V3(STM32F103ZET6)
点击查看本文参考资料
参考方向 参考原文
Makefile 跟我一起写Makefile
点击查看相关文件下载
--- ---
点击查看文件搜索相关测试文件

各文件所在目录的结构如下:

1
2
3
4
5
6
7
8
9
.
├── main.c
├── Makefile
├── test1.c
├── test1.h
├── test2.c
└── test2.h

0 directories, 6 files
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include "test1.h"
#include "test2.h"

int main(int argc, const char *argv[])
{
printf("This is main file!\n");
test1Fun();
test2Fun();
return 0;
}
1
2
3
4
5
6
#include <stdio.h>

void test1Fun()
{
printf("This is test1.c file\n");
}
1
2
3
4
5
6
#include <stdio.h>

void test2Fun()
{
printf("This is test2.c file\n");
}
1
2
3
4
5
6
#ifndef __TEST1_H__
#define __TEST1_H__

void test1Fun();

#endif
1
2
3
4
5
6
#ifndef __TEST2_H__
#define __TEST2_H__

void test2Fun();

#endif

一、文件包含

像 C 语言一样,在 Makefile 中使用 include 关键字可以把别的 Makefile 包含进来,不过这个功能到目前为止我还没有使用过,就先记录在这里吧,后边遇到了再补充笔记。

1.使用格式

使用格式如下:

1
include <filename>

【注意】

(1)在 include 前面可以有一些空字符,但是绝不能是 Tab 键开始。若要包含的多个文件,这些文件之间要使用空格分隔开。

(2)使用 include 包含进来的 Makefile 文件中,如果存在函数或者是变量的引用,它们会在包含的 Makefile 中展开。

(3)使用 include <filenames> , make 在处理程序的时候,文件列表中的任意一个文件不存在的时候或者是没有规则去创建这个文件的时候, make 程序将会提示错误并保存退出。

2.搜索路径

当 make 读取到 include 关键字的时候,会暂停读取当前的 Makefile ,而是去读 include 包含的文件,读取结束后再继读取当前的 Makefile 文件。如果文件都没有指定绝对路径或是相对路径的话, make 会在当前目录下首先寻找,如果当前目录下没有找到,那么, make 还会在下面的几个目录下找:

(1)如果 make 执行时,有 -I 或 –include-dir 参数,那么 make 就会在这个参数所指定 的目录下去寻找。

(2)如果目录 /include (一般是: /usr/local/bin 或 /usr/include )存在的话, make 也会在这些文件夹下寻找文件。

如果有文件没有找到的话, make 会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成 Makefile 的读取, make 会再重试这些没有找到,或是不能读取的文件。如果还是不行, make 才会出现一条致命信息。

3.忽略错误

如果想让 make 不理那些无法读取的文件,而继续执行,我们可以在 include 前加一个减号 - 。

1
-include <filename>

这就表示,无论过程中出现什么错误,都不要报错继续执行。和其它版本 make 兼容 的相关命令是 sinclude ,其作用相同。

二、 Makefile 嵌套执行

在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的 Makefile ,这有利于让我们的 Makefile 变得更加地简洁,而不至于把所有的东西全部写在一个 Makefile 中,这样不仅方便管理,而且可以迅速发现模块中的问题。

1.使用格式

我们有一个子目录叫 subdir ,这个目录下有个 Makefile 文件,来指明了这个目录下文件的编译规则。这个时候,在总的 Makefile 中嵌套使用的语法格式如下:

1
2
3
4
5
6
7
8
# 1.写法一
subsystem:
cd subdir && $(MAKE)

# 2.写法二
# CURDIR 此变量代表 make 的工作目录, -C 选项会使make命令进入指定目录下
subsystem
$(MAKE) -C subdir

当命令执行到上述的规则时,程序会进入到子目录中执行 make 。这就是嵌套执行 make ,我们把最外层的 Makefile 称为是总控 Makefile 。

2.变量传递

总控 Makefile 的变量可以传递到下级的 Makefile 中,但是不会覆盖下层的 Makefile 中所定义的变量,除非指定了 -e 参数。如果需要传递变量到下级 Makefile 中,那么我们可以使用这样的声明方式来声明变量:

1
export <variable ...>

如果这个变量不需要传递到下级,可以这样声明:

1
unexport <variable ...>

如果我们需要传递所有的变量到下一级,那么我们可以直接使用 export :

1
export

【注意】有两个变量,一个是 SHELL ,一个是 MAKEFLAGS ,这两个变量不管是否 export ,其总是要传递到下层 Makefile 中,特别是 MAKEFILES 变量,其中包含了 make 的参数信息。但是 make 命令中的有几个参数并不往下传递,它们是 -C 、 -f 、 -h 、 -o 和 -W 。如果我们不想往下层传递参数,可以这样做:

1
2
subsystem:
cd subdir && $(MAKE) MAKEFLAGS=

3.相关 make 参数

-w 或是 –print-directory 会在 make 的过程中输出一些信息,让我们看到目前的工作目录。比如,如果我们的下级 make 目录
是 /home/hk/gnu/make ,如果我们使用 make -w 来执行,那么当进入该目录时,我们会看到:

1
make: Entering directory  /home/hk/gnu/make'.

而在完成下层 make 后离开目录时,我们会看到:

1
make: Leaving directory  /home/hk/gnu/make'.

当我们使用 -C 参数来指定 make 下层 Makefile 时, -w 会被自动打开的。如果参数中有 -s ( –slient ) 或是 –no-print-directory ,那么, -w 总是失效的。

4.使用实例

首先来看一下文件结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.
├── fun1
│   ├── fun1.c
│   └── Makefile
├── fun2
│   ├── fun2.c
│   └── Makefile
├── include
│   └── myinclude.h
├── main
│   ├── main.c
│   └── Makefile
├── Makefile
└── obj
└── Makefile
点击查看相关文件内容

以下均为 . 下目录。

点击查看 ./main/main.c 文件内容
./main/main.c
1
2
3
4
5
6
7
8
9
10
11
12
#include "myinclude.h"
void printfun1();
void printfun2();

int main(int argc, const char *argv[])
{
printfun1();
printfun2();

printf("end main\n");
return 0;
}
点击查看 ./main/Makefile 文件内容
./main/Makefile
1
2
../$(OBJS_DIR)/main.o:main.c
$(CC) -c $^ -I ../$(H_DIR) -o $@
点击查看 ./fun1/fun1.c 文件内容
./fun1/fun1.c
1
2
3
4
5
6
#include <stdio.h>

void printfun1()
{
printf("this is fun1!\n");
}
点击查看 ./fun1/Makefile 文件内容
./fun1/Makefile
1
2
../$(OBJS_DIR)/fun1.o:fun1.c
$(CC) -c $^ -o $@
点击查看 ./fun2/fun2.c 文件内容
./fun1/fun1.c
1
2
3
4
5
6
#include <stdio.h>

void printfun2()
{
printf("this is fun2!\n");
}
点击查看 ./fun2/Makefile 文件内容
./fun2/Makefile
1
2
../$(OBJS_DIR)/fun2.o:fun2.c
$(CC) -c $^ -o $@
./include/myinclude.h
1
#include <stdio.h>
./obj/Makefile
1
2
../$(BIN_DIR)/$(BIN):$(OBJS)
$(CC) -o $@ $^
点击查看 总控 Makefile 文件内容
./Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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)/*.o

然后执行 make ,会看到有以下输出信息:

点击查看输出信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 目录下。