Makefile的编写总结

原文链接:Makefile的编写总结
发布时间:2015-09-01 22:10:24

在Linux做开发,工程的编译输出,需要自己编写Makefile来构建,下面笔者根据自己的实践,做一些总结,本文并不是什么教程,只是笔者觉得一些重要的东西在此记录下来。

一、基本格式

1
2
3
target : depend

command

target 是要生成的目标文件,可以是可执行文件、.o、.a、*.so等文件,当然也可以一个伪目标。

depend 是依赖文件,可以是源文件,头文件,*.o文件,当然也可以是一个伪依赖。

command 是执行命令,即如何编译依赖文件生成目标文件。值得注意的是command前面必须是一个tab键。

eg : main.c depend.c depend.h 生成可执行文件main

1
2
3
4
5
6
7
8
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:

1
2
CC = gcc
CFLAG = -c -Wall

引用一个变量是这样:$(VarName),例如:$(CC) , $(CFLAG)

笔者的使用习惯,定义变量全都是大写,显然这和C语言的宏定义一样,称之为宏也不为怪。

这里的赋值号前后可以有空格,这与Shell脚本不同,Shell中定义变量,赋值号前后不能有空格。

改写上面的Makefile:

1
2
3
4
5
6
7
8
9
10
11
12
13
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 和VPATH类似,但是比VPATH更灵活

继续改写上面的Makefile:

1
2
3
4
5
6
7
8
9
10
11
12
13
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) $<

四、项目案例

1
2
3
4
5
6
7
8
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

Makefile1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

通配符函数,按照匹配模式生成一个以空格间隔的列表。

语法格式:$(wildcard pattern1 pattern2 ...) ,参数以空格隔开。

例如:SRC = $(wildcard *.c ./foo/*.c) 表示把当前目录下的.c文件和子目录foo的.c文件以列表赋值给SRC。

2、patsubst

模式替换,以特定模式替换字符串。

语法格式:$(patsubst from, to, str)

1
2
SRC = Demo1.c Demo2.c Demo3.c
OBJ = $(patsubst %.c, %.o, $(SRC)) # 把.c替换成对应的.o

此时OBJ展开:OBJ = Demo1.o Demo2.o Demo3.o

3、subst

字符串替换,暴力的把一个字符串替换成另一个字符串。

语法格式:$(subst from, to, str)

4、filter

过滤函数,保留符合模式的字符串。

语法格式:$(filter pattern1 pattern2 ..., text)

1
2
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功能相反,语法一样。

Makefile2(用函数改写):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 比 Makefile1 更简洁通用,但当工程需要生成多个可执行程序时,Makefile1 中 OBJ 变量的定义更灵活清晰。

由于笔者的水平有限,出错在所难免,恳请读者拍砖指正,谢谢阅读