本次实验将介绍 makefile 中 wildcard
、VPATH
、vpath
、GPATH
、-lNAME
的使用方法及文件路径保存方法。
知识点
- 函数
wildcard
的使用 VPATH
和vpath
的使用- 文件路径的保存及
GPATH
的使用 -lNAME
文件的使用
结构图
本章实验涉及到的代码文件位于 /home/project/make_example-master/chapter5
目录下,请在 Terminal 中通过 cd 命令切换至该目录后再进行实验学习。
chapter5
目录结构如图:
.
├── gpath_code:用于测试 GPATH
│ ├── main
│ └── makefile
├── lib_code:用于测试 -lNAME 的使用方法
│ ├── lib
│ │ ├── foo_dynamic.c
│ │ └── foo_static.c
│ ├── main.c
│ └── makefile
├── vpath_code:用于测试 VPATH 和 vpath
│ ├── main.c
│ ├── makefile
│ └── vpath.mk
└── wild_code:用于测试 wildcard 函数
├── foo1.c
├── foo1.h
├── foo2.c
├── foo2.h
├── main.c
├── makefile
└── pat_make.mk
1️⃣ 函数 wildcard 的使用
前面章节介绍了文件通配符的使用,在规则中通配符会被自动展开,那么在这种情况下就需要使用 wildcard
函数代替通配符。
wildcard
函数的使用格式为:
$(wildcard PATTERN...)
在 makefile 中,它将被展开为
接下来对 wildcard
函数进行验证,通过 cd
命令在 Terminal 中打开到对应的文件夹。
cd /home/project/make_example-master/chapter5/wild_code
该文件夹中包含的文件如图:
├── foo1.c
├── foo1.h
├── foo2.c
├── foo2.h
├── main.c
|—— pat_make.mk
└── makefile
我们可以看到在 foo1.c
与 foo2.c
文件中分别定义了 foo1()
和 foo2()
函数,而在文件 main.c
文件中通过引入头文件 foo1.h
与 foo2.h
对这两个函数进行了调用。
main.c
文件的内容如下:
#include <stdio.h>
#include "foo1.h"
#include "foo2.h"
int main(void)
{
foo1();
foo2();
return 0;
}
现在我们查看当前目录下 makefile 文件的主要内容。
#this is a makefile for wildcard code test
.PHONY:all clean
code=$(wildcard *.c)
aim=wildtest
all:$(code)
@echo "objs inlude : " $(code)
$(CC) -o $(aim) $(code)
clean:
$(RM) $(aim)
使用“$(wildcard *.c)”
来获取工作目录下的所有的.c文件——就是符合.c的所有文件,wildcard匹配后面的.c模式
从内容上我们可以看到它的最终目标 all
依赖于当前目录下所有的 .c
文件。重建目标 all
时会打印依赖文件并使用 cc
将其链接为 wildtest
文件。
执行 make
命令并观察输出结果。
Terminal 的输出结果如图:
执行刚才生成的可执行文件 wildtest
。
./wildtest
Terminal 的输出结果如下图:
从输出上可以看到 wildtest
的输出符合预期效果。
patsubsb把.c
转换为 .o
文件
在实际的项目管理中,我们通常用 .o
文件作为依赖,而非 .c
文件,此时需要用到函数的嵌套调用。我们可以使用 $(patsubst SRC_PATTERN,DEST_PATTERN,FULL_STR)
来进行字符串替换,将 .c
文件替换为 .o
文件,代码内容如下:
objs=$(patsubst %.c,%.o,$(wildcard *.c))
$(patsubst pattern,replacement,text)
: 查找text
中以空格分隔的单词,并用replacement
去替代pattern
部分。
格式:$(patsubst pattern,replacement,text)
名称:模式字符串替换函数——patsubst。
功能:查找text中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式pattern,如果匹配的话,
则以replacement替换。
这里,pattern可以包括通配符“%”,表示任意长度的字串。
如果replacement中也包含“%”,那么,replacement中的这个“%”将是pattern中的那个“%”所代表的字串。
(可以用“\”来转义,以“%”来表示真实含义的“%”字符)
返回:函数返回被替换过后的字符串。
示例:
$(patsubst %.c,%.o, a.c b.c)
把字串“a.c b.c”符合模式[%.c]的单词替换成[%.o],返回结果是“a.o b.o”
通过上面的代码就可以将当前目录下的 .c
文件列表转换为 .o
文件列表
再利用 make
的隐含规则自动编译。提供的 代码文件 pat_make.mk
对上述理论进行了验证,pat_make.mk
的内容如下:
#this is a makefile for wildcard code test
.PHONY:all clean
objs=$(patsubst %.c,%.o,$(wildcard *.c))
aim=wildtest2
all:$(objs)
@echo "objs inlude : " $(objs)
$(CC) -o $(aim) $(objs)
clean:
$(RM) $(objs)
$(RM) $(aim)
执行 make
命令并观察输出结果。
make -f pat_make.mk
Terminal 的输出结果如下图:
可见 foo1.c
,main.c
,foo2.c
都已经被替换为对应的 .o
文件。
2️⃣ VPATH 变量和 vpath 关键字的使用
VPATH 的使用
VPATH
变量可以指定文件的搜索路径,若规则的依赖文件或目标文件在当前目录不存在时,make
会在此变量指定的目录下去寻找依赖文件。
VPATH
可以定义多个目录,目录间用「:」隔开,目录搜索顺序与 VPATH
中定义的顺序一致。
输入下面的命令通过 cd
命令进入到 vpath_code
目录中。
cd /home/project/make_example-master/chapter5/vpath_code
查看当前目录下的 main.c
文件,内容如下:
#include <stdio.h>
extern void foo1(void);
extern void foo2(void);
int main(void)
{
foo1();
foo2();
return 0;
}
与上一节实验中的 main.c
文件内容不同的地方是,上一节实验中 main.c
文件内容中是通过导入头文件 foo1.h
与 foo2.h
来对 foo1()
和 foo2()
函数的调用。而本小节则是通过 extern
关键字对函数进行声明并调用。这使得 main.c
本身不需要关注这两个函数的头文件放在什么位置,只要链接时的 .o
文件能够包含它们的实现即可。
foo1()
和 foo2()
的函数实现位于 chapter5/wild_code/
目录下,我们可以通过 VPATH
变量告知 makefile 它们的路径。 makefile 文件内容如下:
#this is a makefile for VPATH test
.PHONY:all clean
depen=main.o foo1.o foo2.o
aim=main
all:$(depen)
@echo "objs inlude : " $(depen)
$(CC) -o $(aim) $(depen)
clean:
$(RM) $(depen)
$(RM) $(aim)
在 makefile
中它只是单纯指定了三个依赖项分别为 main.o
,foo1.o
和 foo2.o
,并在重建 all
目标时将三者链接为 main
文件。
现在执行 make
命令, Terminal 的输出结果如下图:
发现 make
没有找到目标文件 foo1.o
,并且当前路径下也没有 foo1.c
文件,无法依靠隐含规则自动重建 foo1.o
,因此 make
报错并退出执行。
现在我们执行下面的命令,设定 VPATH
为 ../wild_code/
并传给 make
:
make VPATH=../wild_code/
Terminal 的输出结果如下图:
发现输出错误,原因是我们在进行完了上一小节实验后,没有对产生的 .o
文件进行清理。
执行下面的命令,重新观察输出结果。
rm ../wild_code/*.o
make VPATH=../wild_code
Terminal 的输出结果如下图:
说明 makefile 得到了正确的执行。
vpath 关键字的使用
vpath
关键字的作用与 VPATH
变量相似,可以指定依赖文件或目标文件的目录。 但 vpath
的用法更加灵活,下面是vpath
的三种指令形式:
vpath PATTERN DIR
:为匹配PATTERN
模式的文件指定搜索目录。vpath PATTERN
:清除匹配PATTERN
模式的文件设置的搜索目录。vpath
:清除全部搜索目录。
查看当前目录下的 vpath.mk
文件,与 makefile
文件的不同之处在与,在 vpath.mk
的开头多了一行下面的内容。
vpath %.c ../wild_code/
现在执行下面的命令,并观察输出结果。
make clean
make -f vpath.mk
Terminal 的输出结果如下图:
可知 vpath.mk
的执行效果与使用 VPATH
一致。
3️⃣ 文件路径的保存及 GPATH 的使用
如前面实验所展示,有时候某些依赖文件或目标文件需要搜索 VPATH
或 vpath
指定目录才能得到。因此后续的流程中需要决定目录搜索得到的完整路径是要保留还是废弃。
make
在解析 makefile 文件时对文件路径的保存/废弃算法如下:
- 在当前目录查找文件,若不存在则搜索指定路径。
- 若目录搜索成功则将完整路径作为临时文件名保存。
- 依赖文件直接使用完整路径名。
- 目标文件若不需要重建则使用完整路径名,否则完整路径名被废弃。
其中比较难理解的是第四点,简单来说意思就是目标文件会在当前路径被进行重建。
下面进行规则验证,先清除掉上次实验的结果并切换目录:
make -f vpath.mk clean; cd ../gpath_code/
在当前目录下只有一份 makefile 文件,文件的主要内容如下:
#this is a makefile for gpath test
.PHONY:all clean
vpath %.c ../wild_code/
depen=main.o foo1.o foo2.o
aim=main
all:$(depen)
@echo "objs inlude : " $(depen)
$(CC) -o $(aim) $^
#$^:所有的依赖文件
clean:
$(RM) $(depen)
$(RM) $(aim)
相比之前的 makefile 此处同时指定了 .c
和 .o
文件的搜索路径。 此外,还在重建 all
目标时使用自动化变量 $^
代替 $(depen)
,因为 $^
变量会将指定的目标文件展开为完整路径名。
但此次所有的 .c
文件都在 ../wild_code/
目录下,根据文件路径的保存规则,其对应的 .o
文件要在当前路径下生成。 执行 make
并观察当前文件夹中内容的变化。
make
ls
Terminal 的输出结果如下图:
GPATH变量使用
如果不希望在当前目录下生成目标文件,可以使用 GPATH
变量。 若目标文件与 GPATH
变量指定目录相匹配,其完整路径名不会被废弃,此时目标文件会在搜寻到的目录中被重建。
为了测试 GPATH
变量的效果,我们先清除掉上一次测试产生的文件,并切换到 ../wild_code/
目录编译得到对应的 .o
文件:
make clean;ls;cd ../wild_code;
cc -c foo1.c;touch foo1.c
cc -c foo2.c;touch foo2.c
cc -c main.c;touch main.c
注意:touch
在这里用来修改 foo1.c
、foo2.c
与 main.c
文件的时间戳。
现在 wild_code
目录下已经存在 foo1.o
、foo2.o
、main.o
文件了。 现在切回 gpath_code
目录并在执行 make
时使用 GPATH
变量。
cd ../gpath_code;make GPATH=../wild_code;ls
Terminal 的输出结果如下图:
从输出结果中可以看到 .o
文件还是在当前目录下生成,说明结果并不符合预期。但是观察 makefile 文件,发现下面这一行代码,并没有指定 .o
文件所在的路径。
vpath %.c ../wild_code/
将该行代码修改成下面的内容:
vpath %.o ../wild_code/
清除掉上次的执行结果,再执行一次:
touch ../wild_code/main.c
touch ../wild_code/foo1.c
touch ../wild_code/foo2.c
make clean;ls;make GPATH=../wild_code/;ls
Terminal 的输出结果如下图:
可见这一次只有 main
文件在当前路径下生成,其余 .o
文件都在 ../wild_code/
中被重建。
4️⃣ -lNAME 文件的使用
makefile 中可以使用
-lNAME
来链接共享库和静态库。文件列表中的-lNMAE
将被解析为名为libNAME.so
或libNAME.a
文件。
-lNAME
的搜索
make
搜索 -lNAME
的过程如下:
- 在当前目录搜索名为
libNAME.so
的文件。- 若不存在则搜索
VPATH
或vpath
定义的路径。- 若仍然不存在,
make
将搜索系统默认目录,顺序为/lib
,/usr/lib, /usr/local/lib
。- 若依然无法找到文件,
make
将按照以上顺序查找名为libNAME.a
的文件。
注意:-lNAME
中的 NAME
是指动态库或者静态库去掉文件格式 .o
或 .so
和 名字开头的 lib
剩下的那部分,例如 libfoo.so
则 NAME
就是 foo
,-lNAME
此时就应该是 -lfoo
。
接下来对 -LNAME
的搜索过程进行验证,验证的步骤如下:
- 编写同名的动态库文件和静态库文件,使用相同的 api 内部打印不同信息。
- 编写
main
文件调用库文件api
。- 编译库文件生成静态库和动态库。
- makefile 中使用
-lNAME
依赖项进行链接,验证使用的哪个库文件。- 删除之前链接到的库文件再次执行
make
确认另一个库文件能否被成功链接。
在 chapter5/lib_code/
文件夹中已经提供了用来进行验证的编写好的代码,该文件夹的结构如下:
├── lib
│ ├── foo_dynamic.c
│ └── foo_static.c
├── main.c
└── makefile
lib/
下有两个 .c
文件 foo_dynamic.c
和 foo_static.c
,定义了同一个函数 foo()
,分别返回 1 和 2,这两份代码会被分别用于生成动态库和静态库文件。 主目录下的 main.c
调用 foo()
函数并打印得到的结果。
makefile 中提供了生成库文件和链接 main.o
的方法,内容如下:
#this is a makefile for -lNAME test
.PHONY: all clean static_lib dynamic_lib
VPATH=lib/
all: main.o -lfoo
$(CC) -o main $^
static_lib: foo_static.o
$(AR) rc libfoo.a $^;\
mv libfoo.a lib/
dynamic_lib: foo_dynamic.o
$(CC) $^ -fPIC -shared -o libfoo.so;\
mv libfoo.so lib/
clean:
$(RM) *.o *.a *.so main
$(RM) lib/*.a lib/*.soso
动态库和静态库的链接,我们在 chapter0
已经测试过了,现在输入下面的命令先分别生成动态库和静态库文件。
cd ../lib_code
make static_lib;make dynamic_lib;ls lib/
Terminal 的输出结果如下图:
再执行 make
观察最终目标链接的是哪个库文件。
make;./main
Terminal 的输出结果如下图:
可见 -lNAME
优先被解析为
现删除动态库再次编译执行。
rm lib/libfoo.so;make;./main
Terminal 的输出结果如下图:
可见动态库文件不存在时,make
会尝试查找和链接静态库文件。
-lNAME
的展开是由变量.LIBPATTERNS
来指定的,其值默认为lib%.so lib%.a
。 感兴趣的同学可以自己尝试打印和修改此变量。
本章节测试了wildcard
,VPATH
,vpath
,GPATH
,-lNAME
的使用方法及文件路径保存算法。