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