作者说明 · 学习记录
本站作为作者的学习记录站,不保证文章内容严谨或完全正确。
版权信息

warning

本文章为博主原创文章。遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。


在软件开发中,项目通常包含很多源文件,如果每次编译都手动敲命令,不仅繁琐,还容易出错。
Makefile 可以帮助我们自动化构建流程,大幅提升效率。本文将介绍 Makefile 的基础语法与常用用法。


1. 基础用法

一个典型的规则格式如下:

1
2
target: dependencies
<TAB> command

示例:

1
2
main.o: main.c
gcc -c main.c -o main.o

warning

由于makfile对空格、tab极其敏感,建议编写时打开编辑器的空格、tab显示,并避免不必要的空格,规范化书写。

2. 执行逻辑[1]

当我们执行 make 时,大致流程如下:

  1. 解析 Makefile

    • make 会从当前目录寻找 Makefilemakefile 文件。
    • 读取其中的规则、变量、伪目标等定义。
  2. 确定默认目标

    • 一般是文件中的第一个目标(例如 app)。
    • 也可以通过命令行指定,例如:
1
make clean
  1. 检查依赖关系

    • 从目标开始,逐层检查依赖文件是否存在、是否比目标文件更新。
    • 如果依赖文件比目标文件“新”,说明目标需要重新生成。
  2. 执行命令

    • 对需要更新的目标,执行其规则中定义的命令。
    • 命令必须以 TAB 缩进 开头。
  3. 递归构建

    • 如果依赖文件本身也是其他规则的目标,则会递归检查和执行。
    • 直到所有依赖满足,才最终生成目标。
  4. 结束

    • 如果所有目标都已是最新,则 make 会提示:
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目标文件:

1
make clean

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。

5. 隐含规则与通配符

Make 内置了一些规则,可以用简写方式:

% 表示可以匹配任意长度的字符串,用于定义一类文件的生成规则。例如:

1
2
%.o: %.c     
gcc -c $< -o $@

% 可以匹配文件名的某一部分,用于简化规则。例如:

1
2
build/%: src/%     
cp $< $@

在模式规则中,% 可以用于定义多个目标。例如:

1
2
%.a: %.b %.c     
cat $^ > $@`

6. 条件分支

在 Makefile 中,我们可以使用条件语句来根据不同情况执行不同规则或定义变量。常见的有 ifeqifneqifdefifndef

6.1. 语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ifeq (条件1, 条件2)
# 当 条件1 == 条件2 时执行这里
else
# 否则执行这里
endif

ifneq (条件1, 条件2)
# 当 条件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 内置了许多函数,用来处理字符串、文件名、路径等。
常见函数格式为:

1
$(函数名 参数1 参数2 ...)

下面介绍一些常用函数

7.1. subst —— 字符串替换

1
$(subst from,to,text)
1
2
3
4
SRC = main.c utils.c 
OBJ = $(subst .c,.o,$(SRC))

# 结果:OBJ = main.o utils.o

7.2. patsubst —— 模式替换

1
$(patsubst pattern,replacement,text)
1
2
3
4
SRC = main.c utils.c test.c
OBJ = $(patsubst %.c,%.o,$(SRC))

# 结果:OBJ = main.o utils.o test.o

7.3. wildcard —— 获取文件列表

1
$(wildcard pattern)
1
2
SRC = $(wildcard *.c)
# 结果:SRC = 当前目录下所有 .c 文件

7.4. notdir —— 去掉路径,只保留文件名

1
2
3
FILES = src/main.c src/utils.c
NAMES = $(notdir $(FILES))
# 结果:NAMES = main.c utils.c

7.5. dir —— 获取路径部分

1
2
3
FILES = src/main.c src/utils.c
PATHS = $(dir $(FILES))
# 结果:PATHS = src/ src/

7.6. basenameaddsuffixaddprefix —— 批量处理文件名

1
2
3
4
5
6
7
8
9
10
11
12
13
FILES = main.c utils.c

# 去掉后缀
NAMES = $(basename $(FILES))
# NAMES = main utils

# 批量添加后缀
OBJS = $(addsuffix .o,$(NAMES))
# OBJS = main.o utils.o

# 批量添加前缀
OBJS = $(addprefix -I,$(NAMES))
# OBJS = -Imain -Iutils

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
#0###########################################################

# 设置目录变量,方便统一管理和修改
# 当前根目录:
ROOT_DIR := .

# 中间目标文件(.o)输出目录:
BUILD_DIR := build

# 最终生成的二进制文件(.bin)目录:
BIN_DIR := bin

# 工程名
NAME := key

# 指定链接脚本
LDS = imx.lds

#############################################################

#1###########################################################

# 自动查找 src/ 目录下的所有 .c 文件
SRCS = $(shell find $(ROOT_DIR) -name "*.c")

# 将 SRC中的 xxx.c 转换为 build/xxx.o
# 同时添加 build/startup.o(汇编启动文件)
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))

#############################################################

#2###########################################################

# 设置编译工具(使用 ARM 的交叉编译工具链)
CC := arm-none-eabi-

# 编译器(用于 .c 和 .S 文件):
GCC := $(CC)gcc

# 链接器:
LD := $(CC)ld

# 用于将 elf 转为 bin 格式:
OBJCOPY := $(CC)objcopy

# 用于反汇编
OBJDUMP := $(CC)objdump

# 编译选项(GCC 编译阶段)
# -I:指定头文件搜索目录
# -Wall:打开所有警告
# -O2:优化等级 2(推荐用于 release)
# -nostdlib:不链接标准库(适用于裸机)
# -c:只编译,不链接
GCC_FLAGS = $(INCLUDES) -Wall -nostdlib -c

# 链接器选项
LD_FLAGS = -T$(LDS)

# 使用 objdump 工具对生成的 ELF 文件进行反汇编
# -D:反汇编所有节(包括代码段、启动代码等)
# -m arm:指定目标架构为 ARM
# .elf:输入的可执行文件
# > .dis:将反汇编结果输出为 .dis 文本文件
OBJDUMP_FLAGS = -D -m arm $(BUILD_DIR)/$(NAME).elf > $(BUILD_DIR)/$(NAME).dis

#############################################################

#3###########################################################

# 目标:生成最终的二进制文件 bin/$(NAME).bin
$(BIN_DIR)/$(NAME).bin: $(OBJS)
# 链接所有 .o 文件生成 elf 格式可执行文件
    $(LD) $(LD_FLAGS) $(OBJS) -o $(BUILD_DIR)/$(NAME).elf
# 反汇编 调试用
    $(OBJDUMP) $(OBJDUMP_FLAGS)
# 把 elf 文件转换为裸机二进制文件(无符号、无头信息)
    $(OBJCOPY) -O binary -S $(BUILD_DIR)/$(NAME).elf $@

# 编译汇编启动文件 startup.S,生成 build/startup.o
$(BUILD_DIR)/startup.o: startup.S
# 注意 startup.S 是汇编文件,用 gcc 编译也可以,默认会调用汇编器
    $(GCC) $(GCC_FLAGS) $< -o $@

# 编译每个 .c 文件到 build/xxx.o
# $@:目标文件(例如 build/main.o)
# $<:依赖的源文件(例如 src/main.c)
$(BUILD_DIR)/%.o: %.c
# 修复由于没有文件夹报错
    mkdir -p $(dir $@)
    $(GCC) $(GCC_FLAGS) -c $< -o $@

#############################################################

.PHONY: clean
clean:
    rm -r $(BUILD_DIR)/* $(BIN_DIR)/*

  1. 仅作了解 ↩︎

MakeFile简明指南

创建时间:9月 07, 2025

最后更新:9月 08, 2025

字数统计:2.4k字

预计阅读:9min