Makefile 是一个用于自动化构建和编译程序的配置文件,它定义了一系列规则来指定如何编译和链接程序。它主要配合 make工具使用。
核心概念
Makefile 的核心是规则,基本语法为:
目标(target):要生成的文件或要执行的动作名称
依赖(prerequisites):生成目标需要的文件
命令(recipe):如何生成目标的 shell 命令(必须以 Tab 键开头)
简单示例
hello: main.o greet.o gcc -o hello main.o greet.omain.o: main.c greet.h gcc -c main.cgreet.o: greet.c greet.h gcc -c greet.cclean: rm -f hello *.o
常用概念
1. 变量
CC = gccCFLAGS = -Wall -gOBJS = main.o greet.ohello: $(OBJS)$(CC)$(CFLAGS) -o $@$^
$@ — 目标文件
$^— 所有依赖文件
`$<— 第一个依赖文件
2. 模式规则
makefile%.o: %.c$(CC)$(CFLAGS) -c $< -o $@
3. 伪目标
makefile.PHONY: cleanclean: rm -f *.o hello
.PHONY声明伪目标,避免与同名文件冲突。
为什么使用 Makefile
只重新编译修改过的文件,大幅节省构建时间
标准化构建流程,团队成员无需记忆复杂命令
管理项目依赖关系,保证构建顺序正确
Makefile 广泛应用于 C/C++ 项目,也可用于任何需要自动化任务管理的场景。
二、Makefile 中的函数
Makefile 提供了丰富的内置函数,用于处理文本、文件名、条件判断等。函数调用的基本语法是:
makefile $(函数名 参数1,参数2,...)# 或${函数名 参数1,参数2,...}
参数之间用逗号分隔,函数名和第一个参数之间用空格分隔。
文本处理函数
1. subst — 字符串替换
makefile$(subst 被替换的文本,替换后的文本,目标文本)
makefile result = $(subst ee,EE,feet on the street)# 结果: fEEt on the strEEt
2. patsubst — 模式字符串替换
makefile$(patsubst 模式,替换后,目标文本)
makefileobjects = $(patsubst %.c,%.o,main.c greet.c)# 结果: main.o greet.o
3. strip — 去除首尾空格
makefile$(strip hello world )# 结果: hello world
4. findstring — 查找字符串
makefile$(findstring 查找内容,被查找文本)# 找到则返回查找内容,否则返回空
makefileifneq ($(findstring linux,$(shell uname -r)),)# 在 Linux 内核下执行endif
5. filter / filter-out — 过滤/反过滤
makefilesources = foo.c bar.c baz.s ugh.hc-files = $(filter %.c %.h, $(sources))# 结果: foo.c bar.c ugh.hnot-c = $(filter-out %.c, $(sources))# 结果: baz.s ugh.h
6. sort — 排序去重
makefile$(sort foo bar lose foo)# 结果: bar foo lose
7. word — 取第 n 个单词
makefile$(word 2, foo bar baz)# 结果: bar$(words foo bar baz)# 单词数: 3$(firstword foo bar baz)# 第一个: foo$(lastword foo bar baz)# 最后一个: baz
文件名处理函数
makefile$(dir src/foo.c hacks) # 目录部分: src/ ./$(notdir src/foo.c hacks) # 文件名部分: foo.c hacks$(suffix src/foo.c src-1.0/bar) # 后缀: .c$(basename src/foo.c src-1.0/bar) # 去掉后缀: src/foo src-1.0/bar$(addsuffix .c,foo bar) # 加后缀: foo.c bar.c$(addprefix src/,foo.c bar) # 加前缀: src/foo.c src/bar$(join a b, .c .o) # 逐词拼接: a.c b.o$(wildcard *.c) # 通配符展开: main.c greet.c$(realpath names...) # 获取绝对路径$(abspath names...) # 获取绝对路径(不解析符号链接)
条件与逻辑函数
makefile$(if 条件,true分支,false分支) # false 分支可选$(or 条件1,条件2) # 第一个非空值$(and 条件1,条件2) # 全部非空才返回最后值
示例:
makefileFOO = $(if $(filter linux,$(shell uname)),linux-specific,macos-specific)
特殊功能函数
1. foreach — 循环处理
makefile$(foreach 变量,列表,表达式)
makefiledirs = a b c dfiles = $(foreachdir,$(dirs),$(dir)/*.c)# 结果: a/*.c b/*.c c/*.c d/*.c
2. call — 自定义函数
makefiledefine 函数名# 函数体,用 $(0) 代表函数名,$(1) $(2)... 代表参数endef
makefiledefine make-obj$(patsubst %.c,%.o,$(1))endefobjects = $(call make-obj,main.c greet.c)# 结果: main.o greet.o
3. eval — 动态生成 Makefile 内容
makefiledefine PROGRAM_template$(1): $(1).o$(CC) $(CFLAGS) -o$$@$$^endef$(foreach prog,$(PROGRAMS),$(eval $(call PROGRAM_template,$(prog))))
这会为 PROGRAMS 中的每个程序生成独立的构建规则。
4. shell — 执行 shell 命令
CURRENT_TIME = $(shell date +%Y-%m-%d)GIT_HASH = $(shell git rev-parse --short HEAD)
5. value — 取未展开的值
makefileFOO = $PATH$(value FOO)# 返回 $PATH 字符串本身
实用函数组合示例
获取某目录下所有 .c 对应的 .o 文件:
makefileSRCS = $(wildcard src/*.c)OBJS = $(patsubst %.c,%.o,$(SRCS))# 或简写为OBJS = $(SRCS:.c=.o)
生成依赖文件:
makefileDEPS = $(patsubst %.o,%.d,$(OBJS))-include$(DEPS)%.d: %.c @$(CC) -MM $< | sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' > $@
常用速查表
Makefile 的函数体系使其具备了强大的元编程能力,掌握了它们就能写出优雅、可维护的构建系统。