版权声明 · 版权所有
本文 © AUTHOR_NAME year。保留所有权利。
许可:LICENSE_NAME
本文章为博主原创文章。遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
作者说明 · 学习记录
本站作为作者的学习记录站,不保证文章内容严谨或完全正确。
作者:GoDm@
用途:本文为作者的学习记录与实践笔记,用于记录学习过程、思考与示例代码(如有)。
欢迎朋友指正文章中的错误,联系邮箱:god_mao@foxmail.com
版权信息
在软件开发中,项目通常包含很多源文件,如果每次编译都手动敲命令,不仅繁琐,还容易出错。
Makefile 可以帮助我们自动化构建流程,大幅提升效率。本文将介绍 Makefile 的基础语法与常用用法。
1. 基础用法
一个典型的规则格式如下:
1 2
| target: dependencies <TAB> command
|
- target:目标文件,比如可执行文件或中间文件。
- dependencies:依赖文件(源文件、头文件等)。
- command:生成目标所需要执行的命令(必须以 TAB 缩进 开头)。
示例:
1 2
| main.o: main.c gcc -c main.c -o main.o
|
warning
由于makfile对空格、tab极其敏感,建议编写时打开编辑器的空格、tab显示,并避免不必要的空格,规范化书写。
2. 执行逻辑
当我们执行 make
时,大致流程如下:
-
解析 Makefile
make
会从当前目录寻找 Makefile
或 makefile
文件。
- 读取其中的规则、变量、伪目标等定义。
-
确定默认目标
- 一般是文件中的第一个目标(例如
app
)。
- 也可以通过命令行指定,例如:
-
检查依赖关系
- 从目标开始,逐层检查依赖文件是否存在、是否比目标文件更新。
- 如果依赖文件比目标文件“新”,说明目标需要重新生成。
-
执行命令
- 对需要更新的目标,执行其规则中定义的命令。
- 命令必须以 TAB 缩进 开头。
-
递归构建
- 如果依赖文件本身也是其他规则的目标,则会递归检查和执行。
- 直到所有依赖满足,才最终生成目标。
-
结束
1
| make: 'app' is up to date.
|
2.1. 🔄 执行流程示意图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| make │ ▼ 读取并解析 Makefile │ ▼ 确定要构建的目标 (默认/指定) │ ▼ 检查目标的依赖文件 │ ┌──────┴────────┐ │ │ 依赖比目标旧 依赖比目标新/不存在 │ │ 目标已是最新 执行规则命令 → 生成新目标
|
tips
可以使用 make -f [makefile_name] 指定使用某个makefile文件。
3. 伪目标 .PHONY
有些目标不是实际文件,而只是一个操作,例如 clean
。
这时建议使用 .PHONY 声明:
1 2 3
| .PHONY: clean clean: rm -r [filepath]
|
指定make目标文件:
4. 变量
Makefile 支持变量,常用于保存编译器或编译选项。
使用示例如下:
1 2 3 4 5 6 7 8 9 10 11
| CC = gcc CFLAGS = -Wall -g
app: main.o utils.o $(CC) $(CFLAGS) main.o utils.o -o app
main.o: main.c $(CC) $(CFLAGS) -c main.c -o main.o
utils.o: utils.c $(CC) $(CFLAGS) -c utils.c -o utils.o
|
4.1. 赋值符号
- =
我称之为最终赋值,同一个变量无论被赋值多次,永远取最后指定的值。
示例:
1 2 3
| VIR_A = A VIR_B = $(VIR_A) B VIR_A = AA
|
最后VIR_B的值是AA B。
- :=
立即赋值,正常逻辑的赋值号,类似于c语言的赋值号。
- ?=
如果变量在之前没有被赋值则赋值。
可以理解为 1 2 3
| #ifndef #define ... #endif
|
- +=
追加赋值,将值追加到变量中。
5. 隐含规则与通配符
Make 内置了一些规则,可以用简写方式:
$@
:目标文件名
$<
:第一个依赖文件
$^
:所有依赖文件
%
表示可以匹配任意长度的字符串,用于定义一类文件的生成规则。例如:
1 2
| %.o: %.c gcc -c $< -o $@
|
- 含义:
%.o
表示所有以 .o
结尾的目标文件。
%.c
表示所有以 .c
结尾的源文件。
$<
是第一个依赖文件(这里是 .c
文件)。
$@
是目标文件(这里是 .o
文件)。
- 作用:这条规则表示,所有
.c
文件可以通过编译生成对应的 .o
文件。
%
可以匹配文件名的某一部分,用于简化规则。例如:
- 含义:
build/%
表示目标文件在 build/
目录下。
src/%
表示依赖文件在 src/
目录下。
$<
是依赖文件,$@
是目标文件。
- 作用:这条规则表示,将
src/
目录下的文件复制到 build/
目录下。
在模式规则中,%
可以用于定义多个目标。例如:
1 2
| %.a: %.b %.c cat $^ > $@`
|
- 含义:
%.a
是目标文件。
%.b
和 %.c
是依赖文件。
$^
表示所有依赖文件,$@
是目标文件。
- 作用:这条规则表示,将
.b
和 .c
文件合并生成 .a
文件。
6. 条件分支
在 Makefile 中,我们可以使用条件语句来根据不同情况执行不同规则或定义变量。常见的有 ifeq
、ifneq
、ifdef
、ifndef
。
6.1. 语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ifeq (条件1, 条件2) else endif
ifneq (条件1, 条件2) endif
ifdef 变量名 endif
ifndef 变量名 endif
|
6.2. 示例一:根据平台选择clean执行方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| CC = gcc
ifeq ($(OS), Windows_NT) RM = del else RM = rm -f endif
app: main.o $(CC) main.o -o app
clean: $(RM) *.o app
|
6.3. 示例二:调试模式与发布模式
1 2 3 4 5 6 7 8 9 10 11
| CFLAGS = -Wall
ifeq ($(MODE), debug) CFLAGS += -g else CFLAGS += -O2 endif
app: main.o $(CC) $(CFLAGS) main.o -o app
|
使用方式:
1 2
| make MODE=debug make MODE=release
|
7. 函数
Makefile 内置了许多函数,用来处理字符串、文件名、路径等。
常见函数格式为:
下面介绍一些常用函数
7.1. subst
—— 字符串替换
1 2 3 4
| SRC = main.c utils.c OBJ = $(subst .c,.o,$(SRC))
|
7.2. patsubst
—— 模式替换
1
| $(patsubst pattern,replacement,text)
|
-
功能:更灵活的字符串替换,支持通配符 %
。
-
示例:
1 2 3 4
| SRC = main.c utils.c test.c OBJ = $(patsubst %.c,%.o,$(SRC))
|
7.3. wildcard
—— 获取文件列表
7.4. notdir
—— 去掉路径,只保留文件名
1 2 3
| FILES = src/main.c src/utils.c NAMES = $(notdir $(FILES))
|
7.5. dir
—— 获取路径部分
1 2 3
| FILES = src/main.c src/utils.c PATHS = $(dir $(FILES))
|
7.6. basename
、 addsuffix
和 addprefix
—— 批量处理文件名
1 2 3 4 5 6 7 8 9 10 11 12 13
| FILES = main.c utils.c
NAMES = $(basename $(FILES))
OBJS = $(addsuffix .o,$(NAMES))
OBJS = $(addprefix -I,$(NAMES))
|
7.7. shell
—— 执行 shell 命令
1
| DATE = $(shell date +%Y-%m-%d)
|
这样可以在 Makefile 中直接使用系统命令的输出。
8. 完整示例程序
这是一个完整Makefile示例程序,用于将c语言程序编译为可执行的二进制bin文件。它可以制成镜像供SoC烧录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
|
ROOT_DIR := .
BUILD_DIR := build
BIN_DIR := bin
NAME := key
LDS = imx.lds
SRCS = $(shell find $(ROOT_DIR) -name "*.c")
OBJS = $(BUILD_DIR)/startup.o OBJS += $(patsubst %.c,$(BUILD_DIR)/%.o,$(SRCS))
INC_DIRS = $(shell find $(ROOT_DIR) -type f -name "*.h" -exec dirname {} \; | sort -u) INCLUDES = $(addprefix -I, $(INC_DIRS))
CC := arm-none-eabi-
GCC := $(CC)gcc
LD := $(CC)ld
OBJCOPY := $(CC)objcopy
OBJDUMP := $(CC)objdump
GCC_FLAGS = $(INCLUDES) -Wall -nostdlib -c
LD_FLAGS = -T$(LDS)
OBJDUMP_FLAGS = -D -m arm $(BUILD_DIR)/$(NAME).elf > $(BUILD_DIR)/$(NAME).dis
$(BIN_DIR)/$(NAME).bin: $(OBJS) $(LD) $(LD_FLAGS) $(OBJS) -o $(BUILD_DIR)/$(NAME).elf $(OBJDUMP) $(OBJDUMP_FLAGS) $(OBJCOPY) -O binary -S $(BUILD_DIR)/$(NAME).elf $@
$(BUILD_DIR)/startup.o: startup.S $(GCC) $(GCC_FLAGS) $< -o $@
$(BUILD_DIR)/%.o: %.c mkdir -p $(dir $@) $(GCC) $(GCC_FLAGS) -c $< -o $@
.PHONY: clean clean: rm -r $(BUILD_DIR)/* $(BIN_DIR)/*
|