系统:Windows11 编程环境:VMware Workstation 17 pro 镜像:Ubuntu20.04
1、提出问题
在实际工程中所有的.c源文件与.h头文件肯定都不会放在同一个目录下,所以当我将源文件分成几个目录存放时,我该怎么样才能够使用make一次性完成整个工程的编译呢?
2、解决方案设想
最近在学习makefile的文件搜索环境变量VPATH与关键字vpath时发现可以使用这两个玩意解决这个工程管理的问题。将c源文件放入目录src中,头文件放进目录inc中以及编译得出的中间文件.o放入obj目录中。最后使用VPATH与vpath的文件搜索方法进行makefile的编写,如此一来make一键编译工程岂不美哉?
3、实施方案
我将之前的print工程分成三个目录,而目录树如下图所示:
可以看见我把之前的三个文件main.c、print.c、print.h进行归类,.c文件放在目录src中,.h文件放在inc目录中,多出的obj目录用于存放编译生成的中间文件main.o与print.o。
接下来就是今天的主角VPATH与vpath登场了,在使用他们之前需要熟悉以下他们的格式:
VPATH = dir1 dir2 or VPATH = dir1 : dir2
vpath <pattern> <directories>
为符合模式<pattern>的文件指定搜索目录。
vpath <pattern>
清除符合模式<pattern>的文件的搜索目录。
vpath
清除所有已被设置好了的文件搜索目录。
对于VPATH:告诉make去dir1中寻找编译需要的文件,dir1找不到就到dir2里去找,按顺序从左到右进行文件搜索。
对于vpath:<pattern>即为一个条件且需要配合%来使用,%为匹配0或若干个字符的意思,<directories>为搜索目录,意思为告诉make到指定的目录中符合条件的文件集合中寻找编译需要的文件。相对于VPATH,vpath更具灵活性。
VPATH = src inc or VPATH = src : inc
vpath = %.h inc
vpath = %.c src
上面代码里相当于VPATH告诉make命令接下来要去src与inc目录寻找待会编译工程要用到的各种文件,或者是vpath告诉make命令接下来去inc目录里所有.h文件中寻找需要的文件以及去src目录下所有的.c文件中寻找需要的文件,所以我把makefile编写成这个样子:
target = abc
dependent = main.o print.o
VPATH = src inc
#vpath = %.c src
#vpath = %.h inc
$(target) : $(dependent)
cc -o $(target) $(dependent)
main.o : print.h (隐式规则,make在看到main.o后会自动在后面添加main.c,所以可以省去main.c)
cc -c main.c
print.o : (跟上面一样的隐式规则)
cc -c print.c
clean :
rm $(dependent)
加上了VPATH,为了让程序看起来高大上,我用到了变量,其中target与dependent就是变量,引用他们的话直接用$()来包围他们就好了,就像这样$(target)。
使用上面的代码后程序竟然报错了
看起来是cc -c main.c的那行出错了,不应该啊,我不是用VPATH告诉它该去哪找文件了吗?
查了一轮资料后发现make没有问题,有问题的是cc这个命令,也就是cc -c main.c这个命令是有问题的,我们用VPATH时只告诉了make该去哪找目标文件,但是没告诉编译命令cc该去哪找源文件,所以改进代码
main.o : print.h (隐式规则,make在看到main.o后会自动在后面添加main.c,所以可以省去main.c)
cc -c src/main.c
print.o : (跟上面一样的隐式规则)
cc -c src/print.c
第二个错误又来了...
这是因为我们还没有告诉cc命令该去哪找头文件,cc不会自己进去到inc目录下找到print.h,所以我们还得加上一行-I ./inc到cc -c src/main.c的屁股后面
cc -c src/main.c -I ./inc
修改完之后工程一键编译成功
看一看编译后的目录树
饱满又充实,运行最终生成的文件
非常之成功!
4、最终优化
虽然成功了,但是两个编译的中间产物main.o与print.o并不在obj目录下,与我设想结果相比不是很完美,所以还要在这个cc命令上动点手脚,让他们把.o文件输出到obj里
main.o : print.h
cc -c src/main.c -I ./inc -o ./obj/main.o
print.o :
cc -c src/print.c -o ./obj/print.o
编译一遍,第三次出错
原来是把.o文件放到obj后cc -o abc main.o print.o在本目录下找不到这两个.o文件了,所以再做手脚,因为main.o print.o使用的是$(dependent)来表达的,所以直接在dependent变量下修改的话会出现一系列的连带错误。目前为止的源代码如下
target = abc
dependent = main.o print.o
VPATH = src inc
#vpath %.h inc
#vpath %.c src
$(target) : $(dependent)
cc -o $(target) $(dependent)
main.o : print.h
cc -c src/main.c -I ./inc -o ./obj/main.o
print.o :
cc -c src/print.c -o ./obj/print.o
clean :
rm $(dependent)
修改dependent = ./obj/main.o ./obj/print.o之后第一个$(dependent)肯定会出错,因为makefile里没有./obj/main.o与./obj/print.o这两个规则,只有main.o与print.o,所以我做出最后的优化,将变量分为文件层与目录层以及cc命令层,最后再按需添加使用
PWD = $(shell pwd)/
target = abc
dependent = main.o print.o
object = $(OBJ)main.o $(OBJ)print.o
INC = $(PWD)inc
SRC = $(PWD)src/
OBJ = $(PWD)obj/
#VPATH = src inc
vpath %.h inc
vpath %.c src
CFLAGS = -I $(INC)
OFLAHS = -o $(OBJ)
$(target) : $(dependent)
cc -o $(target) $(object)
main.o : print.h
cc -c $(SRC)main.c $(CFLAGS) $(OFLAHS)main.o
print.o :
cc -c $(SRC)print.c $(OFLAHS)print.o
.PHONY : cleanall cleantar cleanobj
cleanall : cleanobj cleantar
cleantar :
rm $(target)
cleanobj :
rm $(object)
make之后tree一下再运行程序看看结果如何
5、总结
至此达成最终目标,虽说是学习VPATH与vpath的用法,但是最多的还是编译命令的文件搜寻问题以及变量的优化问题,所以从makefile的文件搜索引出新问题并学习新知识并以此解决问题还是非常有成就感的。
文章参考了陈皓大牛的《跟我一起写Makefile》