一般情况下,我们用makefile编译一个程序会把所需的源文件,头文件,功能函数文件放置于同一目录下,这样有利于观察各文件的实时变化,但是有时我们也会遇到编译程序的必要文件在不同目录下的情况,这时我们又该如何优雅的去解决呢?一个简单的多文件目录如下图所示:
编译逻辑如下:
一、手写路径
app : hello.o main.o
cc -o app hello.o main.o
hello.o : ./tmp2/hello.c
cc -c ./tmp2/hello.c -o hello.o
main.o : ./tmp1/main.c ./tmp1/hello.h
cc -c ./tmp1/main.c -o main.o
clean :
rm -f *.o app
运行结果如下
这样一个个手写路径虽然逻辑清晰也能成功编译,但对于一些工程很大的程序来说便会显得笨重又耗时耗力了,那有没有更简便一些的方法呢?
二、循环遍历
#定义搜索路径
VPATH = ./tmp1 ./tmp2
#定义源文件,利用for循环遍历每个路径,再用wildcard函数提取每个路径的.c文件
SRCS = $(foreach dirs, $(VPATH), $(wildcard $(dirs)/*.c))
#定义目标文件,利用patsubst进行.c到.o文件的模式替换(仅储存替换名字)
OBJS = $(patsubst %c, %o, $(SRCS))
#定义目标可执行文件app
TARGET = app
# 链接目标
$(TARGET): $(OBJS)
cc -o $@ $^
#定义模式规则
%.o: %.c
cc -c $^ -o $@
# 清理编译生成的文件
clean :
rm -f $(OBJS) $(TARGET)
运行如下:
路径如下:
这次我用for循环实现了对tmp1与tmp2路径下源文件的自动搜索,并定义了各种变量来储存这些列表,这样即使工程量增大也只需更改路径就行。这里值得一提的点便是makefile的隐式规则,由于makefile一开始便是为c语言构建的,所以makefile内置有从.c到.o文件的编译规则(同名查找),即使我们把上述的定义模式规则板块去掉也不会影响我们的编译
三、调用shell的find指令
makefile也支持调用shell指令,对于上面的makefile代码,也是只需要改一句就可以了,但为了整体的完整与规范,我还是多加了一些东西
# 定义编译器
CC=gcc
# 定义编译选项
CFLAGS=-Wall -I./include
# 定义链接选项
LDFLAGS=-L./lib
# 定义目标文件的搜索路径
VPATH =./tmp1 ./tmp2
# 定义所有源文件
#SRCS=$(foreach dirs, $(VPATH), $(wildcard $(dirs)/*.c))
SRCS := $(shell find $(VPATH) -name '*.c')
# 从源文件生成对象文件
OBJS=$(patsubst %.c,%.o,$(SRCS))
# 定义最终的可执行文件
TARGET=app
# 默认目标
all: $(TARGET)
# 链接目标
$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^
# 模式规则:从 .c 文件生成 .o 文件
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 清理编译生成的文件
clean:
rm -f $(OBJS) $(TARGET)
# 伪目标,确保 make 按顺序执行
.PHONY: all clean
运行的结果自然也没问题
这种规范写法的好处在于用变量替换的大量的编译配置同时也方便我们进行替换,比如CC=gcc编译器可以替换为交叉编译链CC=mips-linux-uclibc-gnu-gcc等,最后伪目标的建立也可以防止同名文件clean的干扰。
四、总结
本篇主要讲了利用makefile实现多目录编译的三种方式,属于较为基础的部分,其实我本来还想写 自动生成依赖 部分的,奈何我实力有限,自己也一知半解,有兴趣可以去试一下。对于刚接触makefile的新手来说,我这边推荐一个写的非常好的教程网站,这是真大佬https://liaoxuefeng.com/books/makefile/introduction/index.html
其中 自动生成依赖 部分也是从他这儿看到的,大家去他那边学的话应该能收获更多。