Makefile 是一个用于管理软件项目中编译、链接和其他任务的工具。它使用 Make 工具来自动化构建过程,确保只有修改过的文件才会重新编译。以下是一个简单但详细的 Makefile 教程,帮助你入门。
1. Makefile 的基础结构
一个基本的 Makefile 包含规则(rules)、目标(targets)、依赖关系(dependencies)和命令(commands)。以下是一个简单的例子:
# 注释以 '#' 开头
# 定义变量
CC = gcc
CFLAGS = -Wall
# 第一个目标是默认目标
all: my_program
# 目标和依赖关系
my_program: main.o utils.o
$(CC) $(CFLAGS) -o my_program main.o utils.o
# 编译规则
main.o: main.c
$(CC) $(CFLAGS) -c main.c
utils.o: utils.c
$(CC) $(CFLAGS) -c utils.c
# 清理规则
clean:
rm -f my_program *.o
在这个例子中:
my_program
是目标,依赖于main.o
和utils.o
。main.o
和utils.o
分别是目标,依赖于对应的源文件和头文件。- 每个目标都有相应的规则,指定了如何生成它们的命令。
你可以通过执行 make my_program
来构建可执行文件 my_program
。如果某个文件发生了更改,Make 工具会自动检测并重新构建相关的文件。
2. Makefile 中的关键概念
-
目标(target): 是构建过程中的输出文件,可以是可执行文件、库文件等。 (my_program: main.o utils.o 中my_program就是依赖)
-
依赖关系(dependencies): 是目标生成所依赖的文件或其他目标。(my_program: main.o utils.o 中main.o util.o就是依赖)
-
规则(rule): 定义了如何生成目标的规则。(my_program: main.o utils.o 中my_program 是由main.o和util.o生成)。
-
命令(command): 是实际执行的操作,通常是编译、链接等命令。(如$(CC) $(CFLAGS) -o my_program main.o utils.o 就是命令)
-
变量(variable): 是用于存储值的名称,可以简化 Makefile。(如CC ,CFLAGS)
3.复杂的makefile
编写一个非常复杂的 Makefile 涉及到一个实际的项目,而一个完整的项目的 Makefile 往往更为庞大。以下是一个简化的示例,展示了如何使用上述所有的知识来构建一个具有多个源文件、目录结构和不同规则的项目。请注意,实际项目可能需要更多的规则和变量以满足复杂的构建需求。
假设有如下项目结构
project/
|-- src/
| |-- main.c
| |-- utils.c
| |-- main.h
| |-- utils.h
|-- lib/
| |-- math/
| |-- add.c
| |-- subtract.c
| |-- add.h
| |-- subtract.h
|-- build/
|-- bin/
以下是一个复杂的 Makefile 示例:
# 定义变量
CC = gcc
CFLAGS = -Wall
SRC_DIR = src
LIB_DIR = lib
BUILD_DIR = build
BIN_DIR = bin
# 通配符匹配源文件
SRCS := $(wildcard $(SRC_DIR)/*.c)
OBJS := $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(SRCS))
LIB_SRCS := $(wildcard $(LIB_DIR)/*/*.c)
LIB_OBJS := $(patsubst $(LIB_DIR)/%.c, $(BUILD_DIR)/%.o, $(LIB_SRCS))
# 默认目标:构建可执行文件 my_program
all: $(BIN_DIR)/my_program
# 构建可执行文件规则
$(BIN_DIR)/my_program: $(OBJS) $(LIB_OBJS)
$(CC) $(CFLAGS) -o $@ $^
# 通配符规则:编译所有源文件到目标文件
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
$(CC) $(CFLAGS) -c $< -o $@
# 通配符规则:编译所有库源文件到目标文件
$(BUILD_DIR)/%.o: $(LIB_DIR)/%.c
$(CC) $(CFLAGS) -c $< -o $@
# 清理规则
clean:
rm -rf $(BUILD_DIR)/* $(BIN_DIR)/*
# 输出详细信息
info:
@echo "Source files: $(SRCS)"
@echo "Object files: $(OBJS)"
@echo "Library source files: $(LIB_SRCS)"
@echo "Library object files: $(LIB_OBJS)"
因为这个makefile包含了通配符,和内置变量,看起来有点困难,但是基本的规则,目标和依赖没有变化。
-
通配符:
*
:匹配任意长度的字符,但不包括路径分隔符(如 /
)。
# 匹配所有以 .c 结尾的源文件
*.c: $(CC) $(CFLAGS) -c $< -o $@
%
:匹配任意长度的字符,包括路径分隔符。
# 匹配所有以 .o 结尾的目标文件,对应的源文件是同名的 .c 文件
%.o: %.c $(CC) $(CFLAGS) -c $< -o $@
?
:匹配任意单个字符。
# 匹配所有以 a、b 或 c 开头,接着是一个字符,然后是 .c 结尾的源文件
[abc]?.c: $(CC) $(CFLAGS) -c $< -o $@
[]
:匹配括号内的任意一个字符。
# 匹配所有以 a、b 或 c 开头,接着是 .c 结尾的源文件
[abc]*.c: $(CC) $(CFLAGS) -c $< -o $@
内置变量:
在 Makefile 中,有一些内置的自动变量用于方便地引用特定信息。以下是一些常用的内置变量:
$@
: 表示规则中的目标文件名
my_target: dependency
command $@
$<
: 表示规则中的第一个依赖文件名
my_target: dependency
command $<
$^
: 表示规则中的所有依赖文件列表。
my_target: dependency1 dependency2
command $^
$?
: 表示规则中所有比目标文件更新的依赖文件列表
my_target: dependency1 dependency2
command $?
$(@D)
和 $(@F)
: 分别表示目标文件所在的目录和文件名
my_target: dependency
command $(@D)/$(@F)
这些内置变量使得在 Makefile 中引用文件名、目录名等信息更加方便。使用它们可以避免在规则中硬编码文件名,使得 Makefile 更加灵活和易维护。需要注意的是,这些变量只有在规则的执行过程中才会被正确赋值。
下面逐行解释每一部分:
-
CC = gcc
:定义变量CC
为编译器的命令,使用 GCC。 -
CFLAGS = -Wall
:定义变量CFLAGS
为编译选项,包括开启所有警告。 -
SRC_DIR = src
:定义变量SRC_DIR
为源代码目录。 -
LIB_DIR = lib
:定义变量LIB_DIR
为库目录。 -
BUILD_DIR = build
:定义变量BUILD_DIR
为构建目录,用于存放生成的目标文件。 -
BIN_DIR = bin
:定义变量BIN_DIR
为输出二进制文件目录,用于存放生成的可执行文件。 -
SRCS := $(wildcard $(SRC_DIR)/*.c)
:使用通配符匹配源文件,生成源文件列表SRCS
。(wildcard
是一个函数,用于展开通配符,获取符合通配符模式的文件列表。) -
OBJS := $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(SRCS))
:使用模式替换,生成对应的目标文件列表OBJS
。(patsubst
是 Makefile 中的一个函数,用于执行模式替换(pattern substitution)) -
LIB_SRCS := $(wildcard $(LIB_DIR)/*/*.c)
:使用通配符匹配库源文件,生成库源文件列表LIB_SRCS
。 -
LIB_OBJS := $(patsubst $(LIB_DIR)/%.c, $(BUILD_DIR)/%.o, $(LIB_SRCS))
:使用模式替换,生成对应的库目标文件列表LIB_OBJS
。 -
all: $(BIN_DIR)/my_program
:定义默认目标all
,构建可执行文件my_program
。 -
$(BIN_DIR)/my_program: $(OBJS) $(LIB_OBJS)
:构建可执行文件规则,依赖于所有源文件和库文件的目标文件。 -
$(CC) $(CFLAGS) -o $@ $^
:构建可执行文件命令,使用变量引用。 -
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
:通配符规则,编译所有源文件到目标文件。 -
$(CC) $(CFLAGS) -c $< -o $@
:通配符规则的命令,使用自动变量$<
和$@
。 -
$(BUILD_DIR)/%.o: $(LIB_DIR)/%.c
:通配符规则,编译所有库源文件到目标文件。 -
$(CC) $(CFLAGS) -c $< -o $@
:通配符规则的命令,使用自动变量$<
和$@
。 -
clean: rm -rf $(BUILD_DIR)/* $(BIN_DIR)/*
:定义清理规则,删除构建目录和输出目录下的所有文件。 -
info: @echo "Source files: $(SRCS)" ...
:输出详细信息规则,显示源文件、目标文件等详细信息。使用@echo
避免输出规则本身。
这个 Makefile 结合了之前提到的变量、通配符、自动变量等概念,用于构建包含多个源文件和库文件的项目。