LV02-05-Makefile-05-文件搜索

本文主要是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

一、源文件搜索

一般来说,一个大的工程下有大量的源文件,我们通常会把源文件分类,并存放在不同的目录中,便于查找和区分。所以,当 make 需要去找寻文件的依赖关系时,我们可以把一个路径告诉 make ,让 make 自动去找。

常见的搜索的方法的主要有两种:一般搜索 VPATH 和选择搜索 vpath 。表面上两者只是大小写的区别,其实两者在本质上也是不同的。

  • VPATH 是变量,更具体的说是环境变量, Makefile 中的一种特殊变量,使用时需要指定文件的路径;

  • vpath 是关键字,按照模式搜索,也可以说成是选择搜索。搜索的时候不仅需要加上文件的路径,还需要加上相应限制的条件。

【注意】 VPATH 与 vpath 仅仅是对于 Makefile 来说搜索目标和依赖文件的路径,但是对于命令行来说是无效的,也就是说,在执行 g++ 或者 gcc 时不会自动从 VPATH 或者 vpath 中自动搜索要包含的头文件等信息文件,这个时候就需要在命令行的命令中添加 -I 参数,以便于 gcc 等编译器寻找头文件。

1. VPATH

1.1使用格式

VPATH 变量会告诉 make 去哪里寻找相应的文件,如果没有指明这个变量, make 只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么, make 就会在当前目录找不到的时候,到所指定的目录中去找寻文件了。一般使用格式如下:

1
2
3
4
5
6
7
# 1.单个路径写法
VPATH := src

# 2.多个路径写法
VPATH := src1 ../src2
# 或者
VPATH := src1 : ../src2

上边多个路径的写法指明了两个目录, src1 和 ../src2 。

【注意】

(1)当前目录永远是最高优先搜索的地方。如果当前目录下有我们要使用的文件,那么 make 就会使用我们当前目录下的文件。

(2)多个路径之间要使用空格或者是冒号隔开,表示在多个路径下搜索文件,搜索的顺序为我们书写时的顺序。

1.2使用实例

我们使用上边的文件搜索相关测试文件来测试,我们新建两个目录,并将两个相关文件移动到指定目录:

1
2
3
mkdir test1 test2
mv test1.c test1
mv test2.c test2

所以现在的目录结构为:

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

2 directories, 6 files
  • 不使用 VPATH 的 Makefile
1
2
3
4
5
6
7
8
9
10
11
OBJ := main.o test1.o test2.o

main: $(OBJ)
gcc $(OBJ) -o main

%.o: %.c
gcc -c $< -o $@

.PHONY: clean
clean:
rm -rf *.o main

此时不出意外的话,我们会得到一条这样的报错信息:

1
2
gcc -c main.c -o main.o
make: *** No rule to make target 'test1.o', needed by 'main'。 停止。
  • 使用 VPATH 的 Makefile
1
2
3
4
5
6
7
8
9
10
11
OBJ := main.o test1.o test2.o
VPATH := test1 test2
main: $(OBJ)
gcc $(OBJ) -o main

%.o: %.c
gcc -c $< -o $@

.PHONY: clean
clean:
rm -rf *.o main

此时我们再执行 make 命令,会得到如下的信息提示:

1
2
3
gcc -c test1/test1.c -o test1.o
gcc -c test2/test2.c -o test2.o
gcc main.o test1.o test2.o -o main

可以发现,我们正常编译了所有文件,总的来说当 .c 源文件都位于 test1 和 test2 目录下的时候, VPATH 就成了必要了。

2. vpath

2.1使用格式

vpath 关键字它可以指定不同的文件在不同的搜索目录中。它有三种使用方法:

2.1.1设置搜索路径

1
2
3
4
5
6
7
# 1.单个路径
vpath <pattern> <dir>

# 2.多个路径
vpath <pattern> <dir1> <dir2>
# 或者
vpath <pattern> <dir1> : <dir2>

为符合模式 的文件指定搜索目录

。例如,

1
vpath test.c src # 在 src 路径下搜索文件 test.c

【说明】

(1) vapth 使用方法中的可以包含 % 字符。 % 的意思是匹配零或若干字符。例如,

1
vpath %.h ../headers

该语句表示,要求 make 在 ../headers 目录下搜索所有以 .h 结尾的文件。(如果某文件在当前目录没有找到的话)。

(2)可以连续地使用 vpath 语句,以指定不同搜索策略。如果连续的 vpath 语句中出现了相同的 <pattern> ,或是被重复了的 <pattern> ,那么, make 会按照 vpath 语句的先后顺序来执行搜索。例如,

1
2
3
vpath %.c foo
vpath % blish
vpath %.c bar

表示搜索 .c 结尾的文件,先在 foo 目录,然后是 blish ,最后是 bar 目录搜索 .c 结尾的文件。

(3)还有一种搜索的形式

1
2
vpath %.c foo:bar
vpath % blish

表示搜索 .c 结尾的文件,先在 foo 目录,然后是 bar 目录,最后才是 blish 目录。

2.1.2清除搜索目录

  • 格式一
1
vpath <pattern>

清除符合模式 的文件的搜索目录。例如,

1
vpath test.c # 清除符合文件 test.c 的搜索目录。
  • 格式二
1
vpath

清除所有已被设置好了的文件搜索目录。

2.2使用实例

我们使用上边的文件搜索相关测试文件来测试,我们新建两个目录,并将两个相关文件移动到指定目录:

1
2
3
mkdir test1 test2
mv test1.c test1
mv test2.c test2

所以现在的目录结构为:

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

2 directories, 6 files

我们编写 Makefile 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
OBJ = main.o test1.o test2.o
vpath %.c test1 test2
# 可执行程序 main
main: $(OBJ)
gcc $(OBJ) -o main

%.o: %.c
gcc -c $< -o $@

.PHONY: clean
clean:
rm -rf *.o main

这样,在进行编译的时候 make 就会到设置的路径下寻找源文件。

二、头文件搜索

1.使用格式

我们并不会把所有的头文件都跟 Makefile 文件放在一个目录下,当我们的头文件和调用这个头文件的源文件以及 Makefile 不在同一个目录怎么办呢,他们有可能分布在不同的好几个目录,这个时候我们设置的 VPATH 或者是 vpath 是无法查找 .h 文件的,这时候我们就要靠 gcc 的 -I 参数啦。一般格式如下:

1
gcc filename.c -o filename -I h_dir1 -I h_dir2 -I h_dir3 ... 
  • h_dir 就表示相关头文件路径,可以跟多个,用空格分开,但是每个目录都要有 -I 这个参数。

2.使用实例

我们使用上边的文件搜索相关测试文件来测试,我们新建两个目录,并将两个相关文件移动到指定目录:

1
2
3
4
mkdir test1 test2 user
mv test1.c test1.h test1
mv test2.c test2.h test2
mv main.c user

所以现在的目录结构为:

1
2
3
4
5
6
7
8
9
10
11
12
.
├── Makefile
├── test1
│ ├── test1.c
│ └── test1.h
├── test2
│ ├── test2.c
│ └── test2.h
└── user
└── main.c

3 directories, 6 files

我们编写 Makefile 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
OBJ     := main.o test1.o test2.o

INCLUDE := -I test1 -I test2

vpath %.c user test1 test2

# 可执行程序 main
main: $(OBJ)
gcc $(OBJ) -o main $(INCLUDE)

%.o: %.c
gcc -c $< -o $@ $(INCLUDE)

.PHONY: clean
clean:
rm -rf *.o main

这样,在进行编译的时候 make 就会到设置的路径下寻找源文件,编译的时候也会到指定目录下寻找头文件,我们执行 make 命令的话,会有如下信息提示:

1
2
3
4
gcc -c user/main.c -o main.o -I test1 -I test2
gcc -c test1/test1.c -o test1.o -I test1 -I test2
gcc -c test2/test2.c -o test2.o -I test1 -I test2
gcc main.o test1.o test2.o -o main -I test1 -I test2

三、生成文件路径

1.指定生成文件路径

我们每次生成的一大堆的文件都是存放在当前目录下的,文件多了之后,就会看起来很乱,我们能否指定生成文件路径呢?当然是可以的啦。这里直接以实例来说明。

1.1文件准备

首先我们新建一个 obj 文件夹用于存放生成的 .o 中间文件。

1
mkdir obj

我们使用上边的文件搜索相关测试文件来测试,我们新建两个目录,并将两个相关文件移动到指定目录:

1
2
3
4
mkdir test1 test2 user
mv test1.c test1.h test1
mv test2.c test2.h test2
mv main.c user

所以现在的目录结构为:

1
2
3
4
5
6
7
8
9
10
11
12
13
.
├── Makefile
├── obj
├── test1
│ ├── test1.c
│ └── test1.h
├── test2
│ ├── test2.c
│ └── test2.h
└── user
└── main.c

4 directories, 6 files

1.2两次试错

1.2.1测试1

我们编写 Makefile 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
OBJ     := main.o  test1.o  test2.o

INCLUDE := -I test1 -I test2
OBJDIR := ./obj
vpath %.c user test1 test2

# 可执行程序 main
main: $(OBJ)
gcc $(OBJ) -o main $(INCLUDE)

$(OBJ):$(OBJDIR)/%.o: %.c
gcc -c $< -o $@ $(INCLUDE)

print:
@echo "OBJ = $(OBJ)"
.PHONY: clean
clean:
rm -rf $(OBJDIR)/*.o main

然后我们执行 make 命令,会发现报了下边的错误:

1
2
3
4
5
6
7
8
Makefile:11: target 'main.o' doesn't match the target pattern
Makefile:11: target 'test1.o' doesn't match the target pattern
Makefile:11: target 'test2.o' doesn't match the target pattern
gcc -c -o main.o -I test1 -I test2
gcc: fatal error: no input files
compilation terminated.
Makefile:12: recipe for target 'main.o' failed
make: *** [main.o] Error 1

我们看报错的说明,就是生成 main 的时候,找不到它所依赖的 main.o 、 test1.o 和 test2.o ,并且在模式规则的匹配中,匹配也会有问题,我们来分析一下,我们拆开上边的模式规则,并将变量进行替换,可以得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
OBJ     := main.o  test1.o  test2.o

INCLUDE := -I test1 -I test2
OBJDIR := ./obj
vpath %.c user test1 test2

# 可执行程序 main
main: main.o test1.o test2.o
gcc main.o test1.o test2.o -o main $(INCLUDE)

main.o:./obj/%.o:%.c
gcc -c $< -o $@ $(INCLUDE)

test1.o:./obj/%.o:%.c
gcc -c $< -o $@ $(INCLUDE)

test2.o:./obj/%.o:%.c
gcc -c $< -o $@ $(INCLUDE)

.PHONY: clean
clean:
rm -rf $(OBJDIR)/*.o main

我们会发现, % 匹配的时候,前边的目标文件格式与后边匹配的不太一致,并且我们生成的 .o 文件是存放在 obj 目录下的, gcc 编译的时候是找不到这几个文件的。

1.2.2测试2

我们可以修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
OBJ     := main.o  test1.o  test2.o

INCLUDE := -I test1 -I test2
OBJDIR := ./obj
vpath %.c user test1 test2

# 可执行程序 main
main: ./obj/main.o ./obj/test1.o ./obj/test2.o
gcc ./obj/main.o ./obj/test1.o ./obj/test2.o -o main $(INCLUDE)

./obj/main.o:./obj/%.o:%.c
gcc -c $< -o $@ $(INCLUDE)

./obj/test1.o:./obj/%.o:%.c
gcc -c $< -o $@ $(INCLUDE)

./obj/test2.o:./obj/%.o:%.c
gcc -c $< -o $@ $(INCLUDE)


.PHONY: clean
clean:
rm -rf $(OBJDIR)/*.o main

然后我们再执行 make 命令,会发现有如下提示:

1
2
3
4
gcc -c user/main.c -o obj/main.o -I test1 -I test2
gcc -c test1/test1.c -o obj/test1.o -I test1 -I test2
gcc -c test2/test2.c -o obj/test2.o -I test1 -I test2
gcc ./obj/main.o ./obj/test1.o ./obj/test2.o -o main -I test1 -I test2

此时文件结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.
├── main
├── Makefile
├── obj
│ ├── main.o
│ ├── test1.o
│ └── test2.o
├── test1
│ ├── test1.c
│ └── test1.h
├── test2
│ ├── test2.c
│ └── test2.h
└── user
└── main.c

发现,生成的中间文件全部进入了 obj 目录。

1.3正确的格式

经过前边的试错,我们可以将 Makefile 写成如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
INCLUDE := -I test1 -I test2
OBJDIR := ./obj
OBJ := $(OBJDIR)/main.o $(OBJDIR)/test1.o $(OBJDIR)/test2.o
TARGET := main
vpath %.c user test1 test2

# 可执行程序 main
$(TARGET): $(OBJ)
gcc $(OBJ) -o $@ $(INCLUDE)

$(OBJ):$(OBJDIR)/%.o:%.c
gcc -c $< -o $@ $(INCLUDE)


.PHONY: clean
clean:
rm -rf $(OBJDIR)/*.o main

这样的 Makefile 就简洁了很多了。

2.最终目标路径

上边我们已经把中间文件的路径设置过了,那还有最终生成的目标文件的,但其实也没什么必要,毕竟最终生成的文件只有一个,也不会很乱,不过这里还是记录一下吧,我们只需要在写目标文件的时候加上路径就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
INCLUDE := -I test1 -I test2
OBJDIR := ./obj
OBJ := $(OBJDIR)/main.o $(OBJDIR)/test1.o $(OBJDIR)/test2.o
TARGET := ./bin/main
vpath %.c user test1 test2

# 可执行程序 main
$(TARGET): $(OBJ)
gcc $(OBJ) -o $@ $(INCLUDE)

$(OBJ):$(OBJDIR)/%.o:%.c
gcc -c $< -o $@ $(INCLUDE)


.PHONY: clean
clean:
rm -rf $(OBJDIR)/*.o main

这样我们最终生成目标文件为 main ,并且,它将会被存放到 ./bin 目录下。