Makefile 是一个用于管理软件项目中编译、链接和其他任务的工具。它使用 Make 工具来自动化构建过程,确保只有修改过的文件才会重新编译。以下是一个简单但详细的 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 工具会自动检测并重新构建相关的文件。
编写一个非常复杂的 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 结合了之前提到的变量、通配符、自动变量等概念,用于构建包含多个源文件和库文件的项目。