菜鸟第一次接触Makefile,直接上,基本把所有的坑都踩了一遍,写下来纪念一下。
首先说一下我们的项目,自己写的主程序在main.c文件中,此外主程序需要调用某个客户端程序的功能,因此还需要把该客户端的源码包含进去(本来是将客户端编译成库直接调用的,无奈客户端的功能满足不了我们的需求,还得改客户端程序),客户端程序代码文件太多,很杂乱,为了和主函数区分,所以将客户端的所有.c文件放在./src文件夹中,把对应的.h文件放在./include文件夹中,将所有输出的.o文件(包含main.o)放在./obj文件夹中,主函数main.c仍放在项目主目录下,大概文件结构如下图所示:
1.接下来的所有操作都在main目录下进行,为了以后更改方便,所以把所有目录都定义成变量
SRC=./src
OBJ=./obj
DIR=.
INC=./include
2.然后进行源文件的展开和对应目标文件的替换(表达可能不准确,但这是我自己理解的意思),这里真的好多小细节(大坑)
SOURCE := $(wildcard $(SRC)/*.c)
OBJS := $(patsubst %.c,$(OBJ)/%.o,$(notdir $(SOURCE)))
MAIN_SOURCE := $(wildcard $(DIR)/*.c)
MAIN_OBJS := $(patsubst %.c,$(OBJ)/%.o,$(notdir $(MAIN_SOURCE)))
首先看第一句,SOURCE := $(wildcard $(SRC)/*.c) ,此句的执行结果是将src文件夹中的所有c文件列举出来,比如./src/a.c、./src/b.c等等,中间自动用空格分开,echo $(SOURCE),应该输出./src/a.c ./src/b.c等等。
变量名为SOURCE,$var代表引用名为var的变量,wildcard意为扩展通配符(普通的通配符为%,但是在包含文件路径时%不好用,因为%只能匹配文件名),用法为$(wildcard PATTERN...),将符合PATTERN格式的所有文件列举出来,并以空格分开,此句中的PATTERN为$(SRC)/.*c,意为./src文件夹中所有.c文件,*代表任意文件名。
第二句为可以生成的目标文件,OBJS := $(patsubst %.c,$(OBJ)/%.o,$(notdir $(SOURCE))) ,这句话的执行结果是将所有.c文件替换成.o文件,并且放在obj目录下,即echo $(OBJS),应该输出./obj/a.o ./obj/b.o 等等,但是这句话并不真的生成.o文件,只是替换而已。
patsubst函数以为替换通配符,用法为$(patsubst <pattern>,<replacement>,<text> ),将符合text文本中的.c文件,都替换成replacement。在此句中即是将SOURCE中的所有.c文件都替换到obj目录下的.o文件。在这里,注意pattern位置的.c不要带任何文件路径,就只用通配符%即可,之前看别的教程带有目录,我测试了一下并不对。。。去掉就好了;text中的notdir一定要加,因为前面用的是通配符%,不去掉文件目录会报错的,echo $(notdir $(SOURCE)),输出的就是 a.c b.c等等,将前面的./src/去掉了。
第三四句和第一二句类似。
3.接下来定义一下编译时的参数
CC := gcc
LIBS := -lpthread -lconfig
LDFLAGS :=
DEFINES :=
INCLUDE := -I $(INC)
CFLAGS := -g -Wall -O3 -fPIC $(DEFINES) $(INCLUDE) -DHAVE_CONFIG_H
TARGET := main
这里的参数根据自己需要定义,CC就代表gcc命令,LIBS代表编译时需要的库,中间两个可以忽略,没用到,INCLUDE代表头文件的路径。
4.生成目标文件
$(OBJS):./obj/%.o:./src/%.c
$(CC) -c $< -o $@ $(INCLUDE)
第一步,生成所有客户端文件的目标文件。这是Makefile的静态规则,这句命令是gcc -c ./src/xxx.c -o ./obj/xxx.o -I ./include ,遍历所有的.c文件,生成所有相对应的.o文件,有多少c文件,就执行多少次。这种写法很适合有多个c文件的情况,不用一行一行敲gcc,一行命令就解决了。其中$<代表一个.c文件,$@代表目标文件。注意这里的坑,第一句,./obj/%.o:./src/%.c 我看别的教程中用$(OBJ)/%.o : $(SRC)/%.c 来代替的,但是一直报错——“没有对应的规则生成xxx.o”,后来又改回来了,就通过了,很奇怪,没想通咋回事,但是能用就好了。。。
$(MAIN_OBJS):./obj/%.o:./main.c
$(CC) -c $< -o $@ $(INCLUDE)
第二步,生成主函数的目标文件,和上面类似。
5.生成最终可执行文件
$(TARGET) : $(OBJS) $(MAIN_OBJS)
$(CC) $(CFLAGS) -o $@ $(OBJS) $(MAIN_OBJS) $(LDFLAGS) $(LIBS)
这一步应该是链接,将所有的目标文件和所需的库链接在一起,生成最终可执行文件。目标为main,依赖文件为所有的.o文件,生成规则为下面一行。
6.定义其他操作
有时候需要清楚或者重新编译之类的,所以可以定义一些其他操作:
.PHONY : everything objs clean rebuild
everything : $(TARGET)
objs : $(OBJS)
rebuild: clean everything
clean :
rm -fr $(TARGET)
rm -fr $(OBJS) $(MAIN_OBJS)
PHONY是一个伪目标,可以防止在Makefile中定义的只执行命令的目标和工作目录下的实际文件出现名字冲突,另一种是提交执行makefile时的效率,比如make clean 就清除生成的可执行文件main和所有的目标文件。
PS:其实网上看了很多教程,和我的情况都不咋匹配,所以踩了很多坑,不是打广告,只是想和大家分享,让大家少走弯路,可以去b站搜索“快乐学习的代码狗”,他的《零基础学会LINUX系统编程》里面的“09-makefile3个自动变量和模式规则”,“10-习题和作业”讲的很清楚,一共只要40分钟,比在网上找教程快的多。