
Makefile 基础语法
Makefile 是一种用于自动化构建过程的脚本文件,它通过定义目标文件和依赖关系来告诉 make 工具如何构建项目。Makefile 的语法和结构可以非常灵活,适用于各种复杂的构建场景。本教程将详细介绍 Makefile 的基本概念、语法以及如何编写高效的 Makefile 文件。
Makefile 意义:
- 自动化构建:
Makefile 可以自动构建项目,减少手动编译和链接的时间消耗。
通过简单的 make 命令,就可以自动处理项目的编译和链接过程。 - 依赖管理:
Makefile 能够追踪文件之间的依赖关系,只对那些真正需要更新的文件进行编译。
当源文件发生变化时,Makefile 可以确保只重新编译受影响的部分,而不是整个项目。 - 提高效率:
自动化构建过程减少了人为错误的机会,同时也节省了大量的时间。
开发者可以专注于编写代码而不是编译过程。 - 可配置性和可扩展性:
Makefile 可以轻松地配置不同的构建选项,如编译器标志、调试信息等。
可以很容易地添加新的目标或规则来适应项目的增长和发展。
1. Makefile 基础
1.1 规则格式
Makefile 文件中的每一条规则都由三个部分组成:
- 目标(
target): 目标文件或要完成的任务。 - 依赖(
dependencies): 完成目标所需的文件或其他目标。 - 命令(
commands): 用于生成目标的一系列 shell 命令。
1.2 示例
target: dependencies
command1
command2
1.3 默认目标
Makefile 中的第一个目标会被当作默认目标。如果没有显式指定目标,make 会构建第一个列出的目标。
1.4 变量
Makefile 支持变量定义,可以简化重复的文本。它们用于存储和管理数据,比如文件名、目录路径、编译器选项等。
CC = gcc # 定义编译器
CFLAGS = -Wall -g # 定义编译选项
OBJS = foo.o bar.o baz.o # 定义目标依赖
1.5 自动变量
自动变量 $@, $<, $^, $+ 等用于引用目标或依赖。
$@- 含义:表示规则的目标文件名。当 Makefile 正在执行某个规则时,$@ 就会被替换为目标文件的名称。
- 用途:
- 构建命令:在构建命令中使用 $@ 来指定输出文件
- 依赖检查:在依赖检查时 $@ 用来标识当前规则的目标文件
$^- 含义:在 Makefile 中,自动变量
$^代表的是规则中所有依赖文件的集合。它包含了所有依赖文件的名称,但去除了重复的依赖文件。这与$+不同,后者保留所有依赖文件,即使有重复也会列出多次 - 用途:
- 构建命令:在构建命令中使用
$^来引用所有依赖文件 - 依赖检查:在依赖检查时
$^用来标识所有依赖文件
- 构建命令:在构建命令中使用
- 含义:在 Makefile 中,自动变量
$<
- 含义:在 Makefile 中,自动变量
<
代表的是规则中第一个依赖文件的名称。当
M
a
k
e
f
i
l
e
正在执行某个规则时,
< 代表的是规则中第一个依赖文件的名称。当 Makefile 正在执行某个规则时,
<代表的是规则中第一个依赖文件的名称。当Makefile正在执行某个规则时,< 就会被替换为第一个依赖文件的名称。
- 用途:
- 构建命令:在构建命令中使用 $< 来引用第一个依赖文件
- 依赖检查:在依赖检查时 $< 用来标识第一个依赖文件
示例:
CC = gcc CFLAGS = -Wall -g all: hello hello: main.o util.o $(CC) $(CFLAGS) -o $@ $^ %.o: %.c $(CC) $(CFLAGS) -c -o $@ $< clean: rm -f *.o hello - 用途:
解释:
- 规则 hello 的目标文件是 hello,依赖项是 main.o 和 util.o。
- 在构建 hello 时,
@
就会被替换为
h
e
l
l
o
,
@ 就会被替换为 hello,
@就会被替换为hello,^ 就会被替换为 main.o util.o
- 在构建 .o 文件时,
@
会被替换为
.
o
文件名,例如
m
a
i
n
.
o
或
u
t
i
l
.
o
。
−
在构建
.
o
文件时
,
@ 会被替换为 .o 文件名,例如 main.o 或 util.o。 - 在构建 .o 文件时,
@会被替换为.o文件名,例如main.o或util.o。−在构建.o文件时,^ 不会被使用,因为每个 .o 文件只有一个依赖文件。
- 在构建 .o 文件时,$< 会被替换为 .c 文件名,例如 main.c 或 util.c。
$%- 含义:自动变量
$%代表的是规则中目标成员的名称,通常用于处理归档成员(archive member)的情况,即库文件中的成员文件。这个变量在处理静态库(例如 .a 文件)时非常有用。 - 用途:
- 构建命令:在构建命令中使用 $% 来引用归档库中的成员文件
- 依赖检查:在依赖检查时 $% 用来标识归档库中的成员文件
- 示例:
AR = ar ARFLAGS = rcs LIB = libfoo.a all: $(LIB) $(LIB): foo.o bar.o $(AR) $(ARFLAGS) $@ $^ %.o: %.c $(CC) $(CFLAGS) -c -o $@ $< clean: rm -f *.o $(LIB) - 解释:
- 规则
$(LIB)的目标文件是libfoo.a,依赖项是foo.o和bar.o。 - 在构建
$(LIB)时,$%就会被替换为库中的成员文件名,例如foo.o和bar.o。 - 在构建
.o文件时,$%不会被使用,因为每个.o文件只有一个依赖文件
- 规则
- 含义:自动变量
$?- 含义:自动变量 $? 代表的是所有比目标文件更新的依赖文件的集合。它包含了所有比目标文件更新的依赖文件的名称,并且这些文件之间是以空格分隔的。
- 用途:
- 构建命令:在构建命令中使用 $? 来引用所有比目标文件更新的依赖文件。
- 依赖检查:在依赖检查时 $? 用来标识所有比目标文件更新的依赖文件。
- 示例
CC = gcc CFLAGS = -Wall -g all: myapp myapp: main.o util.o $(CC) $(CFLAGS) -o $@ $? %.o: %.c $(CC) $(CFLAGS) -c -o $@ $< clean: rm -f *.o myapp - 解释:
- 当首次运行
make时,所有.c文件都会比myapp新,因此$?包含main.o和util.o。 $(CC) $(CFLAGS) -o $@ $?会链接main.o和util.o生成myapp。- 如果之后修改了
main.c文件,但util.c未修改,则$?只包含main.o,因此只有main.o会被重新编译,然后与util.o一起链接生成myapp。 - 如果
util.c也被修改了,那么$?将包含main.o和util.o,这两个文件都将被重新编译,然后链接生成myapp。
1.6 通配符
在 Makefile 中,通配符被用来匹配文件名的一部分或全部,以便于构建规则的定义更加灵活。这里有几个常用的通配符及其用途:
- %:在模式规则中用于匹配单个文件名的部分。它通常用于目标或依赖项的模板中。
- 示例:
%.o: %.c gcc -c $< -o $@- 解释:这条规则会匹配所有
.c文件,并将它们编译成相应的.o文件。当你运行make foo.o,make 将会尝试找到匹配foo.o: foo.c的规则。
- *:匹配任意数量的字符(包括零字符)。
- 示例
objects = $(wildcard *.o) all: $(objects)- 解释:这里
$(wildcard *.o)会扩展为当前目录下所有的.o文件。
- ?:匹配单个字符。
1.7 条件执行
在 Makefile 中,条件执行允许您根据变量的值来控制某些规则或命令的执行。这可以通过几个条件指令来实现,主要包括 ifeq, ifneq, ifdef, 和 ifndef。
1. ifeq 和 ifnq
- ifeq 用于测试两个表达式的值是否相等,如果相等则执行 then 后面的语句块,否则跳过。
- ifneq 则相反,如果两个表达式的值不相等,则执行 then 后面的语句块。
2. 基本语法
ifeq (value1, value2)
# TEXT-IF-TRUE
else
# TEXT-IF-FALSE
endif
3. 示例
ifeq ($(DEBUG), 1)
CFLAGS += -g
else
CFLAGS += -O2
endif
4. ifdef 和 ifndef
ifdef用于检查变量是否已经被定义,如果已定义则执行 then 后面的语句块。ifndef则检查变量是否未被定义,如果没有定义则执行 then 后面的语句块。
5. 基本语法
ifdef variable
# TEXT-IF-DEFINED
else
# TEXT-IF-NOT-DEFINED
endif
6. 示例
ifdef DEBUG
CFLAGS += -DDEBUG
endif
1.8 函数
在 Makefile 中,函数是用来处理变量值的工具,可以让构建过程更加灵活和动态。以下是一些常用的 Makefile 函数及其用途:
字符串处理函数
subst:- 功能:字符串替换函数
- 语法:
$(subst FROM,TO,TEXT) - 示例:
STR := hello world NEW_STR := $(subst l,X,$(STR)) - 解释:STR 变量被设置为 “hello world”,然后使用 subst 函数将其中的字母 l 替换为 X,因此,最终 NEW_STR 的值将是 “heXXo worXd”,并将结果赋值给 NEW_STR 变量。
patsubst:- 功能:模式字符串替换函数
- 语法:
$(patsubst PATTERN,REPLACEMENT,TEXT)PATTERN是%.c,表示匹配任何以.c结尾的文件。REPLACEMENT是%.o,表示将匹配到的.c替换成.o。TEXT是FILES变量,包含了一组.c文件。
- 示例:
FILES := foo.c bar.c baz.c OBJECTS := $(patsubst %.c,%.o,$(FILES)) - 解释:
patsubst函数会遍历FILES变量中的每个元素,如果元素匹配PATTERN(即以.c结尾),则用 REPLACEMENT 替换该元素。因此,OBJECTS变量的值将会是:foo.o bar.o baz.o
wildcard:- 功能:扩展通配符,返回匹配的文件列表。
- 语法:
$(wildcard PATTERN) - 示例:
SOURCES := $(wildcard *.c) - 解释:返回当前目录下所有.c文件
notdir:- 功能:从文件路径中移除目录部分,只保留文件名。
- 语法:
$(notdir FILE) - 示例:
FILE := /path/to/file.txt NAME := $(notdir $(FILE)) - 解释:当
notdir函数执行时,它会从给定的路径中移除目录部分,只保留文件名。因此,最终NAME的值将是 “file.txt”。
dir:- 功能:返回文件路径的目录部分
- 语法:$(dir FILE)
- 示例:
FILE := /path/to/file.txt DIR := $(dir $(FILE)) - 解释:
dir函数执行时,它会从给定的路径中移除文件名部分,只保留目录路径。因此,最终DIR的值将是 “/path/to/”。
addsuffix:- 功能:给列表中的每个元素添加后缀
- 语法:
$(addsuffix SUFFIX,LIST) - 示例:
SUFFIX := .o LIST := foo bar baz OBJECTS := $(addsuffix $(SUFFIX),$(LIST)) - 解释:在这里,
SUFFIX是要添加到列表每个元素末尾的后缀,LIST是要处理的列表。addsuffix函数执行时,它会将SUFFIX的值 “.o” 添加到LIST中每个元素的末尾。因此,最终OBJECTS的值将是:foo.o bar.o baz.o
addprefix:- 功能:给列表中的每个元素添加前缀。
- 语法:
$(addprefix PREFIX,LIST) - 示例:
PREFIX := obj/ LIST := foo.o bar.o baz.o OBJECTS := $(addprefix $(PREFIX),$(LIST)) - 解释:在这里,
PREFIX是要添加到列表每个元素的前缀,LIST是要处理的列表。addprefix函数执行时,它会将 prefix 的值 “obj/” 添加到 LIST 中每个元素的末尾。因此,最终 OBJECTS 的值将是:obj/foo obj/bar obj/baz
filter:- 功能:从列表中选择匹配的元素。
- 语法:
$(filter PATTERN,...) - 示例:
FILES := foo.c bar.c foo.h bar.h CS := $(filter %.c,$(FILES)) - 解释:当 filter 函数执行时,它会从 FILES 变量中筛选出所有匹配模式 %.c 的元素。因此,最终 CS 的值将是:foo.c bar.c
filter-out:- 功能:从列表中排除匹配的元素。
- 语法:
$(filter-out PATTERN,...) - 示例:
FILES := foo.c bar.c foo.h bar.h NON_CS := $(filter-out %.c,$(FILES)) - 解释:当 filter-out 函数执行时,它会从 FILES 变量中筛选出所有匹配模式不是 %.c 的元素。因此,最终 NON_CS 的值将是:foo.h bar.h
其他函数
sort:- 功能:对列表排序。
- 语法:
$(sort LIST) - 示例:
FILES := foo.c bar.c baz.c extra.c SORTED_FILES := $(sort $(FILES)) - 解释:sort 函数会对 FILES 变量中的元素进行排序,SORTED_FILES量将包含按字母顺序排序后的文件名列表,结果为:bar.c baz.c extra.c foo.c
strip:- 功能:删除变量值中的空白字符。
- 语法:
$(strip TEXT) - 示例:
TEXT := hello world STRIPPED := $(strip $(TEXT)) - 解释:当 strip 函数执行时,它会去除 TEXT 变量值中的前导和尾随空白字符。由于 “hello world” 本身没有前导或尾随空白字符,因此最终 STRIPPED 的值仍然是 “hello world”。
call:- 功能:调用宏或函数。
- 语法:
$(call macro-name, arguments...) - 示例:
define MY_MACRO @echo "Hello, $(1)" endef $(call MY_MACRO,World) - 解释:这段代码定义了一个宏 MY_MACRO,它接受一个参数(这里用 $(1) 表示),并打印一条包含该参数的消息。
当 call 函数执行时,它会调用 MY_MACRO 宏,并将 “World” 作为参数传递。因此,最终会打印出:Hello, World
foreach:- 功能:用于迭代变量名。
- 语法:
$(foreach var, list, command)- var: 用于迭代的变量名。
- list: 需要迭代的列表。
- command: 对每个元素执行的命令。
- 示例:
SOURCES := foo.c bar.c baz.c OBJECTS := define foreach $(eval $(1) := $(foreach $(2),$(3),$(4))) endef $(call foreach, OBJECTS, %.c, %.o, $(SOURCES)) all: @echo "Sources: $(SOURCES)" @echo "Objects: $(OBJECTS)" - 输出:
Sources: foo.c bar.c baz.c Objects: foo.o bar.o baz.o - 解释:这表明 foreach 函数成功地将每个 .c 文件名转换成了对应的 .o 文件名。
- 注意事项:
- foreach 函数不是内置的 Makefile 函数,而是通过宏定义来实现的。
- 在实际使用中,您可能需要根据具体需求调整 foreach 函数中的命令。
1.10 清理规则
在 Makefile 中,清理规则(clean rule)是一种常见的做法,用于删除中间文件和编译产物,以便重新编译项目或保持工作目录的整洁。
基本语法
.PHONY: clean
clean:
rm -f *.o my_program
详解
.PHONY标记:- clean 目标通常标记为 .PHONY,这意味着即使没有与之对应的目标文件存在,make 也会执行这个目标。这是因为清理规则通常不需要与任何具体的文件关联,而是要删除一系列文件。通常clean放在makefile文件末尾。
- 清理命令:
- rm -f 命令用于删除文件。-f 选项表示强制删除,即使文件不存在也不会产生错误。
- *.o 表示删除所有 .o 文件。
- my_program 表示删除名为 my_program 的可执行文件。
示例
SOURCES := foo.c bar.c baz.c
OBJECTS := $(addsuffix .o, $(basename $(SOURCES)))
all: my_program
my_program: $(OBJECTS)
gcc -o $@ $(OBJECTS)
%.o: %.c
gcc -c $< -o $@
.PHONY: clean
clean:
rm -f $(OBJECTS) my_program
解释
1. 定义源文件和目标文件:
SOURCES := foo.c bar.c baz.c
OBJECTS := $(addsuffix .o, $(basename $(SOURCES)))
2. 定义 all 目标:
all: my_program
运行 make 时,默认会执行 all 目标,这里指向 my_program。
3. 定义 my_program 目标
my_program: $(OBJECTS)
gcc -o $@ $(OBJECTS)
这个目标定义了如何从目标文件列表 OBJECTS 创建可执行文件 my_program。
4. 定义 .o 文件的规则:
%.o: %.c
gcc -c $< -o $@
这个规则定义了如何从 .c 文件创建对应的 .o 文件。
5. 定义清理规则:
.PHONY: clean
clean:
rm -f $(OBJECTS) my_program
这个清理规则定义了如何删除所有的 .o 文件和可执行文件 my_program。
运行示例
$ make
gcc -c foo.c -o foo.o
gcc -c bar.c -o bar.o
gcc -c baz.c -o baz.o
gcc -o my_program foo.o bar.o baz.o
$ ls
foo.c foo.o bar.c bar.o baz.c baz.o my_program Makefile
$ make clean
rm -f foo.o bar.o baz.o my_program
$ ls
foo.c bar.c baz.c Makefile



















