在Linux做开发,工程的编译输出,需要自己编写Makefile来构建,下面笔者根据自己的实践,做一些总结,本文并不是什么教程,
只是笔者觉得一些重要的东西在此记录下来。
一、基本格式
target : depend
command
target 是要生成的目标文件,可以是可执行文件、*.o、*.a、*.so等文件,当然也可以一个伪目标。
depend 是依赖文件,可以是源文件,头文件,*.o文件,当然也可以是一个伪依赖。
command 是执行命令,即如何编译依赖文件生成目标文件。值得注意的是command前面必须是一个tab键。
eg : main.c depend.c depend.h 生成可执行文件main
main:main.o depend.o
gcc main.o depend.o -o main
main.o : main.c
gcc -c -Wall main.c
depend.o:depend.c depend.h
gcc -c -Wall depend.c
二、变量
Makefile中可以自定义变量,有的地方也称之为宏,变量名尽量用数字、字母和下划线命名,并且对大小写敏感。
变量可以用来代表一个文件名列表、编译选项列表、程序运行的选项参数列表、搜索源文件的目录列表、编译输出的目录列表等。
eg: CC = gcc
CFLAG = -c -Wall
引用一个变量是这样:$(VarName),例如:$(CC) , $(CFLAG)
笔者的使用习惯,定义变量全都是大写,显然这和C语言的宏定义一样,称之为宏也不为怪。
这里的赋值号前后可以有空格,这与Shell脚本不同,Shell中定义变量,赋值号前后不能有空格。
改写上面的Makefile:
CC = gcc
CFLAG = -c -Wall
TARGET = main
OBJ = main.o depend.o
$(TARGET):$(OBJ)
$(CC) $(CFLAG) $(OBJ) -o $(TARGET)
main.o : main.c
$(CC) $(CFLAG) main.c
depend.o:depend.c depend.h
$(CC) $(CFLAG) depend.c
三、特殊变量
$@ 表示目标对象
$^ 表示所有依赖对象
$< 表示第一个依赖对象
VPATH 指定源文件的搜索路径,它和linux下环境变量类似
vpath 和VAPTH类似,但是比VPATH更灵活
继续改写上面的Makefile:
CC = gcc
CFLAG = -c -Wall
TARGET = main
OBJ = main.o depend.o
$(TARGET):$(OBJ)
$(CC) $(CFLAG) $^ -o $@
main.o : main.c
$(CC) $(CFLAG) $<
depend.o:depend.c depend.h
$(CC) $(CFLAG) $<
四、项目案例
Project
├── Bin
├── Build
├── Lib --liblog.a libsqlite.a
└── Source
├── Src1 --Demo1.c Demo1.h
├── Src2 --Demo2.c Demo2.h
└── Src3 --Demo3.c Demo3.h
如上图所示,工程项目Project下有四个目录--Bin,Buld,Lib,Source
Source是源代码目录,其下又有三个子目录Src1,Src2,Src3,在实际项目中,源文件一般按功能模块放在不同的目录下,便于管理。
Lib目录是库目录,根据笔者的经验,这样的目录放第三方的库,如果是开源的库,源代码可以放入其中,编译成一个库文件,链接程序时用到。因为第三方的开源库很少需要修改,编译成库文件后,就可以一直使用了,所以要把他们单独放在Lib文件目录里。
Build目录是编译构建目录,Makefile放在里面,编译时生成的中间文件*.gch,*.o都会在这里,不会散落在源代码目录里,这个目录很有必要。
Bin是生成的二进制程序目录,当然这个目录其实软件运行环境目录,这个目录是要部署到机器上的,一个可执行程序的运行,其实是有很多依赖的,比如可能要读取xml配置文件,可能要依赖某个文件创建进程通信的Key值,在这里我简化了它,Bin目录下没有其他子目录,只存放可执行文件。
假设本工程最终生成可执行文件main,依赖Source下的所有源文件,并且依赖Lib下的liblog.a libsqlite.a
Makefile如下,文件名Makefile1
CC = gcc
CFLAG = -c -Wall
BIN = ../Bin
LIB = ../Lib
SOURCE = ../Source
LINKLIB = -L$(LIB) -lmylog -lsqlite
VPATH = $(SOURCE)/Src1:$(SOURCE)/Src2:$(SOURCE)/Src3
TARGET = main
OBJ = Demo1.o \
Demo2.o \
Demo3.o
all:$(TARGET)
cp $(TARGET) $(BIN)
$(TARGET):$(OBJ)
$(CC) $(CFLAG) $^ $(LINKLIB) -o $@
%.o:%.c
$(CC) $(CFLAG) $<
clean:
rm *.o main
说明:1、 Makefile文件是在Build目录中,因此Makefile的工作路径就是这个路径,定义其他路径时都是相对于Makefile的工作路径,
当然执行Makefile时也必须在这个路径
2、开头定义了几个变量,这样做的好处是使Makefile更容易修改维护
3、VPATH定义了源文件寻找目录,以冒号间隔,Make会自动到这几个目录寻找源文件
4、定义OBJ用的字符‘\’表示行连接符
5、all 是一个伪目标,执行命令仅仅是把生成的可执行程序main拷贝到Bin目录,和下面的clean类似
6、%.o:%.c 是Make的一种自动推导方式,表示一个.o文件会依赖一个对应的.c文件,本例中Make会自动到当前目录或者VPATH中寻找依赖Demo1.c Demo2.c Demo3.c
五、常用函数
其实Makefile里也有函数的,有函数定义和调用的语法格式,但是笔者只使用过内置函数,没有使用过自定义函数。
下面仍然用项目案例来介绍说明几个常用的内置函数
1、wildcard
这个词就是通配符的意思,是通配符函数,通配符在正则匹配中经常使用,并不陌生。
这个函数的功能是按照匹配模式,生一个以空格间隔的列表,例如 SRC = $(wildcard *.c ./foo/*.c) 表示把当前目录下的.c文件和当前目录子目录foo的.c文件,以列表的形式赋值给SRC
语法格式 $(wildcard pattern1 pattern2 ...) ,注意参数以空格隔开
2、patsubst
即pattern substitute ,模式取代,以特定模式替换字符串
语法格式:$(patsubst from,to, str)
SRC = Demo1.c Demo2.c Demo3.c
OBJ = $(patsubst %.c,%.o,$(SRC)) //把.c替换成对应的.o
此时OBJ展开就是: OBJ = Demo1.o Demo2.o Demo3.o
3、subst
字符串替换,与patsubst不同,subst只是暴力的把一个字符串替换成另一个字符串
语法格式:$(subst from,to,str)
4、filter
过滤函数,保留符合模式的字符串
语法格式:$*(filter pattern1 pattern2 ...,text)
src = a.c b.c a.h c.sh d.o
csrc = $(filter %.c %.h,$(src)) //保留 .c .h文件
此时csrc展开 csrc = a.c b.c a.h
5.filter-out
过滤函数,保留不符合模式的,与filter功能相反,语法也一样。
下面继续修改项目案例中的Makefile,文件名Makefile2
CC = gcc
CFLAG = -c -Wall
BIN = ../Bin
LIB = ../Lib
SOURCE = ../Source
LINKLIB = -L$(LIB) -lmylog -lsqlite
SRC = $(wildcard $(SOURCE)/Src1/*.c $(SOURCE)/Src2/*.c $(SOURCE)/Src3/*.c)
TARGET = main
OBJ = $(patsubst %.c,%.o,$(SRC))
all:$(TARGET)
cp $(TARGET) $(BIN)
$(TARGET):$(OBJ)
$(CC) $(CFLAG) $^ $(LINKLIB) -o $@
%.o:%.c
$(CC) $(CFLAG) $<
clean:
rm *.o main
说明:这种使用函数的Makefile2显然比上面那个Makfile1更简洁,更有通用性,但是上面的Makfile2也有它的优势,
这个项目案例只是生成一个可执行程序,它依赖源目录下的所有源文件,当工程是生成多个可执行程序时,某个目标文件只会依赖
一些源文件,显然Makefile1中OBJ变量的定义更灵活,更清晰。
由于笔者的水平有限,出错在所难免,恳请读者拍砖指正,谢谢阅读。