一、概述
make是一个类UNIX系统下的编译命令,也可以理解为一个项目管理工具,通过make可以按照自己指定的编译命令编译整个项目,相当于将在命令行的编译命令按序执行,省去了反复键入编译命令的麻烦。除此之外,如果手动执行编译命令,不仅费时难以记忆,最重要的是每执行一次编译命令,项目中的整个文件都要重新编译,即使是未修改过的文件,这在大型项目中是难以忍受的。而make就提供了一种完美的解决方案,它将要执行的编译命令通过特定的语法组织到Makefile文件中,每次只要执行make
命令,就可以完成整个项目的构建。
make执行时是根据文件的时间戳来选择要编译的文件。比如某个项目中你只是修改了其中一个文件,这个文件的时间戳就会晚于你所要生成的目标文件的时间戳,因为最终的目标文件肯定是最后生成的。如此,便不回去重新编译那些未修改的文件,省去了大量不必要的编译时间。所谓Makefile,就是将make要执行的编译命令写入到此文件中,这个文件也可以是其它名字,make时可以使用make -f filename
来进行构建,当然,减少不必要的麻烦,还是统一命名为Makefile或者makefile为好。Makefile中指定了要执行的编译命令以及依赖的相关文件,执行make时,会在命令执行目录下寻找Makefile文件,根据该文件来构建整个项目。在类UNIX系统下的C/C++项目,make都是不可或缺的。
二、Makefile基础
一条规则的基本要素:
- 目标:执行make要生成的目标文件
- 依赖:用来生成目标文件的依赖文件
- 命令:即如何用依赖文件生成目标文件的规则
# 一条简单的规则
# test为目标,test.c为依赖
# gcc一行即是命令,须以TAB键开头
test: test.c
gcc test.c -o test
在一条规则中,目标必须存在,依赖和命令两个中至少要存在一个。在具体的Makefile编写之前,先讲一下Makefile文件中的几个基础知识,后续编写实例会用到
一条规则:模式规则
模式规则:模式规则类似于普通规则。只是在模式规则中,目标名中需要包含有模式字符“%”,包含有模式字符“%”的目标被用来匹配一个文件名,“%”可以匹配任何非空字符串。规则的依赖文件中同样可以使用“%”,依赖文件中模式字符“%”的取值情况由目标中的“%”来决定。例如:对于模式规则“%.o : %.c”,它表示的含义是:所有的.o文件依赖于对应的.c文件。我们可以使用模式规则来定义隐含规则。
两个函数
通配符函数:$(wildcard <pattern>)
功能:返回pattern指定的文件
用法:
# 获取当前目录下的.c文件,并存储到变量SRC中
SRC = $(wildcard *.c)
字符串替换函数:$(patsubst <pattern> <replacement> <text>)
功能:匹配text中符合pattern的单词(单词以“空格”、“Tab”或“回车”“换行”分隔),匹配成功用replacement进行替换,返回被替换过后的字符串。这里,pattern可以包括通配符%
,表示任意长度的字符串,如果replacement也包含%
,那么replacement中的%
将是pattern中那个%
所代表的字符串
用法:
# 将SRC中的.c文件替换为同名的.o文件然后返回
SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))
# 或者 OBJ= $($(SRC):%.c=%.o)
三个自动变量
$@ # 规则中的目标
$< # 所有依赖
$^ # 第一个依赖
伪目标
除了以上几点,Makefile中还有一个比较重要的概念就是伪目标,我们通常再重新执行make之前, 会执行make clean
命令,这个命令的作用就是删除一些规则指定的文件,如生成的中间文件以及可执行文件等,但是,如果Makefile所在文件下恰好存在一名为clean文件时,则不会执行相应的删除文件命令,解决办法就是将clean声明为伪目标,如此,不管当前文件夹是是否存在clean文件,执行make clean
时都会执行相应的删除命令,其声明格式为:
.PHONY: clean
三、Makefile实例编写
有了以上基础知识,便可以编写一些基础的Makefile文件了,下面通过一个具体的实例来演示:
创建Makefile
所需文件:add.c,min.c,main.c
# 依赖.o文件生成可执行文件app
app: main.o add.o min.o
gcc main.o add.o min.o -o app
# 依赖.c文件生成.o文件
main.o: main.c
gcc -c main.c -o main.o
add.o: add.c
gcc -c add.c -o add.o
min.o: min.c
gcc -c min.c -o min.o
# clean目标用于删除中间文件
clean:
rm -rf main.o add.o min.o
注意:Makefile文件按照从上到下的规则依次执行的,并且将文件的第一个目标作为终极目标,执行结果:
使用自定义变量
# 声明自定义变量 obj
obj = main.o add.o min.o
# 使用 $ 取出自定义变量的值
app: $(obj)
gcc main.o add.o min.o -o app
# 依赖.c文件生成.o文件
main.o: main.c
gcc -c main.c -o main.o
add.o: add.c
gcc -c add.c -o add.o
min.o: min.c
gcc -c min.c -o min.o
# clean目标用于删除中间文件
clean:
rm -rf $(obj)
使用自动变量
target = app
obj = main.o add.o min.o
$(target): $(obj)
gcc $^ -o $@
# 依赖.c文件生成.o文件
%.o: %.c
gcc -c $< -o $@
# clean目标用于删除中间文件
clean:
rm -rf $(obj) $(target)
使用Makefile变量
target = app
obj = main.o add.o min.o
CC = gcc
# 编译时使用的参数,如 -c, -g, -Wall, -I 等
CPPFLAGS =
CFLAGS = -c
# 链接库使用的选项 -L, -l
LDFLAGS =
$(target): $(obj)
$(CC) $^ -o $@
%.o: %.c
$(CC) $(CFLAGS) $< -o $@
clean:
rm -rf $(obj) $(target)
使用函数
src = $(wildcard ./*.c)
obj = $(patsubst ./%.c, ./%.o, $(src))
target = app
ALL:$(target)
CC = gcc
CFLAGS = -c
$(target): $(obj)
$(CC) $^ -o $@
%.o: %.c
$(CC) $(CFLAGS) $< -o $@
clean:
# 命令前加'-',执行出错时会继续执行
-rm -rf $(obj) $(target)
# 声明伪目标
.PHONY: clean ALL
编译不同目录下文件
重新组织文件结构,源文件存放在src目录下,头文件在inc目录下,中间的.o文件存放在obj目录下,可执行文件放在bin目录下,如下图所示:
src = $(wildcard ./src/*.c)
obj = $(patsubst ./src/%.c, ./obj/%.o, $(src))
inc_path = ./inc/
target = ./bin/app
ALL:$(target)
CC = gcc
CFLAGS = -c
$(target): $(obj)
$(CC) $^ -o $@
./obj/%.o: ./src/%.c
$(CC) $(CFLAGS) -I $(inc_path) $< -o $@
clean:
-rm -rf $(obj) $(target)
.PHONY: clean ALL
执行结果如下: