简述
Makefile是一种被用作自动化编译,Makefile定义了一系列的规则来指定文件编译顺序,以及哪些文件需要重新编译,甚至可以像shell脚本一样执行操作系统的命令。
本篇只是简单概念及使用方式,详细的可参考:C语言中文网-Makefile教程
规则
普通规则
target... : prerequisites ...
command
......
模块 | 描述 |
---|---|
target | 可以是一个object file(目标文件),也可以是一个执行文件,还可以是一个标签(label) |
prerequisites | 生成该target所依赖的文件,可以有多个依赖 |
command | 该target要执行的命令(任意的shell命令) |
Make工具从上找寻target,根据target后对应的依赖关系,先去查找依赖项的文件。若依赖项不存在,则在makefile中去寻找对应的target,直至生成需要的文件;否则,直接调用command部分。若找到最底层都无法满足最终目标文件的生成条件,就会报错。
在检查依赖关系时,同时会检查目标与源文件的时间戳,当源文件时间戳更新时,make会更新依赖它的链路上所有文件。
# test依赖于libtest.a main.o
test: libtest.a main.o
gcc main.o -ltest -L. -o test
# main.o依赖于main.c
main.o:main.c
gcc main.c -c -o main.o
# libtest.a依赖于test1.o test2.o
libtest.a: test1.o test2.o
ar -r libtest.a test1.o test2.o
# test1.o依赖于test1.c
test1.o:test1.c
gcc test1.c -c -o test1.o
# test2.o依赖于test2.c
test2.o:test2.c
gcc test2.c -c -o test2.o
模式规则
模式规则类似于普通规则,只是在模式规则中,目标名中需要包含有模式字符“%”,包含有模式字符“%”的目标被用来匹配一个文件名,“%”可以匹配任何非空字符串。
%.o:%.c
gcc -c $< -o $@
后缀规则
后缀规则是一个比较老式的定义隐含规则的方法。后缀规则不允许依赖文件,如果有,前面的后缀会被认为是文件名。
#下面的写法等同于模式规则 %.o:%.c
.c.o:
gcc -c -o $@ $<
#下面的写法会被解析为普通规则
.c.o:foo.h
gcc -c -o $@ $<
模式规则和后缀规则的差异
模式规则定义后会覆盖隐式规则,后缀规则不会,所以后缀规则不添加命令将没有意义。
#后缀规则编译成功
main: main.o
echo "generate target"
gcc -o main $<
.c.o:
#模式规则编译失败
main: main.o
echo "generate target"
gcc -o main $<
%.o: %.c
变量和函数
单纯使用基本语法,若要对项目新增模块或文件,需要手动添加新的依赖关系,不利于扩展。引入变量,自动完成依赖关系添加。Makefile中的变量类型基本上就可以直接理解为字符串类型。
等号
Makefile中的等号有4种,"=",":=","?=","+="。
"?="表示,如果左边的变量没有被赋值,那么将等号右边的值赋给左边的变量。如果赋过值,则保持原来的值不变。
"+="表示将等号右边的值追加到左边变量中,但是中间会有一个空格。
"=“与”:=“是比较不好区分的两个等号,可以将”=“理解为"址传递"或引用,”:=“理解为"值传递”。
在Makefile中是不允许将变量自己的值赋给自己的,Makefile不允许出现循环引用。
var := ${var} 1 2 3 # 允许
var = ${var} 1 2 3 # 不允许
最开始例子中*.o的生成方式可写成如下。
SRC = test1.c test2.c main.c
OBJ = test1.o test2.o main.o
${OBJ}:${SRC}
gcc -c ${SRC}
函数
使用 “$(< function > < arguments >)” 的形式可以调用函数。
函数名 | 作用 |
---|---|
wildcard | 通配符展开函数 |
patsubst | 字符串替换 |
常见用法:
src := $(shell ls *.c)
objs := $(patsubst %.c, %.o, $(src))
${objs}:${src}
gcc -c ${src}
自动化变量
关于自动化变量可以理解为由 Makefile 自动产生的变量,而且只能在规则中使用。
自动化变量 | 作用 |
---|---|
$@ | 规则的目标文件名(依赖关系中冒号:左边的文件,如果a: b c,那么$@指a) |
$% | 当目标文件是一个静态库文件时,代表静态库的一个成员名 |
$< | 被依赖文件的第一项(如果a: b c,那么$<指b) |
$? | 所有比目标文件更新的依赖文件列表,空格分隔 |
$^ | 所有依赖文件列表,使用空格分隔(如果a: b c ,那么$^指b c),不包含重复文件 |
$+ | 所有依赖文件列表,使用空格分隔(如果a: b c c,那么$+指b c c),包含重复文件 |
$* | 在模式规则和静态模式规则中的"%“所匹配的内容。这个变量表示目标模式中”%“及其之前的部分。如果目标是"dir/a.foo.b”,并且目标的模式是"a.%.b",那么,"$*“的值就是"dir/a.foo” |
递归编译
递归编译主要通过make的 -C 参数实现,使用该方式命令会进入后面的传入的目录中查找makefile并执行。一般这种情况需要再最外层设置一个总控的makefile,完成整个项目的编译。
make -C $(dirpath)
内置变量
$(AR) # 生产 archive 文件的默认程序 ar
$(CC) # 编译 C 代码的默认编译器 cc
$(CXX) # 编译 C++ 代码的默认编译器 g++
$(CFLAGS) # 编译 C 代码的参数
$(CXXFLAGS) # 编译 C++ 代码的参数
$(LDFLAGS) # 链接时的参数
基本模板
多文件项目编译
CC = gcc
LD = gcc
SRCS = $(wildcard *.cpp)
OBJS = $(patsubst %cpp, %o, $(SRCS))
# -I指定头文件目录
INCLUDE = -I./include
# -L指定库文件目录,-l指定静态库名字(去掉文件名中的lib前缀和.a后缀)
LDFLAGS = -L./libs -ltomcrypt
# 开启编译warning和设置优化等级
CFLAGS = -Wall -O2
CXXFLAGS =
TARGET = TestDemo
.PHONY:all clean
all: $(TARGET)
# 链接时候指定库文件目录及库文件名
$(TARGET): $(OBJS)
$(LD) -o $@ $^ $(LDFLAGS)
# 编译时候指定头文件目录
%.o:%.cpp
$(CC) -c $^ $(INCLUDE) $(CFLAGS)
clean:
rm -f $(OBJS) $(TARGET)
多层目录编译
.PHONY:all clean
# 排除目录
exclude_dirs := .git
# 显示深度为1的子目录
dirs := $(shell find . -type d -maxdepth 1)
# 去掉获取到目录名称前面的./
dirs := $(basename $(patsubst ./%, %, $(dirs)))
# 过滤指定目录
dirs := $(filter-out $(exclude_dirs), $(dirs))
all:
$(foreach N,$(dirs),make -C $(N);)
clean:
$(foreach N,$(dirs),make -C $(N) clean;)